sequel 5.11.0 → 5.12.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.
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