sequel 5.11.0 → 5.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/doc/advanced_associations.rdoc +132 -14
  4. data/doc/postgresql.rdoc +14 -0
  5. data/doc/release_notes/5.12.0.txt +141 -0
  6. data/lib/sequel/adapters/ado/mssql.rb +1 -1
  7. data/lib/sequel/adapters/oracle.rb +5 -6
  8. data/lib/sequel/adapters/postgres.rb +18 -5
  9. data/lib/sequel/adapters/shared/mysql.rb +5 -5
  10. data/lib/sequel/adapters/sqlite.rb +0 -5
  11. data/lib/sequel/adapters/tinytds.rb +0 -5
  12. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +2 -5
  13. data/lib/sequel/core.rb +6 -1
  14. data/lib/sequel/dataset/graph.rb +25 -9
  15. data/lib/sequel/dataset/placeholder_literalizer.rb +47 -17
  16. data/lib/sequel/dataset/prepared_statements.rb +86 -18
  17. data/lib/sequel/dataset/sql.rb +5 -1
  18. data/lib/sequel/extensions/caller_logging.rb +79 -0
  19. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  20. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
  21. data/lib/sequel/model/associations.rb +56 -23
  22. data/lib/sequel/model/base.rb +3 -3
  23. data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
  24. data/lib/sequel/plugins/static_cache.rb +9 -8
  25. data/lib/sequel/plugins/tactical_eager_loading.rb +63 -1
  26. data/lib/sequel/version.rb +1 -1
  27. data/spec/adapters/oracle_spec.rb +44 -0
  28. data/spec/adapters/postgres_spec.rb +39 -0
  29. data/spec/core/dataset_spec.rb +23 -9
  30. data/spec/core/object_graph_spec.rb +314 -284
  31. data/spec/extensions/caller_logging_spec.rb +52 -0
  32. data/spec/extensions/eager_graph_eager_spec.rb +100 -0
  33. data/spec/extensions/finder_spec.rb +1 -1
  34. data/spec/extensions/prepared_statements_spec.rb +7 -12
  35. data/spec/extensions/static_cache_spec.rb +14 -0
  36. data/spec/extensions/tactical_eager_loading_spec.rb +262 -1
  37. data/spec/integration/associations_test.rb +72 -0
  38. data/spec/integration/dataset_test.rb +3 -3
  39. data/spec/model/eager_loading_spec.rb +90 -0
  40. metadata +8 -2
@@ -0,0 +1,52 @@
1
+ require_relative "spec_helper"
2
+ require 'logger'
3
+
4
+ describe "caller_logging extension" do
5
+ before do
6
+ @db = Sequel.mock(:extensions=>[:caller_logging])
7
+ @log_stream = StringIO.new
8
+ @db.loggers << Logger.new(@log_stream)
9
+ @ds = @db[:items]
10
+ end
11
+
12
+ exec_sql_line = __LINE__ + 2
13
+ def exec_sql(sql)
14
+ @db[sql].all
15
+ end
16
+
17
+ it "should log caller information, skipping internal Sequel code" do
18
+ exec_sql("SELECT * FROM items")
19
+ @log_stream.rewind
20
+ lines = @log_stream.read.split("\n")
21
+ lines.length.must_equal 1
22
+ lines[0].must_match(/ \(source: #{__FILE__}:#{exec_sql_line}:in `exec_sql'\) SELECT \* FROM items\z/)
23
+ end
24
+
25
+ it "should allow formatting of caller information" do
26
+ @db.caller_logging_formatter = lambda{|line| line.sub(/\A.+(caller_logging_spec\.rb:\d+).+\z/, '\1:')}
27
+ exec_sql("SELECT * FROM items")
28
+ @log_stream.rewind
29
+ lines = @log_stream.read.split("\n")
30
+ lines.length.must_equal 1
31
+ lines[0].must_match(/ caller_logging_spec\.rb:#{exec_sql_line}: SELECT \* FROM items\z/)
32
+ end
33
+
34
+ it "should allow ignoring additional caller lines in application" do
35
+ @db.caller_logging_ignore = /exec_sql/
36
+ exec_sql("SELECT * FROM items"); line = __LINE__
37
+ @log_stream.rewind
38
+ lines = @log_stream.read.split("\n")
39
+ lines.length.must_equal 1
40
+ lines[0].must_match(/ \(source: #{__FILE__}:#{line}:in `block.+\) SELECT \* FROM items\z/)
41
+ end
42
+
43
+ it "should not log caller information if all callers lines are filtered" do
44
+ @db.caller_logging_ignore = /./
45
+ exec_sql("SELECT * FROM items"); line = __LINE__
46
+ @log_stream.rewind
47
+ lines = @log_stream.read.split("\n")
48
+ lines.length.must_equal 1
49
+ lines[0].must_match(/ SELECT \* FROM items\z/)
50
+ lines[0].wont_match(/ source: #{__FILE__}/)
51
+ end
52
+ end
@@ -0,0 +1,100 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "eager_graph_eager plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:items))
6
+ @c.columns :id, :parent_id
7
+ @c.plugin :eager_graph_eager
8
+ @c.one_to_many :children, :class=>@c, :key=>:parent_id
9
+ @c.many_to_one :parent, :class=>@c
10
+ @c.db.sqls
11
+ end
12
+
13
+ it "should support Dataset#eager_graph_eager for eager loading dependencies of eager_graph associations for one_to_many associations" do
14
+ a = @c.eager_graph(:children).
15
+ with_fetch([{:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1}, {:id=>2, :parent_id=>nil}]).
16
+ eager_graph_eager([:children], :children=>proc{|ds| ds.with_fetch(:id=>4, :parent_id=>3)}).
17
+ all
18
+ @c.db.sqls.must_equal ["SELECT items.id, items.parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM items LEFT OUTER JOIN items AS children ON (children.parent_id = items.id)",
19
+ "SELECT * FROM items WHERE (items.parent_id IN (3))"]
20
+
21
+ a.must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
22
+ a.map(&:children).must_equal [[@c.load(:id=>3, :parent_id=>1)], []]
23
+ a.first.children.first.children.must_equal [@c.load(:id=>4, :parent_id=>3)]
24
+ @c.db.sqls.must_equal []
25
+ end
26
+
27
+ it "should support Dataset#eager_graph_eager for eager loading dependencies of eager_graph associations for many_to_one associations" do
28
+ a = @c.eager_graph(:parent).
29
+ with_fetch([{:id=>4, :parent_id=>3, :parent_id_0=>3, :parent_parent_id=>1}, {:id=>2, :parent_id=>nil}]).
30
+ eager_graph_eager([:parent], :parent=>proc{|ds| ds.with_fetch(:id=>1, :parent_id=>nil)}).
31
+ all
32
+ @c.db.sqls.must_equal ["SELECT items.id, items.parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id FROM items LEFT OUTER JOIN items AS parent ON (parent.id = items.parent_id)",
33
+ "SELECT * FROM items WHERE (items.id IN (1))"]
34
+
35
+ a.must_equal [@c.load(:id=>4, :parent_id=>3), @c.load(:id=>2, :parent_id=>nil)]
36
+ a.map(&:parent).must_equal [@c.load(:id=>3, :parent_id=>1), nil]
37
+ a.first.parent.parent.must_equal @c.load(:id=>1, :parent_id=>nil)
38
+ @c.db.sqls.must_equal []
39
+ end
40
+
41
+ it "should support multiple entries in dependency chain" do
42
+ a = @c.eager_graph(:children=>:children).
43
+ with_fetch([{:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1, :children_0_id=>4, :children_0_parent_id=>3}, {:id=>2, :parent_id=>nil}]).
44
+ eager_graph_eager([:children, :children], :children=>proc{|ds| ds.with_fetch(:id=>5, :parent_id=>4)}).
45
+ all
46
+ @c.db.sqls.must_equal ["SELECT items.id, items.parent_id, children.id AS children_id, children.parent_id AS children_parent_id, children_0.id AS children_0_id, children_0.parent_id AS children_0_parent_id FROM items LEFT OUTER JOIN items AS children ON (children.parent_id = items.id) LEFT OUTER JOIN items AS children_0 ON (children_0.parent_id = children.id)",
47
+ "SELECT * FROM items WHERE (items.parent_id IN (4))"]
48
+
49
+ a.must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
50
+ a.map(&:children).must_equal [[@c.load(:id=>3, :parent_id=>1)], []]
51
+ a.first.children.first.children.must_equal [@c.load(:id=>4, :parent_id=>3)]
52
+ a.first.children.first.children.first.children.must_equal [@c.load(:id=>5, :parent_id=>4)]
53
+ @c.db.sqls.must_equal []
54
+ end
55
+
56
+ it "should support multiple dependency chains" do
57
+ a = @c.eager_graph(:children, :parent).
58
+ with_fetch([{:id=>4, :parent_id=>3, :children_id=>5, :children_parent_id=>4, :parent_id_0=>3, :parent_parent_id=>1}, {:id=>2, :parent_id=>nil}]).
59
+ eager_graph_eager([:children], :children=>proc{|ds| ds.with_fetch(:id=>6, :parent_id=>5)}).
60
+ eager_graph_eager([:parent], :parent=>proc{|ds| ds.with_fetch(:id=>1, :parent_id=>nil)}).
61
+ all
62
+ @c.db.sqls.must_equal ["SELECT items.id, items.parent_id, children.id AS children_id, children.parent_id AS children_parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id FROM items LEFT OUTER JOIN items AS children ON (children.parent_id = items.id) LEFT OUTER JOIN items AS parent ON (parent.id = items.parent_id)",
63
+ "SELECT * FROM items WHERE (items.parent_id IN (5))",
64
+ "SELECT * FROM items WHERE (items.id IN (1))"]
65
+
66
+ a.must_equal [@c.load(:id=>4, :parent_id=>3), @c.load(:id=>2, :parent_id=>nil)]
67
+ a.map(&:children).must_equal [[@c.load(:id=>5, :parent_id=>4)], []]
68
+ a.map(&:parent).must_equal [@c.load(:id=>3, :parent_id=>1), nil]
69
+ a.first.children.first.children.must_equal [@c.load(:id=>6, :parent_id=>5)]
70
+ a.first.parent.parent.must_equal @c.load(:id=>1, :parent_id=>nil)
71
+ @c.db.sqls.must_equal []
72
+ end
73
+
74
+ it "should raise for invalid dependency chains" do
75
+ proc{@c.dataset.eager_graph_eager([], :children)}.must_raise Sequel::Error
76
+ proc{@c.dataset.eager_graph_eager(:children, :children)}.must_raise Sequel::Error
77
+ proc{@c.dataset.eager_graph_eager(['foo'], :children)}.must_raise Sequel::Error
78
+ proc{@c.dataset.eager_graph_eager([:foo], :children)}.must_raise Sequel::Error
79
+ end
80
+
81
+ it "should handle cases where not all associated objects are unique" do
82
+ a = @c.eager_graph(:parent=>:children).
83
+ with_fetch([
84
+ {:id=>4, :parent_id=>3, :parent_id_0=>3, :parent_parent_id=>1, :children_id=>4, :children_parent_id=>3},
85
+ {:id=>5, :parent_id=>3, :parent_id_0=>3, :parent_parent_id=>1, :children_id=>4, :children_parent_id=>3},
86
+ {:id=>4, :parent_id=>3, :parent_id_0=>3, :parent_parent_id=>1, :children_id=>5, :children_parent_id=>3},
87
+ {:id=>5, :parent_id=>3, :parent_id_0=>3, :parent_parent_id=>1, :children_id=>5, :children_parent_id=>3}
88
+ ]).
89
+ eager_graph_eager([:parent], :parent=>proc{|ds| ds.with_fetch(:id=>1, :parent_id=>nil)}).
90
+ all
91
+ @c.db.sqls.must_equal ["SELECT items.id, items.parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM items LEFT OUTER JOIN items AS parent ON (parent.id = items.parent_id) LEFT OUTER JOIN items AS children ON (children.parent_id = parent.id)",
92
+ "SELECT * FROM items WHERE (items.id IN (1))"]
93
+
94
+ a.must_equal [@c.load(:id=>4, :parent_id=>3), @c.load(:id=>5, :parent_id=>3)]
95
+ a.map(&:parent).must_equal [@c.load(:id=>3, :parent_id=>1), @c.load(:id=>3, :parent_id=>1)]
96
+ a.map(&:parent).map(&:children).must_equal [a, a]
97
+ a.map(&:parent).map(&:parent).must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>1, :parent_id=>nil)]
98
+ @c.db.sqls.must_equal []
99
+ end
100
+ end
@@ -118,7 +118,7 @@ describe Sequel::Model, ".prepared_finder" do
118
118
  @db.extend_datasets do
119
119
  def select_sql
120
120
  sql = super
121
- sql << ' -- prepared' if is_a?(Sequel::Dataset::PreparedStatementMethods)
121
+ sql << ' -- prepared' if is_a?(Sequel::Dataset::PreparedStatementMethods) && !opts[:sql]
122
122
  sql
123
123
  end
124
124
  end
@@ -61,7 +61,7 @@ describe "prepared_statements plugin" do
61
61
  server(:default).with_sql_first(insert_select_sql(h))
62
62
  end
63
63
  def insert_select_sql(*v)
64
- "#{insert_sql(*v)} RETURNING #{(opts[:returning] && !opts[:returning].empty?) ? opts[:returning].map{|c| literal(c)}.join(', ') : '*'}"
64
+ insert_sql(*v) << " RETURNING #{(opts[:returning] && !opts[:returning].empty?) ? opts[:returning].map{|c| literal(c)}.join(', ') : '*'}"
65
65
  end
66
66
  end
67
67
  @c.create(:name=>'foo').must_equal @c.load(:id=>1, :name=>'foo', :i => 2)
@@ -107,7 +107,7 @@ describe "prepared_statements plugin" do
107
107
  server(:default).with_sql_first(insert_select_sql(h))
108
108
  end
109
109
  def insert_select_sql(*v)
110
- "#{insert_sql(*v)} RETURNING #{(opts[:returning] && !opts[:returning].empty?) ? opts[:returning].map{|c| literal(c)}.join(', ') : '*'}"
110
+ insert_sql(*v) << " RETURNING #{(opts[:returning] && !opts[:returning].empty?) ? opts[:returning].map{|c| literal(c)}.join(', ') : '*'}"
111
111
  end
112
112
  end
113
113
  @c.new(:name=>'foo').set_server(:read_only).save.must_equal @c.load(:id=>1, :name=>'foo', :i => 2)
@@ -130,21 +130,16 @@ describe "prepared_statements plugin" do
130
130
  it "should correctly handle with schema type when placeholder type specifiers are required" do
131
131
  @c.dataset = @ds.with_extend do
132
132
  def requires_placeholder_type_specifiers?; true end
133
- def prepare(*)
134
- super.with_extend do
133
+ def prepared_statement_modules
134
+ [Module.new do
135
135
  def literal_symbol_append(sql, v)
136
- if @opts[:bind_vars] && (match = /\A\$(.*)\z/.match(v.to_s))
137
- s = match[1].split('__')[0].to_sym
138
- if prepared_arg?(s)
139
- literal_append(sql, prepared_arg(s))
140
- else
141
- sql << v.to_s
142
- end
136
+ if @opts[:bind_vars] && /\A\$(.*)\z/ =~ v
137
+ literal_append(sql, prepared_arg($1.split('__')[0].to_sym))
143
138
  else
144
139
  super
145
140
  end
146
141
  end
147
- end
142
+ end]
148
143
  end
149
144
  end
150
145
  @c.db_schema[:id][:type] = :integer
@@ -264,6 +264,20 @@ describe "Sequel::Plugins::StaticCache" do
264
264
  @c.as_hash[3].must_equal @c.all.first
265
265
  @db.sqls.must_equal ['SELECT * FROM t2']
266
266
  end
267
+
268
+ it "should have load_cache" do
269
+ a = @c.all.sort_by{|o| o.id}
270
+ a.first.must_equal @c1
271
+ a.last.must_equal @c2
272
+ @db.sqls.must_equal []
273
+
274
+ @c.load_cache
275
+
276
+ a = @c.all.sort_by{|o| o.id}
277
+ a.first.must_equal @c1
278
+ a.last.must_equal @c2
279
+ @db.sqls.must_equal ['SELECT * FROM t']
280
+ end
267
281
  end
268
282
 
269
283
  describe "without options" do
@@ -1,6 +1,6 @@
1
1
  require_relative "spec_helper"
2
2
 
3
- describe "Sequel::Plugins::TacticalEagerLoading" do
3
+ describe "tactical_eager_loading plugin" do
4
4
  def sql_match(*args)
5
5
  sqls = DB.sqls
6
6
  sqls.length.must_equal args.length
@@ -139,3 +139,264 @@ describe "Sequel::Plugins::TacticalEagerLoading" do
139
139
  Marshal.dump(ts.map{|x| x.marshallable!})
140
140
  end
141
141
  end
142
+
143
+ describe "tactical_eager_loading plugin eager_graph_support" do
144
+ before do
145
+ @c = Class.new(Sequel::Model)
146
+ @c.class_eval do
147
+ set_dataset DB[:t]
148
+ columns :id, :parent_id
149
+ plugin :tactical_eager_loading
150
+ many_to_one :parent, :class=>self
151
+ one_to_many :children, :class=>self, :key=>:parent_id
152
+ end
153
+ DB.reset
154
+ end
155
+
156
+ it "should allow eager loading of associated objects from one_to_many associated objects retrieved via eager_graph" do
157
+ a = @c.eager_graph(:children).
158
+ with_fetch([
159
+ {:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1},
160
+ {:id=>1, :parent_id=>nil, :children_id=>4, :children_parent_id=>1},
161
+ {:id=>2, :parent_id=>nil, :children_id=>5, :children_parent_id=>2}
162
+ ]).all
163
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM t LEFT OUTER JOIN t AS children ON (children.parent_id = t.id)"]
164
+
165
+ a.must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
166
+ a.map(&:children).must_equal [
167
+ [@c.load(:id=>3, :parent_id=>1),
168
+ @c.load(:id=>4, :parent_id=>1)],
169
+ [@c.load(:id=>5, :parent_id=>2)]]
170
+ @c.db.sqls.must_equal []
171
+
172
+ @c.dataset = @c.dataset.with_fetch([[{:id=>6, :parent_id=>3}, {:id=>7, :parent_id=>4}, {:id=>8, :parent_id=>5}],
173
+ [{:id=>9, :parent_id=>6}, {:id=>10, :parent_id=>7}, {:id=>11, :parent_id=>8}]])
174
+
175
+ a.map(&:children).map{|v| v.map(&:children)}.must_equal [
176
+ [[@c.load(:id=>6, :parent_id=>3)],
177
+ [@c.load(:id=>7, :parent_id=>4)]],
178
+ [[@c.load(:id=>8, :parent_id=>5)]]]
179
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.parent_id IN (3, 4, 5))"]
180
+
181
+ a.map(&:children).map{|v| v.map(&:children).map{|v1| v1.map(&:children)}}.must_equal [
182
+ [[[@c.load(:id=>9, :parent_id=>6)]],
183
+ [[@c.load(:id=>10, :parent_id=>7)]]],
184
+ [[[@c.load(:id=>11, :parent_id=>8)]]]]
185
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.parent_id IN (6, 7, 8))"]
186
+ end
187
+
188
+ it "should allow eager loading of associated objects from many_to_one associated objects retrieved via eager_graph" do
189
+ a = @c.eager_graph(:parent).
190
+ with_fetch([
191
+ {:id=>9, :parent_id=>6, :parent_id_0=>6, :parent_parent_id=>3},
192
+ {:id=>10, :parent_id=>7, :parent_id_0=>7, :parent_parent_id=>4},
193
+ {:id=>11, :parent_id=>8, :parent_id_0=>8, :parent_parent_id=>5}
194
+ ]).all
195
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id FROM t LEFT OUTER JOIN t AS parent ON (parent.id = t.parent_id)"]
196
+
197
+ a.must_equal [@c.load(:id=>9, :parent_id=>6), @c.load(:id=>10, :parent_id=>7), @c.load(:id=>11, :parent_id=>8)]
198
+ a.map(&:parent).must_equal [@c.load(:id=>6, :parent_id=>3), @c.load(:id=>7, :parent_id=>4), @c.load(:id=>8, :parent_id=>5)]
199
+ @c.db.sqls.must_equal []
200
+
201
+ @c.dataset = @c.dataset.with_fetch([[{:id=>5, :parent_id=>2}, {:id=>4, :parent_id=>nil}, {:id=>3, :parent_id=>1}],
202
+ [{:id=>2, :parent_id=>nil}, {:id=>1, :parent_id=>nil}]])
203
+
204
+ a.map(&:parent).map(&:parent).must_equal [@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>nil), @c.load(:id=>5, :parent_id=>2)]
205
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (3, 4, 5))"]
206
+ a.map(&:parent).map(&:parent).map(&:parent).must_equal [@c.load(:id=>1, :parent_id=>nil), nil, @c.load(:id=>2, :parent_id=>nil)]
207
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (2, 1))"]
208
+ end
209
+
210
+ it "should allow eager loading of associated objects when using chained one_to_many associations" do
211
+ a = @c.eager_graph(:children=>:children).
212
+ with_fetch([
213
+ {:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1, :children_0_id=>6, :children_0_parent_id=>3},
214
+ {:id=>1, :parent_id=>nil, :children_id=>4, :children_parent_id=>1, :children_0_id=>7, :children_0_parent_id=>4},
215
+ {:id=>2, :parent_id=>nil, :children_id=>5, :children_parent_id=>2, :children_0_id=>8, :children_0_parent_id=>5}
216
+ ]).all
217
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, children.id AS children_id, children.parent_id AS children_parent_id, children_0.id AS children_0_id, children_0.parent_id AS children_0_parent_id FROM t LEFT OUTER JOIN t AS children ON (children.parent_id = t.id) LEFT OUTER JOIN t AS children_0 ON (children_0.parent_id = children.id)"]
218
+
219
+ a.must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
220
+ a.map(&:children).must_equal [
221
+ [@c.load(:id=>3, :parent_id=>1),
222
+ @c.load(:id=>4, :parent_id=>1)],
223
+ [@c.load(:id=>5, :parent_id=>2)]]
224
+
225
+ a.map(&:children).map{|v| v.map(&:children)}.must_equal [
226
+ [[@c.load(:id=>6, :parent_id=>3)],
227
+ [@c.load(:id=>7, :parent_id=>4)]],
228
+ [[@c.load(:id=>8, :parent_id=>5)]]]
229
+ @c.db.sqls.must_equal []
230
+
231
+ @c.dataset = @c.dataset.with_fetch([{:id=>9, :parent_id=>6}, {:id=>10, :parent_id=>7}, {:id=>11, :parent_id=>8}])
232
+ a.map(&:children).map{|v| v.map(&:children).map{|v1| v1.map(&:children)}}.must_equal [
233
+ [[[@c.load(:id=>9, :parent_id=>6)]],
234
+ [[@c.load(:id=>10, :parent_id=>7)]]],
235
+ [[[@c.load(:id=>11, :parent_id=>8)]]]]
236
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.parent_id IN (6, 7, 8))"]
237
+ end
238
+
239
+ it "should allow eager loading of associated objects when using chained many_to_one associations" do
240
+ a = @c.eager_graph(:parent=>:parent).
241
+ with_fetch([
242
+ {:id=>9, :parent_id=>6, :parent_id_0=>6, :parent_parent_id=>3, :parent_0_id=>3, :parent_0_parent_id=>1},
243
+ {:id=>10, :parent_id=>7, :parent_id_0=>7, :parent_parent_id=>4, :parent_0_id=>4, :parent_0_parent_id=>1},
244
+ {:id=>11, :parent_id=>8, :parent_id_0=>8, :parent_parent_id=>5, :parent_0_id=>5, :parent_0_parent_id=>2}
245
+ ]).all
246
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id, parent_0.id AS parent_0_id, parent_0.parent_id AS parent_0_parent_id FROM t LEFT OUTER JOIN t AS parent ON (parent.id = t.parent_id) LEFT OUTER JOIN t AS parent_0 ON (parent_0.id = parent.parent_id)"]
247
+
248
+ a.must_equal [@c.load(:id=>9, :parent_id=>6), @c.load(:id=>10, :parent_id=>7), @c.load(:id=>11, :parent_id=>8)]
249
+ a.map(&:parent).must_equal [@c.load(:id=>6, :parent_id=>3), @c.load(:id=>7, :parent_id=>4), @c.load(:id=>8, :parent_id=>5)]
250
+ a.map(&:parent).map(&:parent).must_equal [@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>1), @c.load(:id=>5, :parent_id=>2)]
251
+ @c.db.sqls.must_equal []
252
+
253
+ @c.dataset = @c.dataset.with_fetch([{:id=>2, :parent_id=>nil}, {:id=>1, :parent_id=>nil}])
254
+ a.map(&:parent).map(&:parent).map(&:parent).must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
255
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (1, 2))"]
256
+ end
257
+
258
+ it "should allow eager loading of associated objects when using chained many_to_one=>one_to_many associations" do
259
+ a = @c.eager_graph(:parent=>:children).
260
+ with_fetch([
261
+ {:id=>9, :parent_id=>6, :parent_id_0=>6, :parent_parent_id=>3, :children_id=>9, :children_parent_id=>6},
262
+ {:id=>10, :parent_id=>7, :parent_id_0=>7, :parent_parent_id=>4, :children_id=>10, :children_parent_id=>7},
263
+ {:id=>11, :parent_id=>8, :parent_id_0=>8, :parent_parent_id=>5, :children_id=>11, :children_parent_id=>8},
264
+ {:id=>9, :parent_id=>6, :parent_id_0=>6, :parent_parent_id=>3, :children_id=>12, :children_parent_id=>6},
265
+ {:id=>10, :parent_id=>7, :parent_id_0=>7, :parent_parent_id=>4, :children_id=>13, :children_parent_id=>7},
266
+ {:id=>11, :parent_id=>8, :parent_id_0=>8, :parent_parent_id=>5, :children_id=>14, :children_parent_id=>8}
267
+ ]).all
268
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM t LEFT OUTER JOIN t AS parent ON (parent.id = t.parent_id) LEFT OUTER JOIN t AS children ON (children.parent_id = parent.id)"]
269
+
270
+ a.must_equal [@c.load(:id=>9, :parent_id=>6), @c.load(:id=>10, :parent_id=>7), @c.load(:id=>11, :parent_id=>8)]
271
+ a.map(&:parent).must_equal [@c.load(:id=>6, :parent_id=>3), @c.load(:id=>7, :parent_id=>4), @c.load(:id=>8, :parent_id=>5)]
272
+ a.map(&:parent).map(&:children).must_equal [
273
+ [@c.load(:id=>9, :parent_id=>6), @c.load(:id=>12, :parent_id=>6)],
274
+ [@c.load(:id=>10, :parent_id=>7), @c.load(:id=>13, :parent_id=>7)],
275
+ [@c.load(:id=>11, :parent_id=>8), @c.load(:id=>14, :parent_id=>8)]]
276
+ @c.db.sqls.must_equal []
277
+
278
+ @c.dataset = @c.dataset.with_fetch([{:id=>19, :parent_id=>9}, {:id=>24, :parent_id=>14}])
279
+ a.map(&:parent).map(&:children).map{|v| v.map(&:children)}
280
+ #.must_equal [
281
+ # [[@c.load(:id=>19, :parent_id=>9)], []],
282
+ # [[], []],
283
+ # [[], @c.load(:id=>24, :parent_id=>14)]]
284
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.parent_id IN (9, 10, 11, 12, 13, 14))"]
285
+ end
286
+
287
+ it "should allow eager loading of associated objects when using chained one_to_many associations with partial data" do
288
+ a = @c.eager_graph(:children=>:children).
289
+ with_fetch([
290
+ {:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1, :children_0_id=>6, :children_0_parent_id=>3},
291
+ {:id=>1, :parent_id=>nil, :children_id=>4, :children_parent_id=>1, :children_0_id=>nil, :children_0_parent_id=>nil},
292
+ {:id=>2, :parent_id=>nil, :children_id=>nil, :children_parent_id=>nil, :children_0_id=>nil, :children_0_parent_id=>nil}
293
+ ]).all
294
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, children.id AS children_id, children.parent_id AS children_parent_id, children_0.id AS children_0_id, children_0.parent_id AS children_0_parent_id FROM t LEFT OUTER JOIN t AS children ON (children.parent_id = t.id) LEFT OUTER JOIN t AS children_0 ON (children_0.parent_id = children.id)"]
295
+
296
+ a.must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
297
+ a.map(&:children).must_equal [
298
+ [@c.load(:id=>3, :parent_id=>1),
299
+ @c.load(:id=>4, :parent_id=>1)],
300
+ []]
301
+
302
+ a.map(&:children).map{|v| v.map(&:children)}.must_equal [
303
+ [[@c.load(:id=>6, :parent_id=>3)],
304
+ []],
305
+ []]
306
+ @c.db.sqls.must_equal []
307
+
308
+ @c.dataset = @c.dataset.with_fetch([{:id=>9, :parent_id=>6}])
309
+ a.map(&:children).map{|v| v.map(&:children).map{|v1| v1.map(&:children)}}.must_equal [
310
+ [[[@c.load(:id=>9, :parent_id=>6)]],
311
+ []],
312
+ []]
313
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.parent_id IN (6))"]
314
+ end
315
+
316
+ it "should allow eager loading of associated objects when using chained many_to_one associations with partial data" do
317
+ a = @c.eager_graph(:parent=>:parent).
318
+ with_fetch([
319
+ {:id=>9, :parent_id=>6, :parent_id_0=>6, :parent_parent_id=>3, :parent_0_id=>3, :parent_0_parent_id=>1},
320
+ {:id=>10, :parent_id=>7, :parent_id_0=>7, :parent_parent_id=>nil, :parent_0_id=>nil, :parent_0_parent_id=>nil},
321
+ {:id=>11, :parent_id=>nil, :parent_id_0=>nil, :parent_parent_id=>nil, :parent_0_id=>nil, :parent_0_parent_id=>nil}
322
+ ]).all
323
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id, parent_0.id AS parent_0_id, parent_0.parent_id AS parent_0_parent_id FROM t LEFT OUTER JOIN t AS parent ON (parent.id = t.parent_id) LEFT OUTER JOIN t AS parent_0 ON (parent_0.id = parent.parent_id)"]
324
+
325
+ a.must_equal [@c.load(:id=>9, :parent_id=>6), @c.load(:id=>10, :parent_id=>7), @c.load(:id=>11, :parent_id=>nil)]
326
+ a.map(&:parent).must_equal [@c.load(:id=>6, :parent_id=>3), @c.load(:id=>7, :parent_id=>nil), nil]
327
+ a.map(&:parent).map{|v| v.parent if v}.must_equal [@c.load(:id=>3, :parent_id=>1), nil, nil]
328
+ @c.db.sqls.must_equal []
329
+
330
+ @c.dataset = @c.dataset.with_fetch([{:id=>1, :parent_id=>nil}])
331
+ a.map(&:parent).map{|v| v.parent.parent if v && v.parent}.must_equal [@c.load(:id=>1, :parent_id=>nil), nil, nil]
332
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (1))"]
333
+ end
334
+
335
+ it "should skip setup of eager loading when using eager_graph for association not using plugin" do
336
+ c = Class.new(Sequel::Model)
337
+ c.class_eval do
338
+ set_dataset DB[:t]
339
+ columns :id, :t_id
340
+ end
341
+ @c.many_to_one :f, :class=>c, :key=>:parent_id
342
+ @c.one_to_many :fs, :class=>c
343
+ c.many_to_one :t, :class=>@c
344
+ c.one_to_many :ts, :class=>@c, :key=>:parent_id
345
+
346
+ a = @c.eager_graph(:f, :parent, :fs=>:t).
347
+ with_fetch([
348
+ {:id=>5, :parent_id=>4, :f_id=>4, :t_id=>20, :parent_id_0=>4, :parent_parent_id=>3, :fs_id=>5, :fs_t_id=>30, :t_0_id=>30, :t_0_parent_id=>40},
349
+ {:id=>15, :parent_id=>14, :f_id=>14, :t_id=>30, :parent_id_0=>14, :parent_parent_id=>13, :fs_id=>15, :fs_t_id=>40, :t_0_id=>40, :t_0_parent_id=>50},
350
+ ]).
351
+ all
352
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, f.id AS f_id, f.t_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id, fs.id AS fs_id, fs.t_id AS fs_t_id, t_0.id AS t_0_id, t_0.parent_id AS t_0_parent_id FROM t LEFT OUTER JOIN t AS f ON (f.id = t.parent_id) LEFT OUTER JOIN t AS parent ON (parent.id = t.parent_id) LEFT OUTER JOIN t AS fs ON (fs._id = t.id) LEFT OUTER JOIN t AS t_0 ON (t_0.id = fs.t_id)"]
353
+
354
+ a.must_equal [@c.load(:id=>5, :parent_id=>4), @c.load(:id=>15, :parent_id=>14)]
355
+ a.map(&:f).must_equal [c.load(:id=>4, :t_id=>20), c.load(:id=>14, :t_id=>30)]
356
+ a.map(&:parent).must_equal [@c.load(:id=>4, :parent_id=>3), @c.load(:id=>14, :parent_id=>13)]
357
+ a.map(&:fs).must_equal [[c.load(:id=>5, :t_id=>30)], [c.load(:id=>15, :t_id=>40)]]
358
+ a.map(&:fs).map{|v| v.map(&:t)}.must_equal [[@c.load(:id=>30, :parent_id=>40)], [@c.load(:id=>40, :parent_id=>50)]]
359
+ @c.db.sqls.must_equal []
360
+
361
+ @c.dataset = @c.dataset.with_fetch([[{:id=>3, :parent_id=>1}, {:id=>13, :parent_id=>1}],
362
+ [{:id=>1, :parent_id=>nil}],
363
+ [{:id=>20, :parent_id=>nil}],
364
+ [{:id=>30, :parent_id=>nil}],
365
+ [{:id=>50, :parent_id=>nil}, {:id=>40, :parent_id=>nil}]
366
+ ])
367
+ a.map(&:parent).map(&:parent).must_equal [@c.load(:id=>3, :parent_id=>1), @c.load(:id=>13, :parent_id=>1)]
368
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (3, 13))"]
369
+ a.map(&:parent).map(&:parent).map(&:parent).must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>1, :parent_id=>nil)]
370
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (1))"]
371
+
372
+ a.map(&:f).map(&:t).must_equal [@c.load(:id=>20, :parent_id=>nil), @c.load(:id=>30, :parent_id=>nil)]
373
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE id = 20", "SELECT * FROM t WHERE id = 30"]
374
+
375
+ a.map(&:fs).map{|v| v.map(&:t).map(&:parent)}.must_equal [[@c.load(:id=>40, :parent_id=>nil)], [@c.load(:id=>50, :parent_id=>nil)]]
376
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (40, 50))"]
377
+ end
378
+
379
+ it "should skip frozen objects when eager loading for model objects" do
380
+ a = @c.eager_graph(:parent).
381
+ with_fetch([
382
+ {:id=>9, :parent_id=>6, :parent_id_0=>6, :parent_parent_id=>3},
383
+ {:id=>10, :parent_id=>7, :parent_id_0=>7, :parent_parent_id=>4},
384
+ {:id=>11, :parent_id=>8, :parent_id_0=>8, :parent_parent_id=>5}
385
+ ]).all
386
+ @c.db.sqls.must_equal ["SELECT t.id, t.parent_id, parent.id AS parent_id_0, parent.parent_id AS parent_parent_id FROM t LEFT OUTER JOIN t AS parent ON (parent.id = t.parent_id)"]
387
+
388
+ a.must_equal [@c.load(:id=>9, :parent_id=>6), @c.load(:id=>10, :parent_id=>7), @c.load(:id=>11, :parent_id=>8)]
389
+ a.map(&:parent).must_equal [@c.load(:id=>6, :parent_id=>3), @c.load(:id=>7, :parent_id=>4), @c.load(:id=>8, :parent_id=>5)]
390
+ @c.db.sqls.must_equal []
391
+
392
+ @c.dataset = @c.dataset.with_fetch([[{:id=>3, :parent_id=>1}, {:id=>5, :parent_id=>2}], [{:id=>4, :parent_id=>nil}]])
393
+ parents = a.map(&:parent)
394
+ parents[1].freeze
395
+ parents[0].parent.must_equal @c.load(:id=>3, :parent_id=>1)
396
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE (t.id IN (3, 5))"]
397
+ parents[1].parent.must_equal @c.load(:id=>4, :parent_id=>nil)
398
+ @c.db.sqls.must_equal ["SELECT * FROM t WHERE id = 4"]
399
+ parents[2].parent.must_equal @c.load(:id=>5, :parent_id=>2)
400
+ @c.db.sqls.must_equal []
401
+ end
402
+ end