epugh-sequel 0.0.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 (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,3513 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ context "Dataset" do
4
+ setup do
5
+ @dataset = Sequel::Dataset.new("db")
6
+ end
7
+
8
+ specify "should accept database and opts in initialize" do
9
+ db = "db"
10
+ opts = {:from => :test}
11
+ d = Sequel::Dataset.new(db, opts)
12
+ d.db.should be(db)
13
+ d.opts.should be(opts)
14
+
15
+ d = Sequel::Dataset.new(db)
16
+ d.db.should be(db)
17
+ d.opts.should be_a_kind_of(Hash)
18
+ d.opts.should == {}
19
+ end
20
+
21
+ specify "should provide clone for chainability" do
22
+ d1 = @dataset.clone(:from => [:test])
23
+ d1.class.should == @dataset.class
24
+ d1.should_not == @dataset
25
+ d1.db.should be(@dataset.db)
26
+ d1.opts[:from].should == [:test]
27
+ @dataset.opts[:from].should be_nil
28
+
29
+ d2 = d1.clone(:order => [:name])
30
+ d2.class.should == @dataset.class
31
+ d2.should_not == d1
32
+ d2.should_not == @dataset
33
+ d2.db.should be(@dataset.db)
34
+ d2.opts[:from].should == [:test]
35
+ d2.opts[:order].should == [:name]
36
+ d1.opts[:order].should be_nil
37
+ end
38
+
39
+ specify "should include Enumerable" do
40
+ Sequel::Dataset.included_modules.should include(Enumerable)
41
+ end
42
+
43
+ specify "should get quote_identifiers default from database" do
44
+ db = Sequel::Database.new(:quote_identifiers=>true)
45
+ db[:a].quote_identifiers?.should == true
46
+ db = Sequel::Database.new(:quote_identifiers=>false)
47
+ db[:a].quote_identifiers?.should == false
48
+ end
49
+
50
+ specify "should get identifier_input_method default from database" do
51
+ db = Sequel::Database.new(:identifier_input_method=>:upcase)
52
+ db[:a].identifier_input_method.should == :upcase
53
+ db = Sequel::Database.new(:identifier_input_method=>:downcase)
54
+ db[:a].identifier_input_method.should == :downcase
55
+ end
56
+
57
+ specify "should get identifier_output_method default from database" do
58
+ db = Sequel::Database.new(:identifier_output_method=>:upcase)
59
+ db[:a].identifier_output_method.should == :upcase
60
+ db = Sequel::Database.new(:identifier_output_method=>:downcase)
61
+ db[:a].identifier_output_method.should == :downcase
62
+ end
63
+ end
64
+
65
+ context "Dataset" do
66
+ setup do
67
+ @dataset = Sequel::Dataset.new("db")
68
+ end
69
+
70
+ specify "should have quote_identifiers= method which changes literalization of identifiers" do
71
+ @dataset.quote_identifiers = true
72
+ @dataset.literal(:a).should == '"a"'
73
+ @dataset.quote_identifiers = false
74
+ @dataset.literal(:a).should == 'a'
75
+ end
76
+
77
+ deprec_specify "should have upcase_identifiers= method which changes literalization of identifiers" do
78
+ @dataset.upcase_identifiers = true
79
+ @dataset.literal(:a).should == 'A'
80
+ @dataset.upcase_identifiers = false
81
+ @dataset.literal(:a).should == 'a'
82
+ end
83
+
84
+ deprec_specify "should have upcase_identifiers? method which returns whether identifiers are currently upcased" do
85
+ @dataset.upcase_identifiers = true
86
+ @dataset.upcase_identifiers?.should == true
87
+ @dataset.upcase_identifiers = false
88
+ @dataset.upcase_identifiers?.should == false
89
+ end
90
+
91
+ specify "should have identifier_input_method= method which changes literalization of identifiers" do
92
+ @dataset.identifier_input_method = :upcase
93
+ @dataset.literal(:a).should == 'A'
94
+ @dataset.identifier_input_method = :downcase
95
+ @dataset.literal(:A).should == 'a'
96
+ @dataset.identifier_input_method = :reverse
97
+ @dataset.literal(:at_b).should == 'b_ta'
98
+ end
99
+
100
+ specify "should have identifier_output_method= method which changes identifiers returned from the database" do
101
+ @dataset.send(:output_identifier, "at_b_C").should == :at_b_C
102
+ @dataset.identifier_output_method = :upcase
103
+ @dataset.send(:output_identifier, "at_b_C").should == :AT_B_C
104
+ @dataset.identifier_output_method = :downcase
105
+ @dataset.send(:output_identifier, "at_b_C").should == :at_b_c
106
+ @dataset.identifier_output_method = :reverse
107
+ @dataset.send(:output_identifier, "at_b_C").should == :C_b_ta
108
+ end
109
+ end
110
+
111
+ context "Dataset#clone" do
112
+ setup do
113
+ @dataset = Sequel::Dataset.new(nil).from(:items)
114
+ end
115
+
116
+ specify "should create an exact copy of the dataset" do
117
+ @dataset.row_proc = Proc.new{|r| r}
118
+ @clone = @dataset.clone
119
+
120
+ @clone.should_not === @dataset
121
+ @clone.class.should == @dataset.class
122
+ @clone.opts.should == @dataset.opts
123
+ @clone.row_proc.should == @dataset.row_proc
124
+ end
125
+
126
+ specify "should deep-copy the dataset opts" do
127
+ @clone = @dataset.clone
128
+
129
+ @clone.opts.should_not equal(@dataset.opts)
130
+ @dataset.filter!(:a => 'b')
131
+ @clone.opts[:filter].should be_nil
132
+ end
133
+
134
+ specify "should return a clone self" do
135
+ clone = @dataset.clone({})
136
+ clone.class.should == @dataset.class
137
+ clone.db.should == @dataset.db
138
+ clone.opts.should == @dataset.opts
139
+ end
140
+
141
+ specify "should merge the specified options" do
142
+ clone = @dataset.clone(1 => 2)
143
+ clone.opts.should == {1 => 2, :from => [:items]}
144
+ end
145
+
146
+ specify "should overwrite existing options" do
147
+ clone = @dataset.clone(:from => [:other])
148
+ clone.opts.should == {:from => [:other]}
149
+ end
150
+
151
+ specify "should create a clone with a deep copy of options" do
152
+ clone = @dataset.clone(:from => [:other])
153
+ @dataset.opts[:from].should == [:items]
154
+ clone.opts[:from].should == [:other]
155
+ end
156
+
157
+ specify "should return an object with the same modules included" do
158
+ m = Module.new do
159
+ def __xyz__; "xyz"; end
160
+ end
161
+ @dataset.extend(m)
162
+ @dataset.clone({}).should respond_to(:__xyz__)
163
+ end
164
+ end
165
+
166
+ context "A simple dataset" do
167
+ setup do
168
+ @dataset = Sequel::Dataset.new(nil).from(:test)
169
+ end
170
+
171
+ specify "should format a select statement" do
172
+ @dataset.select_sql.should == 'SELECT * FROM test'
173
+ end
174
+
175
+ specify "should format a delete statement" do
176
+ @dataset.delete_sql.should == 'DELETE FROM test'
177
+ end
178
+
179
+ specify "should format an insert statement with default values" do
180
+ @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
181
+ end
182
+
183
+ specify "should format an insert statement with hash" do
184
+ @dataset.insert_sql(:name => 'wxyz', :price => 342).
185
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
186
+
187
+ @dataset.insert_sql({}).should == "INSERT INTO test DEFAULT VALUES"
188
+ end
189
+
190
+ specify "should format an insert statement with string keys" do
191
+ @dataset.insert_sql('name' => 'wxyz', 'price' => 342).
192
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
193
+ end
194
+
195
+ specify "should format an insert statement with an object that respond_to? :values" do
196
+ dbb = Sequel::Database.new
197
+
198
+ v = Object.new
199
+ def v.values; {:a => 1}; end
200
+
201
+ @dataset.insert_sql(v).should == "INSERT INTO test (a) VALUES (1)"
202
+
203
+ def v.values; {}; end
204
+ @dataset.insert_sql(v).should == "INSERT INTO test DEFAULT VALUES"
205
+ end
206
+
207
+ specify "should format an insert statement with an arbitrary value" do
208
+ @dataset.insert_sql(123).should == "INSERT INTO test VALUES (123)"
209
+ end
210
+
211
+ specify "should format an insert statement with sub-query" do
212
+ @sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
213
+ @dataset.insert_sql(@sub).should == \
214
+ "INSERT INTO test (SELECT * FROM something WHERE (x = 2))"
215
+ end
216
+
217
+ specify "should format an insert statement with array" do
218
+ @dataset.insert_sql('a', 2, 6.5).should ==
219
+ "INSERT INTO test VALUES ('a', 2, 6.5)"
220
+ end
221
+
222
+ specify "should format an update statement" do
223
+ @dataset.update_sql(:name => 'abc').should ==
224
+ "UPDATE test SET name = 'abc'"
225
+ end
226
+
227
+ specify "should be able to return rows for arbitrary SQL" do
228
+ @dataset.clone(:sql => 'xxx yyy zzz').select_sql.should ==
229
+ "xxx yyy zzz"
230
+ end
231
+
232
+ specify "should use the :sql option for all sql methods" do
233
+ sql = "X"
234
+ ds = Sequel::Dataset.new(nil, :sql=>sql)
235
+ ds.sql.should == sql
236
+ ds.select_sql.should == sql
237
+ ds.insert_sql.should == sql
238
+ ds.delete_sql.should == sql
239
+ ds.update_sql.should == sql
240
+ end
241
+ end
242
+
243
+ context "A dataset with multiple tables in its FROM clause" do
244
+ setup do
245
+ @dataset = Sequel::Dataset.new(nil).from(:t1, :t2)
246
+ end
247
+
248
+ specify "should raise on #update_sql" do
249
+ proc {@dataset.update_sql(:a=>1)}.should raise_error(Sequel::Error::InvalidOperation)
250
+ end
251
+
252
+ specify "should raise on #delete_sql" do
253
+ proc {@dataset.delete_sql}.should raise_error(Sequel::Error::InvalidOperation)
254
+ end
255
+
256
+ specify "should generate a select query FROM all specified tables" do
257
+ @dataset.select_sql.should == "SELECT * FROM t1, t2"
258
+ end
259
+ end
260
+
261
+ context "Dataset#exists" do
262
+ setup do
263
+ @ds1 = Sequel::Dataset.new(nil).from(:test)
264
+ @ds2 = @ds1.filter(:price.sql_number < 100)
265
+ @ds3 = @ds1.filter(:price.sql_number > 50)
266
+ end
267
+
268
+ specify "should work in filters" do
269
+ @ds1.filter(@ds2.exists).sql.should ==
270
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
271
+ @ds1.filter(@ds2.exists & @ds3.exists).sql.should ==
272
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)) AND EXISTS (SELECT * FROM test WHERE (price > 50)))'
273
+ end
274
+
275
+ specify "should work in select" do
276
+ @ds1.select(@ds2.exists.as(:a), @ds3.exists.as(:b)).sql.should ==
277
+ 'SELECT EXISTS (SELECT * FROM test WHERE (price < 100)) AS a, EXISTS (SELECT * FROM test WHERE (price > 50)) AS b FROM test'
278
+ end
279
+ end
280
+
281
+ context "Dataset#where" do
282
+ setup do
283
+ @dataset = Sequel::Dataset.new(nil).from(:test)
284
+ @d1 = @dataset.where(:region => 'Asia')
285
+ @d2 = @dataset.where('region = ?', 'Asia')
286
+ @d3 = @dataset.where("a = 1")
287
+ end
288
+
289
+ specify "should work with hashes" do
290
+ @dataset.where(:name => 'xyz', :price => 342).select_sql.
291
+ should match(/WHERE \(\(name = 'xyz'\) AND \(price = 342\)\)|WHERE \(\(price = 342\) AND \(name = 'xyz'\)\)/)
292
+ end
293
+
294
+ specify "should work with arrays (ala ActiveRecord)" do
295
+ @dataset.where('price < ? AND id in ?', 100, [1, 2, 3]).select_sql.should ==
296
+ "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
297
+ end
298
+
299
+ specify "should work with strings (custom SQL expressions)" do
300
+ @dataset.where('(a = 1 AND b = 2)').select_sql.should ==
301
+ "SELECT * FROM test WHERE ((a = 1 AND b = 2))"
302
+ end
303
+
304
+ specify "should affect select, delete and update statements" do
305
+ @d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
306
+ @d1.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
307
+ @d1.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
308
+
309
+ @d2.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
310
+ @d2.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
311
+ @d2.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
312
+
313
+ @d3.select_sql.should == "SELECT * FROM test WHERE (a = 1)"
314
+ @d3.delete_sql.should == "DELETE FROM test WHERE (a = 1)"
315
+ @d3.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (a = 1)"
316
+
317
+ end
318
+
319
+ specify "should be composable using AND operator (for scoping)" do
320
+ # hashes are merged, no problem
321
+ @d1.where(:size => 'big').select_sql.should ==
322
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (size = 'big'))"
323
+
324
+ # hash and string
325
+ @d1.where('population > 1000').select_sql.should ==
326
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (population > 1000))"
327
+ @d1.where('(a > 1) OR (b < 2)').select_sql.should ==
328
+ "SELECT * FROM test WHERE ((region = 'Asia') AND ((a > 1) OR (b < 2)))"
329
+
330
+ # hash and array
331
+ @d1.where('GDP > ?', 1000).select_sql.should ==
332
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
333
+
334
+ # array and array
335
+ @d2.where('GDP > ?', 1000).select_sql.should ==
336
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
337
+
338
+ # array and hash
339
+ @d2.where(:name => ['Japan', 'China']).select_sql.should ==
340
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (name IN ('Japan', 'China')))"
341
+
342
+ # array and string
343
+ @d2.where('GDP > ?').select_sql.should ==
344
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > ?))"
345
+
346
+ # string and string
347
+ @d3.where('b = 2').select_sql.should ==
348
+ "SELECT * FROM test WHERE ((a = 1) AND (b = 2))"
349
+
350
+ # string and hash
351
+ @d3.where(:c => 3).select_sql.should ==
352
+ "SELECT * FROM test WHERE ((a = 1) AND (c = 3))"
353
+
354
+ # string and array
355
+ @d3.where('d = ?', 4).select_sql.should ==
356
+ "SELECT * FROM test WHERE ((a = 1) AND (d = 4))"
357
+ end
358
+
359
+ specify "should be composable using AND operator (for scoping) with block" do
360
+ @d3.where{:e.sql_number < 5}.select_sql.should ==
361
+ "SELECT * FROM test WHERE ((a = 1) AND (e < 5))"
362
+ end
363
+
364
+ specify "should raise if the dataset is grouped" do
365
+ proc {@dataset.group(:t).where(:a => 1)}.should_not raise_error
366
+ @dataset.group(:t).where(:a => 1).sql.should ==
367
+ "SELECT * FROM test WHERE (a = 1) GROUP BY t"
368
+ end
369
+
370
+ specify "should accept ranges" do
371
+ @dataset.filter(:id => 4..7).sql.should ==
372
+ 'SELECT * FROM test WHERE ((id >= 4) AND (id <= 7))'
373
+ @dataset.filter(:id => 4...7).sql.should ==
374
+ 'SELECT * FROM test WHERE ((id >= 4) AND (id < 7))'
375
+
376
+ @dataset.filter(:table__id => 4..7).sql.should ==
377
+ 'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id <= 7))'
378
+ @dataset.filter(:table__id => 4...7).sql.should ==
379
+ 'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id < 7))'
380
+ end
381
+
382
+ specify "should accept nil" do
383
+ @dataset.filter(:owner_id => nil).sql.should ==
384
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
385
+ end
386
+
387
+ specify "should accept a subquery" do
388
+ @dataset.filter('gdp > ?', @d1.select(:avg.sql_function(:gdp))).sql.should ==
389
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
390
+
391
+ @dataset.filter(:id => @d1.select(:id)).sql.should ==
392
+ "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
393
+ end
394
+
395
+ specify "should accept a subquery for an EXISTS clause" do
396
+ a = @dataset.filter(:price.sql_number < 100)
397
+ @dataset.filter(a.exists).sql.should ==
398
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
399
+ end
400
+
401
+ specify "should accept proc expressions" do
402
+ d = @d1.select(:avg.sql_function(:gdp))
403
+ @dataset.filter {:gdp.sql_number > d}.sql.should ==
404
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
405
+
406
+ @dataset.filter {:a.sql_number < 1}.sql.should ==
407
+ 'SELECT * FROM test WHERE (a < 1)'
408
+
409
+ @dataset.filter {(:a.sql_number >= 1) & (:b.sql_number <= 2)}.sql.should ==
410
+ 'SELECT * FROM test WHERE ((a >= 1) AND (b <= 2))'
411
+
412
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
413
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
414
+
415
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
416
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
417
+
418
+ @dataset.filter {:c.like 'ABC%', '%XYZ'}.sql.should ==
419
+ "SELECT * FROM test WHERE ((c LIKE 'ABC%') OR (c LIKE '%XYZ'))"
420
+ end
421
+
422
+ specify "should work for grouped datasets" do
423
+ @dataset.group(:a).filter(:b => 1).sql.should ==
424
+ 'SELECT * FROM test WHERE (b = 1) GROUP BY a'
425
+ end
426
+
427
+ specify "should accept true and false as arguments" do
428
+ @dataset.filter(true).sql.should ==
429
+ "SELECT * FROM test WHERE 't'"
430
+ @dataset.filter(false).sql.should ==
431
+ "SELECT * FROM test WHERE 'f'"
432
+ end
433
+
434
+ specify "should allow the use of blocks and arguments simultaneously" do
435
+ @dataset.filter(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
436
+ 'SELECT * FROM test WHERE ((zz < 3) AND (yy > 3))'
437
+ end
438
+
439
+ specify "should yield a VirtualRow to the block" do
440
+ x = nil
441
+ @dataset.filter{|r| x = r; false}
442
+ x.should be_a_kind_of(Sequel::SQL::VirtualRow)
443
+ @dataset.filter{|r| ((r.name < 'b') & {r.table__id => 1}) | r.is_active(r.blah, r.xx, r.x__y_z)}.sql.should ==
444
+ "SELECT * FROM test WHERE (((name < 'b') AND (table.id = 1)) OR is_active(blah, xx, x.y_z))"
445
+ end
446
+
447
+ specify "should instance_eval the block in the context of a VirtualRow if the block doesn't request an argument" do
448
+ x = nil
449
+ @dataset.filter{x = self; false}
450
+ x.should be_a_kind_of(Sequel::SQL::VirtualRow)
451
+ @dataset.filter{((name < 'b') & {table__id => 1}) | is_active(blah, xx, x__y_z)}.sql.should ==
452
+ "SELECT * FROM test WHERE (((name < 'b') AND (table.id = 1)) OR is_active(blah, xx, x.y_z))"
453
+ end
454
+
455
+ specify "should raise an error if an invalid argument is used" do
456
+ proc{@dataset.filter(1)}.should raise_error(Sequel::Error)
457
+ end
458
+
459
+ specify "should raise an error if a NumericExpression or StringExpression is used" do
460
+ proc{@dataset.filter(:x + 1)}.should raise_error(Sequel::Error)
461
+ proc{@dataset.filter(:x.sql_string)}.should raise_error(Sequel::Error)
462
+ end
463
+ end
464
+
465
+ context "Dataset#or" do
466
+ setup do
467
+ @dataset = Sequel::Dataset.new(nil).from(:test)
468
+ @d1 = @dataset.where(:x => 1)
469
+ end
470
+
471
+ specify "should raise if no filter exists" do
472
+ proc {@dataset.or(:a => 1)}.should raise_error(Sequel::Error)
473
+ end
474
+
475
+ specify "should add an alternative expression to the where clause" do
476
+ @d1.or(:y => 2).sql.should ==
477
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
478
+ end
479
+
480
+ specify "should accept all forms of filters" do
481
+ @d1.or('y > ?', 2).sql.should ==
482
+ 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
483
+ @d1.or(:yy.sql_number > 3).sql.should ==
484
+ 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
485
+ end
486
+
487
+ specify "should accept blocks passed to filter" do
488
+ @d1.or{:yy.sql_number > 3}.sql.should ==
489
+ 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
490
+ end
491
+
492
+ specify "should correctly add parens to give predictable results" do
493
+ @d1.filter(:y => 2).or(:z => 3).sql.should ==
494
+ 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
495
+
496
+ @d1.or(:y => 2).filter(:z => 3).sql.should ==
497
+ 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
498
+ end
499
+
500
+ specify "should allow the use of blocks and arguments simultaneously" do
501
+ @d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
502
+ 'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
503
+ end
504
+ end
505
+
506
+ context "Dataset#and" do
507
+ setup do
508
+ @dataset = Sequel::Dataset.new(nil).from(:test)
509
+ @d1 = @dataset.where(:x => 1)
510
+ end
511
+
512
+ specify "should raise if no filter exists" do
513
+ proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
514
+ proc {@dataset.where(:a => 1).group(:t).and(:b => 2)}.should_not raise_error(Sequel::Error)
515
+ @dataset.where(:a => 1).group(:t).and(:b => 2).sql ==
516
+ "SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
517
+ end
518
+
519
+ specify "should add an alternative expression to the where clause" do
520
+ @d1.and(:y => 2).sql.should ==
521
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
522
+ end
523
+
524
+ specify "should accept all forms of filters" do
525
+ # probably not exhaustive, but good enough
526
+ @d1.and('y > ?', 2).sql.should ==
527
+ 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
528
+ @d1.and(:yy.sql_number > 3).sql.should ==
529
+ 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
530
+ end
531
+
532
+ specify "should accept blocks passed to filter" do
533
+ @d1.and {:yy.sql_number > 3}.sql.should ==
534
+ 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
535
+ end
536
+
537
+ specify "should correctly add parens to give predictable results" do
538
+ @d1.or(:y => 2).and(:z => 3).sql.should ==
539
+ 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
540
+
541
+ @d1.and(:y => 2).or(:z => 3).sql.should ==
542
+ 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
543
+ end
544
+ end
545
+
546
+ context "Dataset#exclude" do
547
+ setup do
548
+ @dataset = Sequel::Dataset.new(nil).from(:test)
549
+ end
550
+
551
+ specify "should correctly include the NOT operator when one condition is given" do
552
+ @dataset.exclude(:region=>'Asia').select_sql.should ==
553
+ "SELECT * FROM test WHERE (region != 'Asia')"
554
+ end
555
+
556
+ specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
557
+ @dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
558
+ should match(Regexp.union(/WHERE \(\(region != 'Asia'\) AND \(name != 'Japan'\)\)/,
559
+ /WHERE \(\(name != 'Japan'\) AND \(region != 'Asia'\)\)/))
560
+ end
561
+
562
+ specify "should parenthesize a single string condition correctly" do
563
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
564
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
565
+ end
566
+
567
+ specify "should parenthesize an array condition correctly" do
568
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
569
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
570
+ end
571
+
572
+ specify "should correctly parenthesize when it is used twice" do
573
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
574
+ "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
575
+ end
576
+
577
+ specify "should support proc expressions" do
578
+ @dataset.exclude{:id.sql_number < 6}.sql.should ==
579
+ 'SELECT * FROM test WHERE (id >= 6)'
580
+ end
581
+
582
+ specify "should allow the use of blocks and arguments simultaneously" do
583
+ @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should ==
584
+ 'SELECT * FROM test WHERE (((id < 7) OR (id > 11)) OR (id >= 6))'
585
+ end
586
+ end
587
+
588
+ context "Dataset#invert" do
589
+ setup do
590
+ @d = Sequel::Dataset.new(nil).from(:test)
591
+ end
592
+
593
+ specify "should raise error if the dataset is not filtered" do
594
+ proc{@d.invert}.should raise_error(Sequel::Error)
595
+ end
596
+
597
+ specify "should invert current filter if dataset is filtered" do
598
+ @d.filter(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x'
599
+ end
600
+
601
+ specify "should invert both having and where if both are preset" do
602
+ @d.filter(:x).group(:x).having(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x GROUP BY x HAVING NOT x'
603
+ end
604
+ end
605
+
606
+ context "Dataset#having" do
607
+ setup do
608
+ @dataset = Sequel::Dataset.new(nil).from(:test)
609
+ @grouped = @dataset.group(:region).select(:region, :sum.sql_function(:population), :avg.sql_function(:gdp))
610
+ @d1 = @grouped.having('sum(population) > 10')
611
+ @d2 = @grouped.having(:region => 'Asia')
612
+ @columns = "region, sum(population), avg(gdp)"
613
+ end
614
+
615
+ specify "should raise if the dataset is not grouped" do
616
+ proc {@dataset.having('avg(gdp) > 10')}.should raise_error(Sequel::Error::InvalidOperation)
617
+ end
618
+
619
+ specify "should affect select statements" do
620
+ @d1.select_sql.should ==
621
+ "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
622
+ end
623
+
624
+ specify "should support proc expressions" do
625
+ @grouped.having {:sum.sql_function(:population) > 10}.sql.should ==
626
+ "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
627
+ end
628
+
629
+ specify "should work with and on the having clause" do
630
+ @grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should ==
631
+ "SELECT #{@columns} FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
632
+ end
633
+ end
634
+
635
+ context "a grouped dataset" do
636
+ setup do
637
+ @dataset = Sequel::Dataset.new(nil).from(:test).group(:type_id)
638
+ end
639
+
640
+ specify "should raise when trying to generate an update statement" do
641
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
642
+ end
643
+
644
+ specify "should raise when trying to generate a delete statement" do
645
+ proc {@dataset.delete_sql}.should raise_error
646
+ end
647
+
648
+ specify "should specify the grouping in generated select statement" do
649
+ @dataset.select_sql.should ==
650
+ "SELECT * FROM test GROUP BY type_id"
651
+ end
652
+
653
+ specify "should format the right statement for counting (as a subquery)" do
654
+ db = MockDatabase.new
655
+ db[:test].select(:name).group(:name).count
656
+ db.sqls.should == ["SELECT COUNT(*) FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
657
+ end
658
+ end
659
+
660
+ context "Dataset#group_by" do
661
+ setup do
662
+ @dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
663
+ end
664
+
665
+ specify "should raise when trying to generate an update statement" do
666
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
667
+ end
668
+
669
+ specify "should raise when trying to generate a delete statement" do
670
+ proc {@dataset.delete_sql}.should raise_error
671
+ end
672
+
673
+ specify "should specify the grouping in generated select statement" do
674
+ @dataset.select_sql.should ==
675
+ "SELECT * FROM test GROUP BY type_id"
676
+ @dataset.group_by(:a, :b).select_sql.should ==
677
+ "SELECT * FROM test GROUP BY a, b"
678
+ end
679
+
680
+ specify "should specify the grouping in generated select statement" do
681
+ @dataset.group_by(:type_id=>nil).select_sql.should ==
682
+ "SELECT * FROM test GROUP BY (type_id IS NULL)"
683
+ end
684
+
685
+ specify "should be aliased as #group" do
686
+ @dataset.group(:type_id=>nil).select_sql.should ==
687
+ "SELECT * FROM test GROUP BY (type_id IS NULL)"
688
+ end
689
+ end
690
+
691
+ context "Dataset#as" do
692
+ specify "should set up an alias" do
693
+ dataset = Sequel::Dataset.new(nil).from(:test)
694
+ dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == \
695
+ 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
696
+ end
697
+ end
698
+
699
+ context "Dataset#literal" do
700
+ setup do
701
+ @dataset = Sequel::Dataset.new(nil).from(:test)
702
+ end
703
+
704
+ specify "should escape strings properly" do
705
+ @dataset.literal('abc').should == "'abc'"
706
+ @dataset.literal('a"x"bc').should == "'a\"x\"bc'"
707
+ @dataset.literal("a'bc").should == "'a''bc'"
708
+ @dataset.literal("a''bc").should == "'a''''bc'"
709
+ @dataset.literal("a\\bc").should == "'a\\\\bc'"
710
+ @dataset.literal("a\\\\bc").should == "'a\\\\\\\\bc'"
711
+ @dataset.literal("a\\'bc").should == "'a\\\\''bc'"
712
+ end
713
+
714
+ specify "should escape blobs as strings by default" do
715
+ @dataset.literal('abc'.to_sequel_blob).should == "'abc'"
716
+ end
717
+
718
+ specify "should literalize numbers properly" do
719
+ @dataset.literal(1).should == "1"
720
+ @dataset.literal(1.5).should == "1.5"
721
+ end
722
+
723
+ specify "should literalize nil as NULL" do
724
+ @dataset.literal(nil).should == "NULL"
725
+ end
726
+
727
+ specify "should literalize an array properly" do
728
+ @dataset.literal([]).should == "(NULL)"
729
+ @dataset.literal([1, 'abc', 3]).should == "(1, 'abc', 3)"
730
+ @dataset.literal([1, "a'b''c", 3]).should == "(1, 'a''b''''c', 3)"
731
+ end
732
+
733
+ specify "should literalize symbols as column references" do
734
+ @dataset.literal(:name).should == "name"
735
+ @dataset.literal(:items__name).should == "items.name"
736
+ end
737
+
738
+ specify "should raise an error for unsupported types" do
739
+ proc {@dataset.literal({})}.should raise_error
740
+ end
741
+
742
+ specify "should literalize datasets as subqueries" do
743
+ d = @dataset.from(:test)
744
+ d.literal(d).should == "(#{d.sql})"
745
+ end
746
+
747
+ specify "should literalize Time properly" do
748
+ t = Time.now
749
+ s = t.strftime("'%Y-%m-%dT%H:%M:%S%z'").gsub(/(\d\d')\z/, ':\1')
750
+ @dataset.literal(t).should == s
751
+ end
752
+
753
+ specify "should literalize DateTime properly" do
754
+ t = DateTime.now
755
+ s = t.strftime("'%Y-%m-%dT%H:%M:%S%z'").gsub(/(\d\d')\z/, ':\1')
756
+ @dataset.literal(t).should == s
757
+ end
758
+
759
+ specify "should literalize Date properly" do
760
+ d = Date.today
761
+ s = d.strftime("'%Y-%m-%d'")
762
+ @dataset.literal(d).should == s
763
+ end
764
+
765
+ specify "should not modify literal strings" do
766
+ @dataset.literal('col1 + 2'.lit).should == 'col1 + 2'
767
+
768
+ @dataset.update_sql(:a => 'a + 2'.lit).should ==
769
+ 'UPDATE test SET a = a + 2'
770
+ end
771
+
772
+ specify "should literalize BigDecimal instances correctly" do
773
+ @dataset.literal(BigDecimal.new("80")).should == "80.0"
774
+ @dataset.literal(BigDecimal.new("NaN")).should == "'NaN'"
775
+ @dataset.literal(BigDecimal.new("Infinity")).should == "'Infinity'"
776
+ @dataset.literal(BigDecimal.new("-Infinity")).should == "'-Infinity'"
777
+ end
778
+
779
+ specify "should raise an Error if the object can't be literalized" do
780
+ proc{@dataset.literal(Object.new)}.should raise_error(Sequel::Error)
781
+ end
782
+ end
783
+
784
+ context "Dataset#from" do
785
+ setup do
786
+ @dataset = Sequel::Dataset.new(nil)
787
+ end
788
+
789
+ specify "should accept a Dataset" do
790
+ proc {@dataset.from(@dataset)}.should_not raise_error
791
+ end
792
+
793
+ specify "should format a Dataset as a subquery if it has had options set" do
794
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
795
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
796
+ end
797
+
798
+ specify "should automatically alias sub-queries" do
799
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
800
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
801
+
802
+ d1 = @dataset.from(:a).group(:b)
803
+ d2 = @dataset.from(:c).group(:d)
804
+
805
+ @dataset.from(d1, d2).sql.should ==
806
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
807
+ end
808
+
809
+ specify "should accept a hash for aliasing" do
810
+ @dataset.from(:a => :b).sql.should ==
811
+ "SELECT * FROM a AS b"
812
+
813
+ @dataset.from(:a => 'b').sql.should ==
814
+ "SELECT * FROM a AS b"
815
+
816
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
817
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
818
+ end
819
+
820
+ specify "should always use a subquery if given a dataset" do
821
+ @dataset.from(@dataset.from(:a)).select_sql.should ==
822
+ "SELECT * FROM (SELECT * FROM a) AS t1"
823
+ end
824
+
825
+ specify "should raise if no source is given" do
826
+ proc {@dataset.from(@dataset.from).select_sql}.should raise_error(Sequel::Error)
827
+ end
828
+
829
+ specify "should accept sql functions" do
830
+ @dataset.from(:abc.sql_function(:def)).select_sql.should ==
831
+ "SELECT * FROM abc(def)"
832
+
833
+ @dataset.from(:a.sql_function(:i)).select_sql.should ==
834
+ "SELECT * FROM a(i)"
835
+ end
836
+
837
+ specify "should accept :schema__table___alias symbol format" do
838
+ @dataset.from(:abc__def).select_sql.should ==
839
+ "SELECT * FROM abc.def"
840
+ @dataset.from(:abc__def___d).select_sql.should ==
841
+ "SELECT * FROM abc.def AS d"
842
+ @dataset.from(:abc___def).select_sql.should ==
843
+ "SELECT * FROM abc AS def"
844
+ end
845
+ end
846
+
847
+ context "Dataset#select" do
848
+ setup do
849
+ @d = Sequel::Dataset.new(nil).from(:test)
850
+ end
851
+
852
+ specify "should accept variable arity" do
853
+ @d.select(:name).sql.should == 'SELECT name FROM test'
854
+ @d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
855
+ end
856
+
857
+ specify "should accept symbols and literal strings" do
858
+ @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
859
+ @d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
860
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
861
+ 'SELECT test.cc, test.d AS e FROM test'
862
+ @d.select('test.d AS e'.lit, :test__cc).sql.should ==
863
+ 'SELECT test.d AS e, test.cc FROM test'
864
+
865
+ # symbol helpers
866
+ @d.select(:test.*).sql.should ==
867
+ 'SELECT test.* FROM test'
868
+ @d.select(:test__name.as(:n)).sql.should ==
869
+ 'SELECT test.name AS n FROM test'
870
+ @d.select(:test__name___n).sql.should ==
871
+ 'SELECT test.name AS n FROM test'
872
+ end
873
+
874
+ specify "should use the wildcard if no arguments are given" do
875
+ @d.select.sql.should == 'SELECT * FROM test'
876
+ end
877
+
878
+ specify "should accept a hash for AS values" do
879
+ @d.select(:name => 'n', :__ggh => 'age').sql.should =~
880
+ /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
881
+ end
882
+
883
+ specify "should overrun the previous select option" do
884
+ @d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
885
+ @d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
886
+ end
887
+
888
+ specify "should accept arbitrary objects and literalize them correctly" do
889
+ @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
890
+
891
+ @d.select(nil, :sum.sql_function(:t), :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
892
+
893
+ @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
894
+ end
895
+
896
+ specify "should accept a block that yields a virtual row" do
897
+ @d.select{|o| o.a}.sql.should == 'SELECT a FROM test'
898
+ @d.select{a(1)}.sql.should == 'SELECT a(1) FROM test'
899
+ @d.select{|o| o.a(1, 2)}.sql.should == 'SELECT a(1, 2) FROM test'
900
+ @d.select{[a, a(1, 2)]}.sql.should == 'SELECT a, a(1, 2) FROM test'
901
+ end
902
+
903
+ specify "should merge regular arguments with argument returned from block" do
904
+ @d.select(:b){a}.sql.should == 'SELECT b, a FROM test'
905
+ @d.select(:b, :c){|o| o.a(1)}.sql.should == 'SELECT b, c, a(1) FROM test'
906
+ @d.select(:b){[a, a(1, 2)]}.sql.should == 'SELECT b, a, a(1, 2) FROM test'
907
+ @d.select(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT b, c, a, a(1, 2) FROM test'
908
+ end
909
+ end
910
+
911
+ context "Dataset#select_all" do
912
+ setup do
913
+ @d = Sequel::Dataset.new(nil).from(:test)
914
+ end
915
+
916
+ specify "should select the wildcard" do
917
+ @d.select_all.sql.should == 'SELECT * FROM test'
918
+ end
919
+
920
+ specify "should overrun the previous select option" do
921
+ @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
922
+ end
923
+ end
924
+
925
+ context "Dataset#select_more" do
926
+ setup do
927
+ @d = Sequel::Dataset.new(nil).from(:test)
928
+ end
929
+
930
+ specify "should act like #select for datasets with no selection" do
931
+ @d.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
932
+ @d.select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
933
+ @d.select(:blah).select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
934
+ end
935
+
936
+ specify "should add to the currently selected columns" do
937
+ @d.select(:a).select_more(:b).sql.should == 'SELECT a, b FROM test'
938
+ @d.select(:a.*).select_more(:b.*).sql.should == 'SELECT a.*, b.* FROM test'
939
+ end
940
+
941
+ specify "should accept a block that yields a virtual row" do
942
+ @d.select(:a).select_more{|o| o.b}.sql.should == 'SELECT a, b FROM test'
943
+ @d.select(:a.*).select_more(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
944
+ end
945
+ end
946
+
947
+ context "Dataset#order" do
948
+ setup do
949
+ @dataset = Sequel::Dataset.new(nil).from(:test)
950
+ end
951
+
952
+ specify "should include an ORDER BY clause in the select statement" do
953
+ @dataset.order(:name).sql.should ==
954
+ 'SELECT * FROM test ORDER BY name'
955
+ end
956
+
957
+ specify "should accept multiple arguments" do
958
+ @dataset.order(:name, :price.desc).sql.should ==
959
+ 'SELECT * FROM test ORDER BY name, price DESC'
960
+ end
961
+
962
+ specify "should overrun a previous ordering" do
963
+ @dataset.order(:name).order(:stamp).sql.should ==
964
+ 'SELECT * FROM test ORDER BY stamp'
965
+ end
966
+
967
+ specify "should accept a literal string" do
968
+ @dataset.order('dada ASC'.lit).sql.should ==
969
+ 'SELECT * FROM test ORDER BY dada ASC'
970
+ end
971
+
972
+ specify "should accept a hash as an expression" do
973
+ @dataset.order(:name=>nil).sql.should ==
974
+ 'SELECT * FROM test ORDER BY (name IS NULL)'
975
+ end
976
+
977
+ specify "should accept a nil to remove ordering" do
978
+ @dataset.order(:bah).order(nil).sql.should ==
979
+ 'SELECT * FROM test'
980
+ end
981
+
982
+ specify "should accept a block that yields a virtual row" do
983
+ @dataset.order{|o| o.a}.sql.should == 'SELECT * FROM test ORDER BY a'
984
+ @dataset.order{a(1)}.sql.should == 'SELECT * FROM test ORDER BY a(1)'
985
+ @dataset.order{|o| o.a(1, 2)}.sql.should == 'SELECT * FROM test ORDER BY a(1, 2)'
986
+ @dataset.order{[a, a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, a(1, 2)'
987
+ end
988
+
989
+ specify "should merge regular arguments with argument returned from block" do
990
+ @dataset.order(:b){a}.sql.should == 'SELECT * FROM test ORDER BY b, a'
991
+ @dataset.order(:b, :c){|o| o.a(1)}.sql.should == 'SELECT * FROM test ORDER BY b, c, a(1)'
992
+ @dataset.order(:b){[a, a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, a, a(1, 2)'
993
+ @dataset.order(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, c, a, a(1, 2)'
994
+ end
995
+ end
996
+
997
+ context "Dataset#unfiltered" do
998
+ setup do
999
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1000
+ end
1001
+
1002
+ specify "should remove filtering from the dataset" do
1003
+ @dataset.filter(:score=>1).unfiltered.sql.should ==
1004
+ 'SELECT * FROM test'
1005
+ end
1006
+ end
1007
+
1008
+ context "Dataset#unordered" do
1009
+ setup do
1010
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1011
+ end
1012
+
1013
+ specify "should remove ordering from the dataset" do
1014
+ @dataset.order(:name).unordered.sql.should ==
1015
+ 'SELECT * FROM test'
1016
+ end
1017
+ end
1018
+
1019
+ context "Dataset#with_sql" do
1020
+ setup do
1021
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1022
+ end
1023
+
1024
+ specify "should remove use static sql" do
1025
+ @dataset.with_sql('SELECT 1 FROM test').sql.should == 'SELECT 1 FROM test'
1026
+ end
1027
+
1028
+ specify "should keep row_proc and transform" do
1029
+ @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1030
+ @dataset.with_sql('SELECT 1 FROM test').instance_variable_get(:@transform).should == @dataset.instance_variable_get(:@transform)
1031
+ end
1032
+ end
1033
+
1034
+ context "Dataset#order_by" do
1035
+ setup do
1036
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1037
+ end
1038
+
1039
+ specify "should include an ORDER BY clause in the select statement" do
1040
+ @dataset.order_by(:name).sql.should ==
1041
+ 'SELECT * FROM test ORDER BY name'
1042
+ end
1043
+
1044
+ specify "should accept multiple arguments" do
1045
+ @dataset.order_by(:name, :price.desc).sql.should ==
1046
+ 'SELECT * FROM test ORDER BY name, price DESC'
1047
+ end
1048
+
1049
+ specify "should overrun a previous ordering" do
1050
+ @dataset.order_by(:name).order(:stamp).sql.should ==
1051
+ 'SELECT * FROM test ORDER BY stamp'
1052
+ end
1053
+
1054
+ specify "should accept a string" do
1055
+ @dataset.order_by('dada ASC'.lit).sql.should ==
1056
+ 'SELECT * FROM test ORDER BY dada ASC'
1057
+ end
1058
+
1059
+ specify "should accept a nil to remove ordering" do
1060
+ @dataset.order_by(:bah).order_by(nil).sql.should ==
1061
+ 'SELECT * FROM test'
1062
+ end
1063
+ end
1064
+
1065
+ context "Dataset#order_more" do
1066
+ setup do
1067
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1068
+ end
1069
+
1070
+ specify "should include an ORDER BY clause in the select statement" do
1071
+ @dataset.order_more(:name).sql.should ==
1072
+ 'SELECT * FROM test ORDER BY name'
1073
+ end
1074
+
1075
+ specify "should add to a previous ordering" do
1076
+ @dataset.order(:name).order_more(:stamp.desc).sql.should ==
1077
+ 'SELECT * FROM test ORDER BY name, stamp DESC'
1078
+ end
1079
+
1080
+ specify "should accept a block that yields a virtual row" do
1081
+ @dataset.order(:a).order_more{|o| o.b}.sql.should == 'SELECT * FROM test ORDER BY a, b'
1082
+ @dataset.order(:a, :b).order_more(:c, :d){[e, f(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, b, c, d, e, f(1, 2)'
1083
+ end
1084
+ end
1085
+
1086
+ context "Dataset#reverse_order" do
1087
+ setup do
1088
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1089
+ end
1090
+
1091
+ specify "should use DESC as default order" do
1092
+ @dataset.reverse_order(:name).sql.should ==
1093
+ 'SELECT * FROM test ORDER BY name DESC'
1094
+ end
1095
+
1096
+ specify "should invert the order given" do
1097
+ @dataset.reverse_order(:name.desc).sql.should ==
1098
+ 'SELECT * FROM test ORDER BY name ASC'
1099
+ end
1100
+
1101
+ specify "should invert the order for ASC expressions" do
1102
+ @dataset.reverse_order(:name.asc).sql.should ==
1103
+ 'SELECT * FROM test ORDER BY name DESC'
1104
+ end
1105
+
1106
+ specify "should accept multiple arguments" do
1107
+ @dataset.reverse_order(:name, :price.desc).sql.should ==
1108
+ 'SELECT * FROM test ORDER BY name DESC, price ASC'
1109
+ end
1110
+
1111
+ specify "should reverse a previous ordering if no arguments are given" do
1112
+ @dataset.order(:name).reverse_order.sql.should ==
1113
+ 'SELECT * FROM test ORDER BY name DESC'
1114
+ @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should ==
1115
+ 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1116
+ end
1117
+
1118
+ specify "should return an unordered dataset for a dataset with no order" do
1119
+ @dataset.unordered.reverse_order.sql.should ==
1120
+ 'SELECT * FROM test'
1121
+ end
1122
+
1123
+ specify "should have #reverse alias" do
1124
+ @dataset.order(:name).reverse.sql.should ==
1125
+ 'SELECT * FROM test ORDER BY name DESC'
1126
+ end
1127
+ end
1128
+
1129
+ context "Dataset#limit" do
1130
+ setup do
1131
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1132
+ end
1133
+
1134
+ specify "should include a LIMIT clause in the select statement" do
1135
+ @dataset.limit(10).sql.should ==
1136
+ 'SELECT * FROM test LIMIT 10'
1137
+ end
1138
+
1139
+ specify "should accept ranges" do
1140
+ @dataset.limit(3..7).sql.should ==
1141
+ 'SELECT * FROM test LIMIT 5 OFFSET 3'
1142
+
1143
+ @dataset.limit(3...7).sql.should ==
1144
+ 'SELECT * FROM test LIMIT 4 OFFSET 3'
1145
+ end
1146
+
1147
+ specify "should include an offset if a second argument is given" do
1148
+ @dataset.limit(6, 10).sql.should ==
1149
+ 'SELECT * FROM test LIMIT 6 OFFSET 10'
1150
+ end
1151
+
1152
+ specify "should work with fixed sql datasets" do
1153
+ @dataset.opts[:sql] = 'select * from cccc'
1154
+ @dataset.limit(6, 10).sql.should ==
1155
+ 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1156
+ end
1157
+
1158
+ specify "should raise an error if an invalid limit or offset is used" do
1159
+ proc{@dataset.limit(-1)}.should raise_error(Sequel::Error)
1160
+ proc{@dataset.limit(0)}.should raise_error(Sequel::Error)
1161
+ proc{@dataset.limit(1)}.should_not raise_error(Sequel::Error)
1162
+ proc{@dataset.limit(1, -1)}.should raise_error(Sequel::Error)
1163
+ proc{@dataset.limit(1, 0)}.should_not raise_error(Sequel::Error)
1164
+ proc{@dataset.limit(1, 1)}.should_not raise_error(Sequel::Error)
1165
+ end
1166
+ end
1167
+
1168
+ context "Dataset#naked" do
1169
+ setup do
1170
+ @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
1171
+ @d2 = @d1.clone
1172
+ @d2.row_proc = Proc.new{|r| r}
1173
+ end
1174
+
1175
+ specify "should remove any existing row_proc" do
1176
+ naked = @d2.naked
1177
+ naked.row_proc.should be_nil
1178
+ end
1179
+ end
1180
+
1181
+ context "Dataset#qualified_column_name" do
1182
+ setup do
1183
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1184
+ end
1185
+
1186
+ specify "should return the literal value if not given a symbol" do
1187
+ @dataset.literal(@dataset.send(:qualified_column_name, 'ccc__b', :items)).should == "'ccc__b'"
1188
+ @dataset.literal(@dataset.send(:qualified_column_name, 3, :items)).should == '3'
1189
+ @dataset.literal(@dataset.send(:qualified_column_name, 'a'.lit, :items)).should == 'a'
1190
+ end
1191
+
1192
+ specify "should qualify the column with the supplied table name if given an unqualified symbol" do
1193
+ @dataset.literal(@dataset.send(:qualified_column_name, :b1, :items)).should == 'items.b1'
1194
+ end
1195
+
1196
+ specify "should not changed the qualifed column's table if given a qualified symbol" do
1197
+ @dataset.literal(@dataset.send(:qualified_column_name, :ccc__b, :items)).should == 'ccc.b'
1198
+ end
1199
+ end
1200
+
1201
+ class DummyDataset < Sequel::Dataset
1202
+ VALUES = [
1203
+ {:a => 1, :b => 2},
1204
+ {:a => 3, :b => 4},
1205
+ {:a => 5, :b => 6}
1206
+ ]
1207
+ def fetch_rows(sql, &block)
1208
+ VALUES.each(&block)
1209
+ end
1210
+ end
1211
+
1212
+ context "Dataset#map" do
1213
+ setup do
1214
+ @d = DummyDataset.new(nil).from(:items)
1215
+ end
1216
+
1217
+ specify "should provide the usual functionality if no argument is given" do
1218
+ @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
1219
+ end
1220
+
1221
+ specify "should map using #[column name] if column name is given" do
1222
+ @d.map(:a).should == [1, 3, 5]
1223
+ end
1224
+
1225
+ specify "should return the complete dataset values if nothing is given" do
1226
+ @d.map.to_a.should == DummyDataset::VALUES
1227
+ end
1228
+ end
1229
+
1230
+ context "Dataset#to_hash" do
1231
+ setup do
1232
+ @d = DummyDataset.new(nil).from(:items)
1233
+ end
1234
+
1235
+ specify "should provide a hash with the first column as key and the second as value" do
1236
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
1237
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
1238
+ end
1239
+
1240
+ specify "should provide a hash with the first column as key and the entire hash as value if the value column is blank or nil" do
1241
+ @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1242
+ @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1243
+ end
1244
+ end
1245
+
1246
+ context "Dataset#distinct" do
1247
+ setup do
1248
+ @db = MockDatabase.new
1249
+ @dataset = @db[:test].select(:name)
1250
+ end
1251
+
1252
+ specify "should include DISTINCT clause in statement" do
1253
+ @dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
1254
+ end
1255
+
1256
+ deprec_specify "should be aliased by Dataset#uniq" do
1257
+ @dataset.uniq.sql.should == 'SELECT DISTINCT name FROM test'
1258
+ end
1259
+
1260
+ specify "should accept an expression list" do
1261
+ @dataset.distinct(:a, :b).sql.should == 'SELECT DISTINCT ON (a, b) name FROM test'
1262
+ @dataset.distinct(:stamp.cast(:integer), :node_id=>nil).sql.should == 'SELECT DISTINCT ON (CAST(stamp AS integer), (node_id IS NULL)) name FROM test'
1263
+ end
1264
+
1265
+ specify "should do a subselect for count" do
1266
+ @dataset.distinct.count
1267
+ @db.sqls.should == ['SELECT COUNT(*) FROM (SELECT DISTINCT name FROM test) AS t1 LIMIT 1']
1268
+ end
1269
+ end
1270
+
1271
+ context "Dataset#count" do
1272
+ setup do
1273
+ @c = Class.new(Sequel::Dataset) do
1274
+ def self.sql
1275
+ @@sql
1276
+ end
1277
+
1278
+ def fetch_rows(sql)
1279
+ @columns = [sql =~ /SELECT COUNT/i ? :count : :a]
1280
+ @@sql = sql
1281
+ yield({@columns.first=>1})
1282
+ end
1283
+ end
1284
+ @dataset = @c.new(nil).from(:test)
1285
+ end
1286
+
1287
+ specify "should format SQL properly" do
1288
+ @dataset.count.should == 1
1289
+ @c.sql.should == 'SELECT COUNT(*) FROM test LIMIT 1'
1290
+ end
1291
+
1292
+ deprec_specify "should be aliased by #size" do
1293
+ @dataset.size.should == 1
1294
+ end
1295
+
1296
+ specify "should include the where clause if it's there" do
1297
+ @dataset.filter(:abc.sql_number < 30).count.should == 1
1298
+ @c.sql.should == 'SELECT COUNT(*) FROM test WHERE (abc < 30) LIMIT 1'
1299
+ end
1300
+
1301
+ specify "should count properly for datasets with fixed sql" do
1302
+ @dataset.opts[:sql] = "select abc from xyz"
1303
+ @dataset.count.should == 1
1304
+ @c.sql.should == "SELECT COUNT(*) FROM (select abc from xyz) AS t1 LIMIT 1"
1305
+ end
1306
+
1307
+ specify "should count properly when using UNION, INTERSECT, or EXCEPT" do
1308
+ @dataset.union(@dataset).count.should == 1
1309
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"
1310
+ @dataset.intersect(@dataset).count.should == 1
1311
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"
1312
+ @dataset.except(@dataset).count.should == 1
1313
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"
1314
+ end
1315
+
1316
+ specify "should return limit if count is greater than it" do
1317
+ @dataset.limit(5).count.should == 1
1318
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1319
+ end
1320
+
1321
+ it "should work on a graphed_dataset" do
1322
+ @dataset.should_receive(:columns).twice.and_return([:a])
1323
+ @dataset.graph(@dataset, [:a], :table_alias=>:test2).count.should == 1
1324
+ @c.sql.should == 'SELECT COUNT(*) FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
1325
+ end
1326
+
1327
+ specify "should not cache the columns value" do
1328
+ ds = @dataset.from(:blah)
1329
+ ds.columns.should == [:a]
1330
+ ds.count.should == 1
1331
+ @c.sql.should == 'SELECT COUNT(*) FROM blah LIMIT 1'
1332
+ ds.columns.should == [:a]
1333
+ end
1334
+ end
1335
+
1336
+
1337
+ context "Dataset#group_and_count" do
1338
+ setup do
1339
+ @c = Class.new(Sequel::Dataset) do
1340
+ def self.sql
1341
+ @@sql
1342
+ end
1343
+
1344
+ def fetch_rows(sql)
1345
+ @@sql = sql
1346
+ yield({1 => 1})
1347
+ end
1348
+ end
1349
+ @ds = @c.new(nil).from(:test)
1350
+ end
1351
+
1352
+ specify "should format SQL properly" do
1353
+ @ds.group_and_count(:name).sql.should ==
1354
+ "SELECT name, count(*) AS count FROM test GROUP BY name ORDER BY count"
1355
+ end
1356
+
1357
+ specify "should accept multiple columns for grouping" do
1358
+ @ds.group_and_count(:a, :b).sql.should ==
1359
+ "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1360
+ end
1361
+
1362
+ deprec_specify "should work within query block" do
1363
+ @ds.query{group_and_count(:a, :b)}.sql.should ==
1364
+ "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1365
+ end
1366
+ end
1367
+
1368
+ context "Dataset#empty?" do
1369
+ specify "should return true if records exist in the dataset" do
1370
+ @c = Class.new(Sequel::Dataset) do
1371
+ def self.sql
1372
+ @@sql
1373
+ end
1374
+
1375
+ def fetch_rows(sql)
1376
+ @@sql = sql
1377
+ yield({1 => 1}) unless sql =~ /WHERE 'f'/
1378
+ end
1379
+ end
1380
+ @c.new(nil).from(:test).should_not be_empty
1381
+ @c.sql.should == 'SELECT 1 FROM test LIMIT 1'
1382
+ @c.new(nil).from(:test).filter(false).should be_empty
1383
+ @c.sql.should == "SELECT 1 FROM test WHERE 'f' LIMIT 1"
1384
+ end
1385
+ end
1386
+
1387
+ context "Dataset#join_table" do
1388
+ setup do
1389
+ @d = MockDataset.new(nil).from(:items)
1390
+ @d.quote_identifiers = true
1391
+ end
1392
+
1393
+ specify "should format the JOIN clause properly" do
1394
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
1395
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1396
+ end
1397
+
1398
+ specify "should handle multiple conditions on the same join table column" do
1399
+ @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
1400
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."category_id" = "items"."id") AND (("categories"."category_id" >= 0) AND ("categories"."category_id" <= 100)))'
1401
+ end
1402
+
1403
+ specify "should include WHERE clause if applicable" do
1404
+ @d.filter(:price.sql_number < 100).join_table(:right_outer, :categories, :category_id => :id).sql.should ==
1405
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") WHERE ("price" < 100)'
1406
+ end
1407
+
1408
+ specify "should include ORDER BY clause if applicable" do
1409
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
1410
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
1411
+ end
1412
+
1413
+ specify "should support multiple joins" do
1414
+ @d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
1415
+ 'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
1416
+ end
1417
+
1418
+ specify "should support left outer joins" do
1419
+ @d.join_table(:left_outer, :categories, :category_id=>:id).sql.should ==
1420
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1421
+
1422
+ @d.left_outer_join(:categories, :category_id=>:id).sql.should ==
1423
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1424
+ end
1425
+
1426
+ specify "should support right outer joins" do
1427
+ @d.join_table(:right_outer, :categories, :category_id=>:id).sql.should ==
1428
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1429
+
1430
+ @d.right_outer_join(:categories, :category_id=>:id).sql.should ==
1431
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1432
+ end
1433
+
1434
+ specify "should support full outer joins" do
1435
+ @d.join_table(:full_outer, :categories, :category_id=>:id).sql.should ==
1436
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1437
+
1438
+ @d.full_outer_join(:categories, :category_id=>:id).sql.should ==
1439
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1440
+ end
1441
+
1442
+ specify "should support inner joins" do
1443
+ @d.join_table(:inner, :categories, :category_id=>:id).sql.should ==
1444
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1445
+
1446
+ @d.inner_join(:categories, :category_id=>:id).sql.should ==
1447
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1448
+ end
1449
+
1450
+ specify "should default to a plain join if nil is used for the type" do
1451
+ @d.join_table(nil, :categories, :category_id=>:id).sql.should ==
1452
+ 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1453
+ end
1454
+
1455
+ specify "should use an inner join for Dataset#join" do
1456
+ @d.join(:categories, :category_id=>:id).sql.should ==
1457
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1458
+ end
1459
+
1460
+ specify "should support aliased tables using the deprecated argument" do
1461
+ @d.from('stats').join('players', {:id => :player_id}, 'p').sql.should ==
1462
+ 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1463
+ end
1464
+
1465
+ specify "should support aliased tables using the :table_alias option" do
1466
+ @d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should ==
1467
+ 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1468
+ end
1469
+
1470
+ specify "should support using an alias for the FROM when doing the first join with unqualified condition columns" do
1471
+ ds = MockDataset.new(nil).from(:foo => :f)
1472
+ ds.quote_identifiers = true
1473
+ ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
1474
+ 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1475
+ end
1476
+
1477
+ specify "should support implicit schemas in from table symbols" do
1478
+ @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should ==
1479
+ 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
1480
+ end
1481
+
1482
+ specify "should support implicit aliases in from table symbols" do
1483
+ @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should ==
1484
+ 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
1485
+ @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should ==
1486
+ 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
1487
+ end
1488
+
1489
+ specify "should support AliasedExpressions" do
1490
+ @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should ==
1491
+ 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
1492
+ end
1493
+
1494
+ specify "should support the :implicit_qualifier option" do
1495
+ @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should ==
1496
+ 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
1497
+ end
1498
+
1499
+ specify "should allow for arbitrary conditions in the JOIN clause" do
1500
+ @d.join_table(:left_outer, :categories, :status => 0).sql.should ==
1501
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
1502
+ @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should ==
1503
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
1504
+ @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
1505
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
1506
+ @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should ==
1507
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
1508
+ end
1509
+
1510
+ specify "should raise error for a table without a source" do
1511
+ proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}. \
1512
+ should raise_error(Sequel::Error)
1513
+ end
1514
+
1515
+ specify "should support joining datasets" do
1516
+ ds = Sequel::Dataset.new(nil).from(:categories)
1517
+
1518
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
1519
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
1520
+
1521
+ ds.filter!(:active => true)
1522
+
1523
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
1524
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
1525
+ end
1526
+
1527
+ specify "should support joining datasets and aliasing the join" do
1528
+ ds = Sequel::Dataset.new(nil).from(:categories)
1529
+
1530
+ @d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should ==
1531
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
1532
+ end
1533
+
1534
+ specify "should support joining multiple datasets" do
1535
+ ds = Sequel::Dataset.new(nil).from(:categories)
1536
+ ds2 = Sequel::Dataset.new(nil).from(:nodes).select(:name)
1537
+ ds3 = Sequel::Dataset.new(nil).from(:attributes).filter("name = 'blah'")
1538
+
1539
+ @d.join_table(:left_outer, ds, :item_id => :id).join_table(:inner, ds2, :node_id=>:id).join_table(:right_outer, ds3, :attribute_id=>:id).sql.should ==
1540
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id") ' \
1541
+ 'INNER JOIN (SELECT name FROM nodes) AS "t2" ON ("t2"."node_id" = "t1"."id") ' \
1542
+ 'RIGHT OUTER JOIN (SELECT * FROM attributes WHERE (name = \'blah\')) AS "t3" ON ("t3"."attribute_id" = "t2"."id")'
1543
+ end
1544
+
1545
+ specify "should support joining objects that respond to :table_name" do
1546
+ ds = Object.new
1547
+ def ds.table_name; :categories end
1548
+
1549
+ @d.join(ds, :item_id => :id).sql.should ==
1550
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
1551
+ end
1552
+
1553
+ specify "should support using a SQL String as the join condition" do
1554
+ @d.join(:categories, %{c.item_id = items.id}, :c).sql.should ==
1555
+ 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
1556
+ end
1557
+
1558
+ specify "should support using a boolean column as the join condition" do
1559
+ @d.join(:categories, :active).sql.should ==
1560
+ 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
1561
+ end
1562
+
1563
+ specify "should support using an expression as the join condition" do
1564
+ @d.join(:categories, :number.sql_number > 10).sql.should ==
1565
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
1566
+ end
1567
+
1568
+ specify "should support natural and cross joins using nil" do
1569
+ @d.join_table(:natural, :categories).sql.should ==
1570
+ 'SELECT * FROM "items" NATURAL JOIN "categories"'
1571
+ @d.join_table(:cross, :categories, nil).sql.should ==
1572
+ 'SELECT * FROM "items" CROSS JOIN "categories"'
1573
+ @d.join_table(:natural, :categories, nil, :c).sql.should ==
1574
+ 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
1575
+ end
1576
+
1577
+ specify "should support joins with a USING clause if an array of symbols is used" do
1578
+ @d.join(:categories, [:id]).sql.should ==
1579
+ 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
1580
+ @d.join(:categories, [:id1, :id2]).sql.should ==
1581
+ 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
1582
+ end
1583
+
1584
+ specify "should raise an error if using an array of symbols with a block" do
1585
+ proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
1586
+ end
1587
+
1588
+ specify "should support using a block that receieves the join table/alias, last join table/alias, and array of previous joins" do
1589
+ @d.join(:categories) do |join_alias, last_join_alias, joins|
1590
+ join_alias.should == :categories
1591
+ last_join_alias.should == :items
1592
+ joins.should == []
1593
+ end
1594
+
1595
+ @d.from(:items=>:i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1596
+ join_alias.should == :c
1597
+ last_join_alias.should == :i
1598
+ joins.should == []
1599
+ end
1600
+
1601
+ @d.from(:items___i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1602
+ join_alias.should == :c
1603
+ last_join_alias.should == :i
1604
+ joins.should == []
1605
+ end
1606
+
1607
+ @d.join(:blah).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1608
+ join_alias.should == :c
1609
+ last_join_alias.should == :blah
1610
+ joins.should be_a_kind_of(Array)
1611
+ joins.length.should == 1
1612
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
1613
+ joins.first.join_type.should == :inner
1614
+ end
1615
+
1616
+ @d.join_table(:natural, :blah, nil, :b).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1617
+ join_alias.should == :c
1618
+ last_join_alias.should == :b
1619
+ joins.should be_a_kind_of(Array)
1620
+ joins.length.should == 1
1621
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
1622
+ joins.first.join_type.should == :natural
1623
+ end
1624
+
1625
+ @d.join(:blah).join(:categories).join(:blah2) do |join_alias, last_join_alias, joins|
1626
+ join_alias.should == :blah2
1627
+ last_join_alias.should == :categories
1628
+ joins.should be_a_kind_of(Array)
1629
+ joins.length.should == 2
1630
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
1631
+ joins.first.table.should == :blah
1632
+ joins.last.should be_a_kind_of(Sequel::SQL::JoinClause)
1633
+ joins.last.table.should == :categories
1634
+ end
1635
+ end
1636
+
1637
+ specify "should use the block result as the only condition if no condition is given" do
1638
+ @d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
1639
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
1640
+ @d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
1641
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
1642
+ end
1643
+
1644
+ specify "should combine the block conditions and argument conditions if both given" do
1645
+ @d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
1646
+ 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
1647
+ @d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
1648
+ 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
1649
+ end
1650
+ end
1651
+
1652
+ context "Dataset#[]=" do
1653
+ setup do
1654
+ c = Class.new(Sequel::Dataset) do
1655
+ def last_sql
1656
+ @@last_sql
1657
+ end
1658
+
1659
+ def update(*args)
1660
+ @@last_sql = update_sql(*args)
1661
+ end
1662
+ end
1663
+
1664
+ @d = c.new(nil).from(:items)
1665
+ end
1666
+
1667
+ specify "should perform an update on the specified filter" do
1668
+ @d[:a => 1] = {:x => 3}
1669
+ @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
1670
+ end
1671
+ end
1672
+
1673
+ context "Dataset#set" do
1674
+ setup do
1675
+ c = Class.new(Sequel::Dataset) do
1676
+ def last_sql
1677
+ @@last_sql
1678
+ end
1679
+
1680
+ def update(*args, &block)
1681
+ @@last_sql = update_sql(*args, &block)
1682
+ end
1683
+ end
1684
+
1685
+ @d = c.new(nil).from(:items)
1686
+ end
1687
+
1688
+ specify "should act as alias to #update" do
1689
+ @d.set({:x => 3})
1690
+ @d.last_sql.should == 'UPDATE items SET x = 3'
1691
+ end
1692
+ end
1693
+
1694
+
1695
+ context "Dataset#insert_multiple" do
1696
+ setup do
1697
+ c = Class.new(Sequel::Dataset) do
1698
+ attr_reader :inserts
1699
+ def insert(arg)
1700
+ @inserts ||= []
1701
+ @inserts << arg
1702
+ end
1703
+ end
1704
+
1705
+ @d = c.new(nil)
1706
+ end
1707
+
1708
+ specify "should insert all items in the supplied array" do
1709
+ @d.insert_multiple [:aa, 5, 3, {1 => 2}]
1710
+ @d.inserts.should == [:aa, 5, 3, {1 => 2}]
1711
+ end
1712
+
1713
+ specify "should pass array items through the supplied block if given" do
1714
+ a = ["inevitable", "hello", "the ticking clock"]
1715
+ @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
1716
+ @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
1717
+ end
1718
+ end
1719
+
1720
+ context "Dataset aggregate methods" do
1721
+ setup do
1722
+ c = Class.new(Sequel::Dataset) do
1723
+ def fetch_rows(sql)
1724
+ yield({1 => sql})
1725
+ end
1726
+ end
1727
+ @d = c.new(nil).from(:test)
1728
+ end
1729
+
1730
+ specify "should include min" do
1731
+ @d.min(:a).should == 'SELECT min(a) FROM test LIMIT 1'
1732
+ end
1733
+
1734
+ specify "should include max" do
1735
+ @d.max(:b).should == 'SELECT max(b) FROM test LIMIT 1'
1736
+ end
1737
+
1738
+ specify "should include sum" do
1739
+ @d.sum(:c).should == 'SELECT sum(c) FROM test LIMIT 1'
1740
+ end
1741
+
1742
+ specify "should include avg" do
1743
+ @d.avg(:d).should == 'SELECT avg(d) FROM test LIMIT 1'
1744
+ end
1745
+
1746
+ specify "should accept qualified columns" do
1747
+ @d.avg(:test__bc).should == 'SELECT avg(test.bc) FROM test LIMIT 1'
1748
+ end
1749
+ end
1750
+
1751
+ context "Dataset#range" do
1752
+ setup do
1753
+ c = Class.new(Sequel::Dataset) do
1754
+ @@sql = nil
1755
+
1756
+ def last_sql; @@sql; end
1757
+
1758
+ def fetch_rows(sql)
1759
+ @@sql = sql
1760
+ yield(:v1 => 1, :v2 => 10)
1761
+ end
1762
+ end
1763
+ @d = c.new(nil).from(:test)
1764
+ end
1765
+
1766
+ specify "should generate a correct SQL statement" do
1767
+ @d.range(:stamp)
1768
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"
1769
+
1770
+ @d.filter(:price.sql_number > 100).range(:stamp)
1771
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"
1772
+ end
1773
+
1774
+ specify "should return a range object" do
1775
+ @d.range(:tryme).should == (1..10)
1776
+ end
1777
+ end
1778
+
1779
+ context "Dataset#interval" do
1780
+ setup do
1781
+ c = Class.new(Sequel::Dataset) do
1782
+ @@sql = nil
1783
+
1784
+ def last_sql; @@sql; end
1785
+
1786
+ def fetch_rows(sql)
1787
+ @@sql = sql
1788
+ yield(:v => 1234)
1789
+ end
1790
+ end
1791
+ @d = c.new(nil).from(:test)
1792
+ end
1793
+
1794
+ specify "should generate a correct SQL statement" do
1795
+ @d.interval(:stamp)
1796
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"
1797
+
1798
+ @d.filter(:price.sql_number > 100).interval(:stamp)
1799
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"
1800
+ end
1801
+
1802
+ specify "should return an integer" do
1803
+ @d.interval(:tryme).should == 1234
1804
+ end
1805
+ end
1806
+
1807
+ context "Dataset #first and #last" do
1808
+ setup do
1809
+ @c = Class.new(Sequel::Dataset) do
1810
+ def each(&block)
1811
+ s = select_sql
1812
+ x = [:a,1,:b,2,s]
1813
+ i = /LIMIT (\d+)/.match(s)[1].to_i.times{yield x}
1814
+ end
1815
+ end
1816
+ @d = @c.new(nil).from(:test)
1817
+ end
1818
+
1819
+ specify "should return a single record if no argument is given" do
1820
+ @d.order(:a).first.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a LIMIT 1']
1821
+ @d.order(:a).last.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a DESC LIMIT 1']
1822
+ end
1823
+
1824
+ specify "should return the first/last matching record if argument is not an Integer" do
1825
+ @d.order(:a).first(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1']
1826
+ @d.order(:a).first('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1']
1827
+ @d.order(:a).last(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1']
1828
+ @d.order(:a).last('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1']
1829
+ end
1830
+
1831
+ specify "should set the limit and return an array of records if the given number is > 1" do
1832
+ i = rand(10) + 10
1833
+ r = @d.order(:a).first(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a LIMIT #{i}"]] * i
1834
+ i = rand(10) + 10
1835
+ r = @d.order(:a).last(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a DESC LIMIT #{i}"]] * i
1836
+ end
1837
+
1838
+ specify "should return the first matching record if a block is given without an argument" do
1839
+ @d.first{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) LIMIT 1']
1840
+ @d.order(:name).last{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1']
1841
+ end
1842
+
1843
+ specify "should combine block and standard argument filters if argument is not an Integer" do
1844
+ @d.first(:y=>25){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1']
1845
+ @d.order(:name).last('y = ?', 16){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 16)) ORDER BY name DESC LIMIT 1']
1846
+ end
1847
+
1848
+ specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
1849
+ i = rand(10) + 10
1850
+ r = @d.order(:a).first(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"]] * i
1851
+ i = rand(10) + 10
1852
+ r = @d.order(:a).last(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a DESC LIMIT #{i}"]] * i
1853
+ end
1854
+
1855
+ specify "#last should raise if no order is given" do
1856
+ proc {@d.last}.should raise_error(Sequel::Error)
1857
+ proc {@d.last(2)}.should raise_error(Sequel::Error)
1858
+ proc {@d.order(:a).last}.should_not raise_error
1859
+ proc {@d.order(:a).last(2)}.should_not raise_error
1860
+ end
1861
+
1862
+ specify "#last should invert the order" do
1863
+ @d.order(:a).last.pop.should == 'SELECT * FROM test ORDER BY a DESC LIMIT 1'
1864
+ @d.order(:b.desc).last.pop.should == 'SELECT * FROM test ORDER BY b ASC LIMIT 1'
1865
+ @d.order(:c, :d).last.pop.should == 'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'
1866
+ @d.order(:e.desc, :f).last.pop.should == 'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'
1867
+ end
1868
+ end
1869
+
1870
+ context "Dataset compound operations" do
1871
+ setup do
1872
+ @a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
1873
+ @b = Sequel::Dataset.new(nil).from(:b).filter(:z => 2)
1874
+ end
1875
+
1876
+ specify "should support UNION and UNION ALL" do
1877
+ @a.union(@b).sql.should == \
1878
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)"
1879
+ @b.union(@a, true).sql.should == \
1880
+ "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1881
+ end
1882
+
1883
+ specify "should support INTERSECT and INTERSECT ALL" do
1884
+ @a.intersect(@b).sql.should == \
1885
+ "SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)"
1886
+ @b.intersect(@a, true).sql.should == \
1887
+ "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
1888
+ end
1889
+
1890
+ specify "should support EXCEPT and EXCEPT ALL" do
1891
+ @a.except(@b).sql.should == \
1892
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)"
1893
+ @b.except(@a, true).sql.should == \
1894
+ "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1895
+ end
1896
+
1897
+ specify "should handle chained compound operations" do
1898
+ @a.union(@b).union(@a, true).sql.should == \
1899
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1900
+ @a.intersect(@b, true).intersect(@a).sql.should == \
1901
+ "SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
1902
+ @a.except(@b).except(@a, true).sql.should == \
1903
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1904
+ @a.union(@b, true).intersect(@a).except(@b, true).union(@a).intersect(@b, true).except(@a).sql.should == \
1905
+ "SELECT * FROM a WHERE (z = 1) UNION ALL SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1) EXCEPT ALL SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
1906
+ end
1907
+
1908
+ specify "should use a subselect when using a compound operation with a dataset that already has a compound operation" do
1909
+ @a.union(@b.union(@a, true)).sql.should == \
1910
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1))"
1911
+ @a.intersect(@b.intersect(@a), true).sql.should == \
1912
+ "SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1))"
1913
+ @a.except(@b.except(@a, true)).sql.should == \
1914
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1))"
1915
+ @a.union(@b.intersect(@a.except(@b, true)), true).union(@a.intersect(@b.except(@a), true)).sql.should == \
1916
+ "SELECT * FROM a WHERE (z = 1) UNION ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT ALL SELECT * FROM b WHERE (z = 2))) UNION SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)))"
1917
+ end
1918
+ end
1919
+
1920
+ context "Dataset#[]" do
1921
+ setup do
1922
+ @c = Class.new(Sequel::Dataset) do
1923
+ @@last_dataset = nil
1924
+
1925
+ def self.last_dataset
1926
+ @@last_dataset
1927
+ end
1928
+
1929
+ def single_record
1930
+ @@last_dataset = opts ? clone(opts) : self
1931
+ {1 => 2, 3 => 4}
1932
+ end
1933
+ end
1934
+ @d = @c.new(nil).from(:test)
1935
+ end
1936
+
1937
+ specify "should return a single record filtered according to the given conditions" do
1938
+ @d[:name => 'didi'].should == {1 => 2, 3 => 4}
1939
+ @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "(name = 'didi')"
1940
+
1941
+ @d[:id => 5..45].should == {1 => 2, 3 => 4}
1942
+ @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "((id >= 5) AND (id <= 45))"
1943
+ end
1944
+ end
1945
+
1946
+ context "Dataset#single_record" do
1947
+ setup do
1948
+ @c = Class.new(Sequel::Dataset) do
1949
+ def fetch_rows(sql)
1950
+ yield sql
1951
+ end
1952
+ end
1953
+ @cc = Class.new(@c) do
1954
+ def fetch_rows(sql); end
1955
+ end
1956
+
1957
+ @d = @c.new(nil).from(:test)
1958
+ @e = @cc.new(nil).from(:test)
1959
+ end
1960
+
1961
+ specify "should call each with a limit of 1 and return the record" do
1962
+ @d.single_record.should == 'SELECT * FROM test LIMIT 1'
1963
+ end
1964
+
1965
+ deprec_specify "should pass opts to each" do
1966
+ @d.single_record(:order => [:name]).should == 'SELECT * FROM test ORDER BY name LIMIT 1'
1967
+ end
1968
+
1969
+ deprec_specify "should override the limit if passed as an option" do
1970
+ @d.single_record(:limit => 3).should == 'SELECT * FROM test LIMIT 1'
1971
+ end
1972
+
1973
+ specify "should return nil if no record is present" do
1974
+ @e.single_record.should be_nil
1975
+ end
1976
+ end
1977
+
1978
+ context "Dataset#single_value" do
1979
+ setup do
1980
+ @c = Class.new(Sequel::Dataset) do
1981
+ def fetch_rows(sql)
1982
+ yield({1 => sql})
1983
+ end
1984
+ end
1985
+ @cc = Class.new(@c) do
1986
+ def fetch_rows(sql); end
1987
+ end
1988
+
1989
+ @d = @c.new(nil).from(:test)
1990
+ @e = @cc.new(nil).from(:test)
1991
+ end
1992
+
1993
+ specify "should call each and return the first value of the first record" do
1994
+ @d.single_value.should == 'SELECT * FROM test LIMIT 1'
1995
+ end
1996
+
1997
+ deprec_specify "should pass opts to each" do
1998
+ @d.single_value(:from => [:blah]).should == 'SELECT * FROM blah LIMIT 1'
1999
+ end
2000
+
2001
+ specify "should return nil if no records" do
2002
+ @e.single_value.should be_nil
2003
+ end
2004
+
2005
+ it "should work on a graphed_dataset" do
2006
+ @d.should_receive(:columns).twice.and_return([:a])
2007
+ @d.graph(@d, [:a], :table_alias=>:test2).single_value.should == 'SELECT test.a, test2.a AS test2_a FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
2008
+ end
2009
+ end
2010
+
2011
+ context "Dataset#get" do
2012
+ setup do
2013
+ @c = Class.new(Sequel::Dataset) do
2014
+ attr_reader :last_sql
2015
+
2016
+ def fetch_rows(sql)
2017
+ @last_sql = sql
2018
+ yield(:name => sql)
2019
+ end
2020
+ end
2021
+
2022
+ @d = @c.new(nil).from(:test)
2023
+ end
2024
+
2025
+ specify "should select the specified column and fetch its value" do
2026
+ @d.get(:name).should == "SELECT name FROM test LIMIT 1"
2027
+ @d.get(:abc).should == "SELECT abc FROM test LIMIT 1" # the first available value is returned always
2028
+ end
2029
+
2030
+ specify "should work with filters" do
2031
+ @d.filter(:id => 1).get(:name).should == "SELECT name FROM test WHERE (id = 1) LIMIT 1"
2032
+ end
2033
+
2034
+ specify "should work with aliased fields" do
2035
+ @d.get(:x__b.as(:name)).should == "SELECT x.b AS name FROM test LIMIT 1"
2036
+ end
2037
+
2038
+ specify "should accept a block that yields a virtual row" do
2039
+ @d.get{|o| o.x__b.as(:name)}.should == "SELECT x.b AS name FROM test LIMIT 1"
2040
+ @d.get{x(1).as(:name)}.should == "SELECT x(1) AS name FROM test LIMIT 1"
2041
+ end
2042
+
2043
+ specify "should raise an error if both a regular argument and block argument are used" do
2044
+ proc{@d.get(:name){|o| o.x__b.as(:name)}}.should raise_error(Sequel::Error)
2045
+ end
2046
+ end
2047
+
2048
+ context "Dataset#set_row_proc" do
2049
+ setup do
2050
+ @c = Class.new(Sequel::Dataset) do
2051
+ def fetch_rows(sql, &block)
2052
+ # yield a hash with kind as the 1 bit of a number
2053
+ (1..10).each {|i| block.call({:kind => i[0]})}
2054
+ end
2055
+ end
2056
+ @dataset = @c.new(nil).from(:items)
2057
+ end
2058
+
2059
+ specify "should cause dataset to pass all rows through the filter" do
2060
+ @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2061
+
2062
+ rows = @dataset.all
2063
+ rows.size.should == 10
2064
+
2065
+ rows.each {|r| r[:der].should == (r[:kind] + 2)}
2066
+ end
2067
+
2068
+ specify "should be copied over when dataset is cloned" do
2069
+ @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2070
+
2071
+ @dataset.filter(:a => 1).first.should == {:kind => 1, :der => 3}
2072
+ end
2073
+ end
2074
+
2075
+ context "Dataset#set_model" do
2076
+ setup do
2077
+ @c = Class.new(Sequel::Dataset) do
2078
+ def fetch_rows(sql, &block)
2079
+ # yield a hash with kind as the 1 bit of a number
2080
+ (1..10).each {|i| block.call({:kind => i[0]})}
2081
+ end
2082
+ end
2083
+ @dataset = @c.new(nil).from(:items)
2084
+ @m = Class.new do
2085
+ attr_accessor :c, :args
2086
+ def initialize(c, *args); @c = c; @args = args; end
2087
+ def ==(o); (@c == o.c) && (@args = o.args); end
2088
+ end
2089
+ end
2090
+
2091
+ deprec_specify "should clear the models hash and restore the stock #each if nil is specified" do
2092
+ @dataset.set_model(@m)
2093
+ @dataset.set_model(nil)
2094
+ @dataset.first.should == {:kind => 1}
2095
+ @dataset.model_classes.should be_nil
2096
+ end
2097
+
2098
+ deprec_specify "should clear the models hash and restore the stock #each if nothing is specified" do
2099
+ @dataset.set_model(@m)
2100
+ @dataset.set_model(nil)
2101
+ @dataset.first.should == {:kind => 1}
2102
+ @dataset.model_classes.should be_nil
2103
+ end
2104
+
2105
+ deprec_specify "should alter #each to provide model instances" do
2106
+ @dataset.first.should == {:kind => 1}
2107
+ @dataset.set_model(@m)
2108
+ @dataset.first.should == @m.new({:kind => 1})
2109
+ end
2110
+
2111
+ deprec_specify "should set opts[:naked] to nil" do
2112
+ @dataset.opts[:naked] = true
2113
+ @dataset.set_model(@m)
2114
+ @dataset.opts[:naked].should be_nil
2115
+ end
2116
+
2117
+ deprec_specify "should send additional arguments to the models' initialize method" do
2118
+ @dataset.set_model(@m, 7, 6, 5)
2119
+ @dataset.first.should == @m.new({:kind => 1}, 7, 6, 5)
2120
+ end
2121
+
2122
+ deprec_specify "should provide support for polymorphic model instantiation" do
2123
+ @m1 = Class.new(@m)
2124
+ @m2 = Class.new(@m)
2125
+ @dataset.set_model(:kind, 0 => @m1, 1 => @m2)
2126
+ @dataset.opts[:polymorphic_key].should == :kind
2127
+ all = @dataset.all
2128
+ all[0].class.should == @m2
2129
+ all[1].class.should == @m1
2130
+ all[2].class.should == @m2
2131
+ all[3].class.should == @m1
2132
+ #...
2133
+
2134
+ # denude model
2135
+ @dataset.set_model(nil)
2136
+ @dataset.first.should == {:kind => 1}
2137
+ end
2138
+
2139
+ deprec_specify "should send additional arguments for polymorphic models as well" do
2140
+ @m1 = Class.new(@m)
2141
+ @m2 = Class.new(@m)
2142
+ @dataset.set_model(:kind, {0 => @m1, 1 => @m2}, :hey => :wow)
2143
+ all = @dataset.all
2144
+ all[0].class.should == @m2; all[0].args.should == [{:hey => :wow}]
2145
+ all[1].class.should == @m1; all[1].args.should == [{:hey => :wow}]
2146
+ all[2].class.should == @m2; all[2].args.should == [{:hey => :wow}]
2147
+ all[3].class.should == @m1; all[3].args.should == [{:hey => :wow}]
2148
+ end
2149
+
2150
+ deprec_specify "should raise for invalid parameters" do
2151
+ proc {@dataset.set_model('kind')}.should raise_error(ArgumentError)
2152
+ proc {@dataset.set_model(0)}.should raise_error(ArgumentError)
2153
+ proc {@dataset.set_model(:kind)}.should raise_error(ArgumentError) # no hash given
2154
+ end
2155
+ end
2156
+
2157
+ context "Dataset#model_classes" do
2158
+ setup do
2159
+ @c = Class.new(Sequel::Dataset) do
2160
+ # # We don't need that for now
2161
+ # def fetch_rows(sql, &block)
2162
+ # (1..10).each(&block)
2163
+ # end
2164
+ end
2165
+ @dataset = @c.new(nil).from(:items)
2166
+ @m = Class.new do
2167
+ attr_accessor :c
2168
+ def initialize(c); @c = c; end
2169
+ def ==(o); @c == o.c; end
2170
+ end
2171
+ end
2172
+
2173
+ deprec_specify "should return nil for a naked dataset" do
2174
+ @dataset.model_classes.should == nil
2175
+ end
2176
+
2177
+ deprec_specify "should return a {nil => model_class} hash for a model dataset" do
2178
+ @dataset.set_model(@m)
2179
+ @dataset.model_classes.should == {nil => @m}
2180
+ end
2181
+
2182
+ deprec_specify "should return the polymorphic hash for a polymorphic model dataset" do
2183
+ @m1 = Class.new(@m)
2184
+ @m2 = Class.new(@m)
2185
+ @dataset.set_model(:key, 0 => @m1, 1 => @m2)
2186
+ @dataset.model_classes.should == {0 => @m1, 1 => @m2}
2187
+ end
2188
+ end
2189
+
2190
+ context "Dataset#polymorphic_key" do
2191
+ setup do
2192
+ @c = Class.new(Sequel::Dataset) do
2193
+ # # We don't need this for now
2194
+ # def fetch_rows(sql, &block)
2195
+ # (1..10).each(&block)
2196
+ # end
2197
+ end
2198
+ @dataset = @c.new(nil).from(:items)
2199
+ @m = Class.new do
2200
+ attr_accessor :c
2201
+ def initialize(c); @c = c; end
2202
+ def ==(o); @c == o.c; end
2203
+ end
2204
+ end
2205
+
2206
+ deprec_specify "should return nil for a naked dataset" do
2207
+ @dataset.polymorphic_key.should be_nil
2208
+ end
2209
+
2210
+ deprec_specify "should return the polymorphic key" do
2211
+ @dataset.set_model(:id, nil => @m)
2212
+ @dataset.polymorphic_key.should == :id
2213
+ end
2214
+ end
2215
+
2216
+ context "A model dataset" do
2217
+ setup do
2218
+ @c = Class.new(Sequel::Dataset) do
2219
+ def fetch_rows(sql, &block)
2220
+ (1..10).each(&block)
2221
+ end
2222
+ end
2223
+ @dataset = @c.new(nil).from(:items)
2224
+ @m = Class.new do
2225
+ attr_accessor :c
2226
+ def initialize(c); @c = c; end
2227
+ def ==(o); @c == o.c; end
2228
+ end
2229
+ @dataset.row_proc = Proc.new{|r| @m.new(r)}
2230
+ end
2231
+
2232
+ deprec_specify "should supply naked records if the naked option is specified" do
2233
+ @dataset.each {|r| r.class.should == @m}
2234
+ @dataset.naked.each(:naked => true) {|r| r.class.should == Fixnum}
2235
+ end
2236
+ end
2237
+
2238
+ context "A polymorphic model dataset" do
2239
+ setup do
2240
+ @c = Class.new(Sequel::Dataset) do
2241
+ def fetch_rows(sql, &block)
2242
+ (1..10).each {|i| block.call(:bit => i[0])}
2243
+ end
2244
+ end
2245
+ @dataset = @c.new(nil).from(:items)
2246
+ @m = Class.new do
2247
+ attr_accessor :c
2248
+ def initialize(c); @c = c; end
2249
+ def ==(o); @c == o.c; end
2250
+ end
2251
+ end
2252
+
2253
+ deprec_specify "should use a nil key in the polymorphic hash to specify the default model class" do
2254
+ @m2 = Class.new(@m)
2255
+ @dataset.set_model(:bit, nil => @m, 1 => @m2)
2256
+ all = @dataset.all
2257
+ all[0].class.should == @m2
2258
+ all[1].class.should == @m
2259
+ all[2].class.should == @m2
2260
+ all[3].class.should == @m
2261
+ #...
2262
+ end
2263
+
2264
+ deprec_specify "should raise Sequel::Error if no suitable class is found in the polymorphic hash" do
2265
+ @m2 = Class.new(@m)
2266
+ @dataset.set_model(:bit, 1 => @m2)
2267
+ proc {@dataset.all}.should raise_error(Sequel::Error)
2268
+ end
2269
+
2270
+ deprec_specify "should supply naked records if the naked option is specified" do
2271
+ @dataset.set_model(:bit, nil => @m)
2272
+ @dataset.each(:naked => true) {|r| r.class.should == Hash}
2273
+ end
2274
+ end
2275
+
2276
+ context "A dataset with associated model class(es)" do
2277
+ setup do
2278
+ @c = Class.new(Sequel::Dataset) do
2279
+ def fetch_rows(sql, &block)
2280
+ block.call({:x => 1, :y => 2})
2281
+ end
2282
+ end
2283
+ @dataset = @c.new(nil).from(:items)
2284
+ @m1 = Class.new do
2285
+ attr_accessor :v
2286
+ def initialize(v); @v = v; end
2287
+ end
2288
+ @m2 = Class.new do
2289
+ attr_accessor :v, :vv
2290
+ def initialize(v = nil); @v = v; end
2291
+ def self.load(v); o = new(nil); o.vv = v; o; end
2292
+ end
2293
+ @m3 = Class.new(@m2)
2294
+ end
2295
+
2296
+ deprec_specify "should instantiate an instance by passing the record hash as argument" do
2297
+ @dataset.set_model(@m1)
2298
+ o = @dataset.first
2299
+ o.class.should == @m1
2300
+ o.v.should == {:x => 1, :y => 2}
2301
+ end
2302
+
2303
+ deprec_specify "should use the .load constructor if available" do
2304
+ @dataset.set_model(@m2)
2305
+ o = @dataset.first
2306
+ o.class.should == @m2
2307
+ o.v.should == nil
2308
+ o.vv.should == {:x => 1, :y => 2}
2309
+ end
2310
+
2311
+ deprec_specify "should use the .load constructor also for polymorphic datasets" do
2312
+ @dataset.set_model(:y, 1 => @m2, 2 => @m3)
2313
+ o = @dataset.first
2314
+ o.class.should == @m3
2315
+ o.v.should == nil
2316
+ o.vv.should == {:x => 1, :y => 2}
2317
+ end
2318
+ end
2319
+
2320
+ context "Dataset#<<" do
2321
+ setup do
2322
+ @d = Sequel::Dataset.new(nil)
2323
+ @d.meta_def(:insert) do |*args|
2324
+ 1234567890
2325
+ end
2326
+ end
2327
+
2328
+ specify "should call #insert" do
2329
+ (@d << {:name => 1}).should == 1234567890
2330
+ end
2331
+ end
2332
+
2333
+ context "A paginated dataset" do
2334
+ setup do
2335
+ @d = Sequel::Dataset.new(nil)
2336
+ @d.meta_def(:count) {153}
2337
+
2338
+ deprec{@paginated = @d.paginate(1, 20)}
2339
+ end
2340
+
2341
+ deprec_specify "should raise an error if the dataset already has a limit" do
2342
+ proc{@d.limit(10).paginate(1,10)}.should raise_error(Sequel::Error)
2343
+ proc{@paginated.paginate(2,20)}.should raise_error(Sequel::Error)
2344
+ end
2345
+
2346
+ deprec_specify "should set the limit and offset options correctly" do
2347
+ @paginated.opts[:limit].should == 20
2348
+ @paginated.opts[:offset].should == 0
2349
+ end
2350
+
2351
+ deprec_specify "should set the page count correctly" do
2352
+ @paginated.page_count.should == 8
2353
+ @d.paginate(1, 50).page_count.should == 4
2354
+ end
2355
+
2356
+ deprec_specify "should set the current page number correctly" do
2357
+ @paginated.current_page.should == 1
2358
+ @d.paginate(3, 50).current_page.should == 3
2359
+ end
2360
+
2361
+ deprec_specify "should return the next page number or nil if we're on the last" do
2362
+ @paginated.next_page.should == 2
2363
+ @d.paginate(4, 50).next_page.should be_nil
2364
+ end
2365
+
2366
+ deprec_specify "should return the previous page number or nil if we're on the last" do
2367
+ @paginated.prev_page.should be_nil
2368
+ @d.paginate(4, 50).prev_page.should == 3
2369
+ end
2370
+
2371
+ deprec_specify "should return the page range" do
2372
+ @paginated.page_range.should == (1..8)
2373
+ @d.paginate(4, 50).page_range.should == (1..4)
2374
+ end
2375
+
2376
+ deprec_specify "should return the record range for the current page" do
2377
+ @paginated.current_page_record_range.should == (1..20)
2378
+ @d.paginate(4, 50).current_page_record_range.should == (151..153)
2379
+ @d.paginate(5, 50).current_page_record_range.should == (0..0)
2380
+ end
2381
+
2382
+ deprec_specify "should return the record count for the current page" do
2383
+ @paginated.current_page_record_count.should == 20
2384
+ @d.paginate(3, 50).current_page_record_count.should == 50
2385
+ @d.paginate(4, 50).current_page_record_count.should == 3
2386
+ @d.paginate(5, 50).current_page_record_count.should == 0
2387
+ end
2388
+
2389
+ deprec_specify "should know if current page is last page" do
2390
+ @paginated.last_page?.should be_false
2391
+ @d.paginate(2, 20).last_page?.should be_false
2392
+ @d.paginate(5, 30).last_page?.should be_false
2393
+ @d.paginate(6, 30).last_page?.should be_true
2394
+ end
2395
+
2396
+ deprec_specify "should know if current page is first page" do
2397
+ @paginated.first_page?.should be_true
2398
+ @d.paginate(1, 20).first_page?.should be_true
2399
+ @d.paginate(2, 20).first_page?.should be_false
2400
+ end
2401
+
2402
+ deprec_specify "should work with fixed sql" do
2403
+ ds = @d.clone(:sql => 'select * from blah')
2404
+ ds.meta_def(:count) {150}
2405
+ ds.paginate(2, 50).sql.should == 'SELECT * FROM (select * from blah) AS t1 LIMIT 50 OFFSET 50'
2406
+ end
2407
+ end
2408
+
2409
+ context "Dataset#each_page" do
2410
+ setup do
2411
+ @d = Sequel::Dataset.new(nil).from(:items)
2412
+ @d.meta_def(:count) {153}
2413
+ end
2414
+
2415
+ deprec_specify "should raise an error if the dataset already has a limit" do
2416
+ proc{@d.limit(10).each_page(10){}}.should raise_error(Sequel::Error)
2417
+ end
2418
+
2419
+ deprec_specify "should iterate over each page in the resultset as a paginated dataset" do
2420
+ a = []
2421
+ @d.each_page(50) {|p| a << p}
2422
+ a.map {|p| p.sql}.should == [
2423
+ 'SELECT * FROM items LIMIT 50 OFFSET 0',
2424
+ 'SELECT * FROM items LIMIT 50 OFFSET 50',
2425
+ 'SELECT * FROM items LIMIT 50 OFFSET 100',
2426
+ 'SELECT * FROM items LIMIT 50 OFFSET 150',
2427
+ ]
2428
+ end
2429
+ end
2430
+
2431
+ context "Dataset#columns" do
2432
+ setup do
2433
+ @dataset = DummyDataset.new(nil).from(:items)
2434
+ @dataset.meta_def(:columns=) {|c| @columns = c}
2435
+ i = 'a'
2436
+ @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2437
+ end
2438
+
2439
+ specify "should return the value of @columns if @columns is not nil" do
2440
+ @dataset.columns = [:a, :b, :c]
2441
+ @dataset.columns.should == [:a, :b, :c]
2442
+ end
2443
+
2444
+ specify "should attempt to get a single record and return @columns if @columns is nil" do
2445
+ @dataset.columns = nil
2446
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2447
+ @dataset.opts[:from] = [:nana]
2448
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2449
+ end
2450
+
2451
+ specify "should ignore any filters, orders, or DISTINCT clauses" do
2452
+ @dataset.filter!(:b=>100).order!(:b).distinct!(:b)
2453
+ @dataset.columns = nil
2454
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2455
+ end
2456
+ end
2457
+
2458
+ context "Dataset#columns!" do
2459
+ setup do
2460
+ @dataset = DummyDataset.new(nil).from(:items)
2461
+ i = 'a'
2462
+ @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2463
+ end
2464
+
2465
+ specify "should always attempt to get a record and return @columns" do
2466
+ @dataset.columns!.should == 'SELECT * FROM items LIMIT 1a'
2467
+ @dataset.columns!.should == 'SELECT * FROM items LIMIT 1b'
2468
+ @dataset.opts[:from] = [:nana]
2469
+ @dataset.columns!.should == 'SELECT * FROM nana LIMIT 1c'
2470
+ end
2471
+ end
2472
+
2473
+ require 'stringio'
2474
+
2475
+ context "Dataset#print" do
2476
+ setup do
2477
+ @output = StringIO.new
2478
+ @orig_stdout = $stdout
2479
+ $stdout = @output
2480
+ @dataset = DummyDataset.new(nil).from(:items)
2481
+ end
2482
+
2483
+ teardown do
2484
+ $stdout = @orig_stdout
2485
+ end
2486
+
2487
+ deprec_specify "should print out a table with the values" do
2488
+ @dataset.print(:a, :b)
2489
+ @output.rewind
2490
+ @output.read.should == \
2491
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
2492
+ end
2493
+
2494
+ deprec_specify "should default to the dataset's columns" do
2495
+ @dataset.meta_def(:columns) {[:a, :b]}
2496
+ @dataset.print
2497
+ @output.rewind
2498
+ @output.read.should == \
2499
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
2500
+ end
2501
+ end
2502
+
2503
+ context "Dataset#multi_insert" do
2504
+ setup do
2505
+ @dbc = Class.new do
2506
+ attr_reader :sqls
2507
+
2508
+ def execute(sql, opts={})
2509
+ @sqls ||= []
2510
+ @sqls << sql
2511
+ end
2512
+ alias execute_dui execute
2513
+
2514
+ def transaction(opts={})
2515
+ @sqls ||= []
2516
+ @sqls << 'BEGIN'
2517
+ yield
2518
+ @sqls << 'COMMIT'
2519
+ end
2520
+ end
2521
+ @db = @dbc.new
2522
+
2523
+ @ds = Sequel::Dataset.new(@db).from(:items)
2524
+
2525
+ @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2526
+ end
2527
+
2528
+ specify "should join all inserts into a single SQL string" do
2529
+ @ds.multi_insert(@list)
2530
+ @db.sqls.should == [
2531
+ 'BEGIN',
2532
+ "INSERT INTO items (name) VALUES ('abc')",
2533
+ "INSERT INTO items (name) VALUES ('def')",
2534
+ "INSERT INTO items (name) VALUES ('ghi')",
2535
+ 'COMMIT'
2536
+ ]
2537
+ end
2538
+
2539
+ specify "should handle different formats for tables" do
2540
+ @ds = @ds.from(:sch__tab)
2541
+ @ds.multi_insert(@list)
2542
+ @db.sqls.should == [
2543
+ 'BEGIN',
2544
+ "INSERT INTO sch.tab (name) VALUES ('abc')",
2545
+ "INSERT INTO sch.tab (name) VALUES ('def')",
2546
+ "INSERT INTO sch.tab (name) VALUES ('ghi')",
2547
+ 'COMMIT'
2548
+ ]
2549
+ @db.sqls.clear
2550
+
2551
+ @ds = @ds.from(:tab.qualify(:sch))
2552
+ @ds.multi_insert(@list)
2553
+ @db.sqls.should == [
2554
+ 'BEGIN',
2555
+ "INSERT INTO sch.tab (name) VALUES ('abc')",
2556
+ "INSERT INTO sch.tab (name) VALUES ('def')",
2557
+ "INSERT INTO sch.tab (name) VALUES ('ghi')",
2558
+ 'COMMIT'
2559
+ ]
2560
+ @db.sqls.clear
2561
+ @ds = @ds.from(:sch__tab.identifier)
2562
+ @ds.multi_insert(@list)
2563
+ @db.sqls.should == [
2564
+ 'BEGIN',
2565
+ "INSERT INTO sch__tab (name) VALUES ('abc')",
2566
+ "INSERT INTO sch__tab (name) VALUES ('def')",
2567
+ "INSERT INTO sch__tab (name) VALUES ('ghi')",
2568
+ 'COMMIT'
2569
+ ]
2570
+ end
2571
+
2572
+ specify "should accept the :commit_every option for committing every x records" do
2573
+ @ds.multi_insert(@list, :commit_every => 2)
2574
+ @db.sqls.should == [
2575
+ 'BEGIN',
2576
+ "INSERT INTO items (name) VALUES ('abc')",
2577
+ "INSERT INTO items (name) VALUES ('def')",
2578
+ 'COMMIT',
2579
+ 'BEGIN',
2580
+ "INSERT INTO items (name) VALUES ('ghi')",
2581
+ 'COMMIT'
2582
+ ]
2583
+ end
2584
+
2585
+ specify "should accept the :slice option for committing every x records" do
2586
+ @ds.multi_insert(@list, :slice => 2)
2587
+ @db.sqls.should == [
2588
+ 'BEGIN',
2589
+ "INSERT INTO items (name) VALUES ('abc')",
2590
+ "INSERT INTO items (name) VALUES ('def')",
2591
+ 'COMMIT',
2592
+ 'BEGIN',
2593
+ "INSERT INTO items (name) VALUES ('ghi')",
2594
+ 'COMMIT'
2595
+ ]
2596
+ end
2597
+
2598
+ specify "should accept string keys as column names" do
2599
+ @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2600
+ @ds.multi_insert(['x', 'y'], [[1, 2], [3, 4]])
2601
+ @db.sqls.should == [
2602
+ 'BEGIN',
2603
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2604
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2605
+ 'COMMIT'
2606
+ ] * 2
2607
+ end
2608
+
2609
+ specify "should accept a columns array and a values array" do
2610
+ @ds.multi_insert([:x, :y], [[1, 2], [3, 4]])
2611
+ @db.sqls.should == [
2612
+ 'BEGIN',
2613
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2614
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2615
+ 'COMMIT'
2616
+ ]
2617
+ end
2618
+
2619
+ specify "should accept a columns array and a dataset" do
2620
+ @ds2 = Sequel::Dataset.new(@db).from(:cats).filter(:purr => true).select(:a, :b)
2621
+
2622
+ @ds.multi_insert([:x, :y], @ds2)
2623
+ @db.sqls.should == [
2624
+ 'BEGIN',
2625
+ "INSERT INTO items (x, y) VALUES (SELECT a, b FROM cats WHERE (purr IS TRUE))",
2626
+ 'COMMIT'
2627
+ ]
2628
+ end
2629
+
2630
+ specify "should accept a columns array and a values array with slice option" do
2631
+ @ds.multi_insert([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2632
+ @db.sqls.should == [
2633
+ 'BEGIN',
2634
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2635
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2636
+ 'COMMIT',
2637
+ 'BEGIN',
2638
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2639
+ 'COMMIT'
2640
+ ]
2641
+ end
2642
+
2643
+ deprec_specify "should be aliased by #import" do
2644
+ @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2645
+ @db.sqls.should == [
2646
+ 'BEGIN',
2647
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2648
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2649
+ 'COMMIT',
2650
+ 'BEGIN',
2651
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2652
+ 'COMMIT'
2653
+ ]
2654
+ end
2655
+
2656
+ specify "should not do anything if no columns or values are given" do
2657
+ @ds.multi_insert
2658
+ @db.sqls.should be_nil
2659
+
2660
+ @ds.multi_insert([])
2661
+ @db.sqls.should be_nil
2662
+
2663
+ @ds.multi_insert([], [])
2664
+ @db.sqls.should be_nil
2665
+
2666
+ @ds.multi_insert([{}, {}])
2667
+ @db.sqls.should be_nil
2668
+
2669
+ @ds.multi_insert([:a, :b], [])
2670
+ @db.sqls.should be_nil
2671
+
2672
+ @ds.multi_insert([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2673
+ @db.sqls.should == [
2674
+ 'BEGIN',
2675
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2676
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2677
+ 'COMMIT',
2678
+ 'BEGIN',
2679
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2680
+ 'COMMIT'
2681
+ ]
2682
+ end
2683
+
2684
+ end
2685
+
2686
+ context "Dataset#query" do
2687
+ setup do
2688
+ @d = Sequel::Dataset.new(nil)
2689
+ end
2690
+
2691
+ deprec_specify "should support #from" do
2692
+ q = @d.query {from :xxx}
2693
+ q.class.should == @d.class
2694
+ q.sql.should == "SELECT * FROM xxx"
2695
+ end
2696
+
2697
+ deprec_specify "should support #select" do
2698
+ q = @d.query do
2699
+ select :a, :b___mongo
2700
+ from :yyy
2701
+ end
2702
+ q.class.should == @d.class
2703
+ q.sql.should == "SELECT a, b AS mongo FROM yyy"
2704
+ end
2705
+
2706
+ deprec_specify "should support #where" do
2707
+ q = @d.query do
2708
+ from :zzz
2709
+ where(:x + 2 > :y + 3)
2710
+ end
2711
+ q.class.should == @d.class
2712
+ q.sql.should == "SELECT * FROM zzz WHERE ((x + 2) > (y + 3))"
2713
+
2714
+ q = @d.from(:zzz).query do
2715
+ where((:x.sql_number > 1) & (:y.sql_number > 2))
2716
+ end
2717
+ q.class.should == @d.class
2718
+ q.sql.should == "SELECT * FROM zzz WHERE ((x > 1) AND (y > 2))"
2719
+
2720
+ q = @d.from(:zzz).query do
2721
+ where :x => 33
2722
+ end
2723
+ q.class.should == @d.class
2724
+ q.sql.should == "SELECT * FROM zzz WHERE (x = 33)"
2725
+ end
2726
+
2727
+ deprec_specify "should support #group_by and #having" do
2728
+ q = @d.query do
2729
+ from :abc
2730
+ group_by :id
2731
+ having(:x.sql_number >= 2)
2732
+ end
2733
+ q.class.should == @d.class
2734
+ q.sql.should == "SELECT * FROM abc GROUP BY id HAVING (x >= 2)"
2735
+ end
2736
+
2737
+ deprec_specify "should support #order, #order_by" do
2738
+ q = @d.query do
2739
+ from :xyz
2740
+ order_by :stamp
2741
+ end
2742
+ q.class.should == @d.class
2743
+ q.sql.should == "SELECT * FROM xyz ORDER BY stamp"
2744
+ end
2745
+
2746
+ deprec_specify "should raise on non-chainable method calls" do
2747
+ proc {@d.query {first_source}}.should raise_error(Sequel::Error)
2748
+ end
2749
+
2750
+ deprec_specify "should raise on each, insert, update, delete" do
2751
+ proc {@d.query {each}}.should raise_error(Sequel::Error)
2752
+ proc {@d.query {insert(:x => 1)}}.should raise_error(Sequel::Error)
2753
+ proc {@d.query {update(:x => 1)}}.should raise_error(Sequel::Error)
2754
+ proc {@d.query {delete}}.should raise_error(Sequel::Error)
2755
+ end
2756
+ end
2757
+
2758
+ context "Dataset" do
2759
+ setup do
2760
+ @d = Sequel::Dataset.new(nil).from(:x)
2761
+ end
2762
+
2763
+ specify "should support self-changing select!" do
2764
+ @d.select!(:y)
2765
+ @d.sql.should == "SELECT y FROM x"
2766
+ end
2767
+
2768
+ specify "should support self-changing from!" do
2769
+ @d.from!(:y)
2770
+ @d.sql.should == "SELECT * FROM y"
2771
+ end
2772
+
2773
+ specify "should support self-changing order!" do
2774
+ @d.order!(:y)
2775
+ @d.sql.should == "SELECT * FROM x ORDER BY y"
2776
+ end
2777
+
2778
+ specify "should support self-changing filter!" do
2779
+ @d.filter!(:y => 1)
2780
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1)"
2781
+ end
2782
+
2783
+ specify "should support self-changing filter! with block" do
2784
+ @d.filter!{:y.sql_number < 2}
2785
+ @d.sql.should == "SELECT * FROM x WHERE (y < 2)"
2786
+ end
2787
+
2788
+ specify "should raise for ! methods that don't return a dataset" do
2789
+ proc {@d.opts!}.should raise_error(NameError)
2790
+ end
2791
+
2792
+ specify "should raise for missing methods" do
2793
+ proc {@d.xuyz}.should raise_error(NameError)
2794
+ proc {@d.xyz!}.should raise_error(NameError)
2795
+ proc {@d.xyz?}.should raise_error(NameError)
2796
+ end
2797
+
2798
+ specify "should support chaining of bang methods" do
2799
+ @d.order!(:y)
2800
+ @d.filter!(:y => 1)
2801
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2802
+ end
2803
+ end
2804
+
2805
+ context "Dataset#transform" do
2806
+ setup do
2807
+ @c = Class.new(Sequel::Dataset) do
2808
+ attr_accessor :raw
2809
+ attr_accessor :sql
2810
+
2811
+ def fetch_rows(sql, &block)
2812
+ block[@raw]
2813
+ end
2814
+
2815
+ def insert(v)
2816
+ @sql = insert_sql(v)
2817
+ end
2818
+
2819
+ def update(v)
2820
+ @sql = update_sql(v)
2821
+ end
2822
+ end
2823
+
2824
+ @ds = @c.new(nil).from(:items)
2825
+ @ds.transform(:x => [
2826
+ proc {|v| Marshal.load(v)},
2827
+ proc {|v| Marshal.dump(v)}
2828
+ ])
2829
+ end
2830
+
2831
+ specify "should change the dataset to transform values loaded from the database" do
2832
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2833
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2834
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2835
+ @ds.all.should == [{:x => [1, 2, 3], :y => 'hello'}]
2836
+ end
2837
+
2838
+ specify "should change the dataset to transform values saved to the database" do
2839
+ @ds.insert(:x => :toast)
2840
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2841
+
2842
+ @ds.insert(:y => 'butter')
2843
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2844
+
2845
+ @ds.update(:x => ['dream'])
2846
+ @ds.sql.should == "UPDATE items SET x = '#{Marshal.dump(['dream'])}'"
2847
+ end
2848
+
2849
+ specify "should be transferred to cloned datasets" do
2850
+ @ds2 = @ds.filter(:a => 1)
2851
+
2852
+ @ds2.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2853
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2854
+
2855
+ @ds2.insert(:x => :toast)
2856
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2857
+ end
2858
+
2859
+ specify "should work correctly together with set_row_proc" do
2860
+ @ds.row_proc = proc{|r| r[:z] = r[:x] * 2; r}
2861
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2862
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2863
+
2864
+ f = nil
2865
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2866
+ @ds.naked.each{|r| f = r}
2867
+ f.should == {:x => "wow", :y => 'hello'}
2868
+ end
2869
+
2870
+ specify "should leave the supplied values intact" do
2871
+ h = {:x => :toast}
2872
+ @ds.insert(h)
2873
+ h.should == {:x => :toast}
2874
+ end
2875
+ end
2876
+
2877
+ context "Dataset#transform" do
2878
+ setup do
2879
+ @c = Class.new(Sequel::Dataset) do
2880
+ attr_accessor :raw
2881
+ attr_accessor :sql
2882
+
2883
+ def fetch_rows(sql, &block)
2884
+ block[@raw]
2885
+ end
2886
+
2887
+ def insert(v)
2888
+ @sql = insert_sql(v)
2889
+ end
2890
+
2891
+ def update(v)
2892
+ @sql = update_sql(v)
2893
+ end
2894
+ end
2895
+
2896
+ @ds = @c.new(nil).from(:items)
2897
+ end
2898
+
2899
+ specify "should raise Sequel::Error for invalid transformations" do
2900
+ proc {@ds.transform(:x => 'mau')}.should raise_error(Sequel::Error::InvalidTransform)
2901
+ proc {@ds.transform(:x => :mau)}.should raise_error(Sequel::Error::InvalidTransform)
2902
+ proc {@ds.transform(:x => [])}.should raise_error(Sequel::Error::InvalidTransform)
2903
+ proc {@ds.transform(:x => ['mau'])}.should raise_error(Sequel::Error::InvalidTransform)
2904
+ proc {@ds.transform(:x => [proc {|v|}, proc {|v|}])}.should_not raise_error(Sequel::Error::InvalidTransform)
2905
+ end
2906
+
2907
+ specify "should support stock YAML transformation" do
2908
+ @ds.transform(:x => :yaml)
2909
+
2910
+ @ds.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
2911
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2912
+
2913
+ @ds.insert(:x => :toast)
2914
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}')"
2915
+ @ds.insert(:y => 'butter')
2916
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2917
+ @ds.update(:x => ['dream'])
2918
+ @ds.sql.should == "UPDATE items SET x = '#{['dream'].to_yaml}'"
2919
+
2920
+ @ds2 = @ds.filter(:a => 1)
2921
+ @ds2.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
2922
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2923
+ @ds2.insert(:x => :toast)
2924
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}')"
2925
+
2926
+ @ds.row_proc = proc{|r| r[:z] = r[:x] * 2; r}
2927
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
2928
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2929
+ f = nil
2930
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
2931
+ @ds.naked.each{|r| f = r}
2932
+ f.should == {:x => "wow", :y => 'hello'}
2933
+ end
2934
+
2935
+ specify "should support stock Marshal transformation with Base64 encoding" do
2936
+ @ds.transform(:x => :marshal)
2937
+
2938
+ @ds.raw = {:x => [Marshal.dump([1, 2, 3])].pack('m'), :y => 'hello'}
2939
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2940
+
2941
+ @ds.insert(:x => :toast)
2942
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{[Marshal.dump(:toast)].pack('m')}')"
2943
+ @ds.insert(:y => 'butter')
2944
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2945
+ @ds.update(:x => ['dream'])
2946
+ @ds.sql.should == "UPDATE items SET x = '#{[Marshal.dump(['dream'])].pack('m')}'"
2947
+
2948
+ @ds2 = @ds.filter(:a => 1)
2949
+ @ds2.raw = {:x => [Marshal.dump([1, 2, 3])].pack('m'), :y => 'hello'}
2950
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2951
+ @ds2.insert(:x => :toast)
2952
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{[Marshal.dump(:toast)].pack('m')}')"
2953
+
2954
+ @ds.row_proc = proc{|r| r[:z] = r[:x] * 2; r}
2955
+ @ds.raw = {:x => [Marshal.dump("wow")].pack('m'), :y => 'hello'}
2956
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2957
+ f = nil
2958
+ @ds.raw = {:x => [Marshal.dump("wow")].pack('m'), :y => 'hello'}
2959
+ @ds.naked.each{|r| f = r}
2960
+ f.should == {:x => "wow", :y => 'hello'}
2961
+ end
2962
+
2963
+ specify "should support loading of Marshalled values without Base64 encoding" do
2964
+ @ds.transform(:x => :marshal)
2965
+
2966
+ @ds.raw = {:x => Marshal.dump([1,2,3]), :y => nil}
2967
+ @ds.first.should == {:x => [1,2,3], :y => nil}
2968
+ end
2969
+
2970
+ specify "should return self" do
2971
+ @ds.transform(:x => :marshal).should be(@ds)
2972
+ end
2973
+ end
2974
+
2975
+ context "A dataset with a transform" do
2976
+ setup do
2977
+ @ds = Sequel::Dataset.new(nil).from(:items)
2978
+ @ds.transform(:x => :marshal)
2979
+ end
2980
+
2981
+ specify "should automatically transform hash filters" do
2982
+ @ds.filter(:y => 2).sql.should == 'SELECT * FROM items WHERE (y = 2)'
2983
+
2984
+ @ds.filter(:x => 2).sql.should == "SELECT * FROM items WHERE (x = '#{[Marshal.dump(2)].pack('m')}')"
2985
+ end
2986
+ end
2987
+
2988
+ context "Dataset#to_csv" do
2989
+ setup do
2990
+ @c = Class.new(Sequel::Dataset) do
2991
+ attr_accessor :data
2992
+ attr_accessor :columns
2993
+
2994
+ def fetch_rows(sql, &block)
2995
+ @data.each(&block)
2996
+ end
2997
+
2998
+ # naked should return self here because to_csv wants a naked result set.
2999
+ def naked
3000
+ self
3001
+ end
3002
+ end
3003
+
3004
+ @ds = @c.new(nil).from(:items)
3005
+ @ds.columns = [:a, :b, :c]
3006
+ @ds.data = [ {:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9} ]
3007
+ end
3008
+
3009
+ specify "should format a CSV representation of the records" do
3010
+ @ds.to_csv.should ==
3011
+ "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
3012
+ end
3013
+
3014
+ specify "should exclude column titles if so specified" do
3015
+ @ds.to_csv(false).should ==
3016
+ "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
3017
+ end
3018
+ end
3019
+
3020
+ context "Dataset#create_view" do
3021
+ setup do
3022
+ @dbc = Class.new(Sequel::Database) do
3023
+ attr_reader :sqls
3024
+
3025
+ def execute(sql, opts={})
3026
+ @sqls ||= []
3027
+ @sqls << sql
3028
+ end
3029
+ end
3030
+ @db = @dbc.new
3031
+
3032
+ @ds = @db[:items].order(:abc).filter(:category => 'ruby')
3033
+ end
3034
+
3035
+ deprec_specify "should create a view with the dataset's sql" do
3036
+ @ds.create_view(:xyz)
3037
+ @db.sqls.should == ["CREATE VIEW xyz AS #{@ds.sql}"]
3038
+ end
3039
+ end
3040
+
3041
+ context "Dataset#create_or_replace_view" do
3042
+ setup do
3043
+ @dbc = Class.new(Sequel::Database) do
3044
+ attr_reader :sqls
3045
+
3046
+ def execute(sql, opts={})
3047
+ @sqls ||= []
3048
+ @sqls << sql
3049
+ end
3050
+ end
3051
+ @db = @dbc.new
3052
+
3053
+ @ds = @db[:items].order(:abc).filter(:category => 'ruby')
3054
+ end
3055
+
3056
+ deprec_specify "should create a view with the dataset's sql" do
3057
+ @ds.create_or_replace_view(:xyz)
3058
+ @db.sqls.should == ["CREATE OR REPLACE VIEW xyz AS #{@ds.sql}"]
3059
+ end
3060
+ end
3061
+
3062
+ context "Dataset#update_sql" do
3063
+ setup do
3064
+ @ds = Sequel::Dataset.new(nil).from(:items)
3065
+ end
3066
+
3067
+ specify "should accept strings" do
3068
+ @ds.update_sql("a = b").should == "UPDATE items SET a = b"
3069
+ end
3070
+
3071
+ specify "should accept hash with string keys" do
3072
+ @ds.update_sql('c' => 'd').should == "UPDATE items SET c = 'd'"
3073
+ end
3074
+
3075
+ specify "should accept array subscript references" do
3076
+ @ds.update_sql((:day.sql_subscript(1)) => 'd').should == "UPDATE items SET day[1] = 'd'"
3077
+ end
3078
+ end
3079
+
3080
+ context "Dataset#insert_sql" do
3081
+ setup do
3082
+ @ds = Sequel::Dataset.new(nil).from(:items)
3083
+ end
3084
+
3085
+ specify "should accept hash with symbol keys" do
3086
+ @ds.insert_sql(:c => 'd').should == "INSERT INTO items (c) VALUES ('d')"
3087
+ end
3088
+
3089
+ specify "should accept hash with string keys" do
3090
+ @ds.insert_sql('c' => 'd').should == "INSERT INTO items (c) VALUES ('d')"
3091
+ end
3092
+
3093
+ specify "should accept array subscript references" do
3094
+ @ds.insert_sql((:day.sql_subscript(1)) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
3095
+ end
3096
+ end
3097
+
3098
+ class DummyMummyDataset < Sequel::Dataset
3099
+ def first
3100
+ raise if @opts[:from] == [:a]
3101
+ true
3102
+ end
3103
+ end
3104
+
3105
+ class DummyMummyDatabase < Sequel::Database
3106
+ attr_reader :sqls
3107
+
3108
+ def execute(sql)
3109
+ @sqls ||= []
3110
+ @sqls << sql
3111
+ end
3112
+
3113
+ def transaction; yield; end
3114
+
3115
+ def dataset
3116
+ DummyMummyDataset.new(self)
3117
+ end
3118
+ end
3119
+
3120
+ context "Dataset#table_exists?" do
3121
+ setup do
3122
+ @db = DummyMummyDatabase.new
3123
+ @db.instance_variable_set(:@schemas, {:a=>[]})
3124
+ @db2 = DummyMummyDatabase.new
3125
+ end
3126
+
3127
+ specify "should use the database schema if available" do
3128
+ @db[:a].table_exists?.should be_true
3129
+ end
3130
+
3131
+ specify "should otherwise try to select the first record from the table's dataset" do
3132
+ @db2[:a].table_exists?.should be_false
3133
+ @db2[:b].table_exists?.should be_true
3134
+ end
3135
+
3136
+ specify "should raise Sequel::Error if dataset references more than one table" do
3137
+ proc {@db.from(:a, :b).table_exists?}.should raise_error(Sequel::Error)
3138
+ end
3139
+
3140
+ specify "should raise Sequel::Error if dataset is from a subquery" do
3141
+ proc {@db.from(@db[:a]).table_exists?}.should raise_error(Sequel::Error)
3142
+ end
3143
+
3144
+ specify "should raise Sequel::Error if dataset has fixed sql" do
3145
+ proc {@db['select * from blah'].table_exists?}.should raise_error(Sequel::Error)
3146
+ end
3147
+ end
3148
+
3149
+ context "Dataset#inspect" do
3150
+ setup do
3151
+ @ds = Sequel::Dataset.new(nil).from(:blah)
3152
+ end
3153
+
3154
+ specify "should include the class name and the corresponding SQL statement" do
3155
+ @ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
3156
+ end
3157
+ end
3158
+
3159
+ context "Dataset#all" do
3160
+ setup do
3161
+ @c = Class.new(Sequel::Dataset) do
3162
+ def fetch_rows(sql, &block)
3163
+ block.call({:x => 1, :y => 2})
3164
+ block.call({:x => 3, :y => 4})
3165
+ block.call(sql)
3166
+ end
3167
+ end
3168
+ @dataset = @c.new(nil).from(:items)
3169
+ end
3170
+
3171
+ specify "should return an array with all records" do
3172
+ @dataset.all.should == [
3173
+ {:x => 1, :y => 2},
3174
+ {:x => 3, :y => 4},
3175
+ "SELECT * FROM items"
3176
+ ]
3177
+ end
3178
+
3179
+ deprec_specify "should accept options and pass them to #each" do
3180
+ @dataset.all(:limit => 33).should == [
3181
+ {:x => 1, :y => 2},
3182
+ {:x => 3, :y => 4},
3183
+ "SELECT * FROM items LIMIT 33"
3184
+ ]
3185
+ end
3186
+
3187
+ specify "should iterate over the array if a block is given" do
3188
+ a = []
3189
+
3190
+ @dataset.all do |r|
3191
+ a << (r.is_a?(Hash) ? r[:x] : r)
3192
+ end
3193
+
3194
+ a.should == [1, 3, "SELECT * FROM items"]
3195
+ end
3196
+ end
3197
+
3198
+ context "Dataset#grep" do
3199
+ setup do
3200
+ @ds = Sequel::Dataset.new(nil).from(:posts)
3201
+ end
3202
+
3203
+ specify "should format a SQL filter correctly" do
3204
+ @ds.grep(:title, 'ruby').sql.should ==
3205
+ "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3206
+ end
3207
+
3208
+ specify "should support multiple columns" do
3209
+ @ds.grep([:title, :body], 'ruby').sql.should ==
3210
+ "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3211
+ end
3212
+
3213
+ specify "should support multiple search terms" do
3214
+ @ds.grep(:title, ['abc', 'def']).sql.should ==
3215
+ "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')))"
3216
+ end
3217
+
3218
+ specify "should support multiple columns and search terms" do
3219
+ @ds.grep([:title, :body], ['abc', 'def']).sql.should ==
3220
+ "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')) OR ((body LIKE 'abc') OR (body LIKE 'def')))"
3221
+ end
3222
+
3223
+ specify "should support regexps though the database may not support it" do
3224
+ @ds.grep(:title, /ruby/).sql.should ==
3225
+ "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3226
+
3227
+ @ds.grep(:title, [/^ruby/, 'ruby']).sql.should ==
3228
+ "SELECT * FROM posts WHERE (((title ~ '^ruby') OR (title LIKE 'ruby')))"
3229
+ end
3230
+
3231
+ specify "should support searching against other columns" do
3232
+ @ds.grep(:title, :body).sql.should ==
3233
+ "SELECT * FROM posts WHERE ((title LIKE body))"
3234
+ end
3235
+ end
3236
+
3237
+ context "Sequel.use_parse_tree" do
3238
+ deprec_specify "be false" do
3239
+ Sequel.use_parse_tree.should == false
3240
+ end
3241
+ end
3242
+
3243
+ context "Sequel.use_parse_tree=" do
3244
+ deprec_specify "raise an error if true" do
3245
+ proc{Sequel.use_parse_tree = true}.should raise_error(Sequel::Error)
3246
+ end
3247
+
3248
+ deprec_specify "do nothing if false" do
3249
+ proc{Sequel.use_parse_tree = false}.should_not raise_error
3250
+ end
3251
+ end
3252
+
3253
+ context "Dataset.dataset_classes" do
3254
+ deprec_specify "should be an array of dataset subclasses" do
3255
+ ds_class = Class.new(Sequel::Dataset)
3256
+ Sequel::Dataset.dataset_classes.should be_a_kind_of(Array)
3257
+ Sequel::Dataset.dataset_classes.should include(ds_class)
3258
+ end
3259
+ end
3260
+
3261
+ context "Dataset default #fetch_rows, #insert, #update, and #delete, #execute" do
3262
+ setup do
3263
+ @db = Sequel::Database.new
3264
+ @ds = @db[:items]
3265
+ end
3266
+
3267
+ specify "#fetch_rows should raise a NotImplementedError" do
3268
+ proc{@ds.fetch_rows(''){}}.should raise_error(NotImplementedError)
3269
+ end
3270
+
3271
+ specify "#delete should execute delete SQL" do
3272
+ @db.should_receive(:execute).once.with('DELETE FROM items', :server=>:default)
3273
+ @ds.delete
3274
+ end
3275
+
3276
+ specify "#insert should execute insert SQL" do
3277
+ @db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3278
+ @ds.insert([])
3279
+ end
3280
+
3281
+ specify "#update should execute update SQL" do
3282
+ @db.should_receive(:execute).once.with('UPDATE items SET number = 1', :server=>:default)
3283
+ @ds.update(:number=>1)
3284
+ end
3285
+
3286
+ specify "#execute should execute the SQL on the database" do
3287
+ @db.should_receive(:execute).once.with('SELECT 1', :server=>:read_only)
3288
+ @ds.send(:execute, 'SELECT 1')
3289
+ end
3290
+ end
3291
+
3292
+ context "Dataset prepared statements and bound variables " do
3293
+ setup do
3294
+ @db = Sequel::Database.new
3295
+ @db.send :metaattr_accessor, :sqls
3296
+ @db.sqls = []
3297
+ def @db.execute(sql, opts={})
3298
+ @sqls << sql
3299
+ end
3300
+ def @db.dataset
3301
+ ds = super()
3302
+ def ds.fetch_rows(sql, &block)
3303
+ execute(sql)
3304
+ end
3305
+ ds
3306
+ end
3307
+ @ds = @db[:items]
3308
+ end
3309
+
3310
+ specify "#call should take a type and bind hash and interpolate it" do
3311
+ @ds.filter(:num=>:$n).call(:select, :n=>1)
3312
+ @ds.filter(:num=>:$n).call(:first, :n=>1)
3313
+ @ds.filter(:num=>:$n).call(:delete, :n=>1)
3314
+ @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3315
+ @ds.call(:insert, {:n=>1}, :num=>:$n)
3316
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3317
+ 'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3318
+ 'DELETE FROM items WHERE (num = 1)',
3319
+ 'UPDATE items SET num = 2 WHERE (num = 1)',
3320
+ 'INSERT INTO items (num) VALUES (1)']
3321
+ end
3322
+
3323
+ specify "#prepare should take a type and name and store it in the database for later use with call" do
3324
+ pss = []
3325
+ pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
3326
+ pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
3327
+ pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3328
+ pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3329
+ pss << @ds.prepare(:insert, :in, :num=>:$n)
3330
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
3331
+ [:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3332
+ @db.call(:sn, :n=>1)
3333
+ @db.call(:fn, :n=>1)
3334
+ @db.call(:dn, :n=>1)
3335
+ @db.call(:un, :n=>1, :n2=>2)
3336
+ @db.call(:in, :n=>1)
3337
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3338
+ 'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3339
+ 'DELETE FROM items WHERE (num = 1)',
3340
+ 'UPDATE items SET num = 2 WHERE (num = 1)',
3341
+ 'INSERT INTO items (num) VALUES (1)']
3342
+ end
3343
+
3344
+ specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
3345
+ @ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
3346
+ '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3347
+ end
3348
+
3349
+ specify "should handle literal strings" do
3350
+ @ds.filter("num = ?", :$n).call(:select, :n=>1)
3351
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3352
+ end
3353
+
3354
+ specify "should handle datasets using static sql and placeholders" do
3355
+ @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3356
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3357
+ end
3358
+
3359
+ specify "should handle subselects" do
3360
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>:$n)).filter(:$c).call(:select, :n=>1, :b=>0, :c=>2)
3361
+ @db.sqls.should == ['SELECT * FROM items WHERE ((0 AND (num IN (SELECT num FROM items WHERE (num = 1)))) AND 2)']
3362
+ end
3363
+
3364
+ specify "should handle subselects in subselects" do
3365
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>@ds.select(:num).filter(:num=>:$n))).call(:select, :n=>1, :b=>0)
3366
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num IN (SELECT num FROM items WHERE (num = 1))))))']
3367
+ end
3368
+
3369
+ specify "should handle subselects with literal strings" do
3370
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter("num = ?", :$n)).call(:select, :n=>1, :b=>0)
3371
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3372
+ end
3373
+
3374
+ specify "should handle subselects with static sql and placeholders" do
3375
+ @ds.filter(:$b).filter(:num=>@db["SELECT num FROM items WHERE (num = ?)", :$n]).call(:select, :n=>1, :b=>0)
3376
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3377
+ end
3378
+ end
3379
+
3380
+ context Sequel::Dataset::UnnumberedArgumentMapper do
3381
+ setup do
3382
+ @db = Sequel::Database.new
3383
+ @db.send :metaattr_accessor, :sqls
3384
+ @db.sqls = []
3385
+ def @db.execute(sql, opts={})
3386
+ @sqls << [sql, *opts[:arguments]]
3387
+ end
3388
+ @ds = @db[:items].filter(:num=>:$n)
3389
+ def @ds.fetch_rows(sql, &block)
3390
+ execute(sql)
3391
+ end
3392
+ def @ds.execute(sql, opts={}, &block)
3393
+ @db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
3394
+ end
3395
+ @ps = @ds.prepare(:select, :sn)
3396
+ @ps.extend(Sequel::Dataset::UnnumberedArgumentMapper)
3397
+ end
3398
+
3399
+ specify "#inspect should show the actual SQL submitted to the database" do
3400
+ @ps.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3401
+ end
3402
+
3403
+ specify "should submitted the SQL to the database with placeholders and bind variables" do
3404
+ @ps.call(:n=>1)
3405
+ @db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1]]
3406
+ end
3407
+ end
3408
+
3409
+ context "Sequel::Dataset#server" do
3410
+ specify "should set the server to use for the dataset" do
3411
+ @db = Sequel::Database.new
3412
+ @ds = @db[:items].server(:s)
3413
+ sqls = []
3414
+ @db.meta_def(:execute) do |sql, opts|
3415
+ sqls << [sql, opts[:server]]
3416
+ end
3417
+ def @ds.fetch_rows(sql, &block)
3418
+ execute(sql)
3419
+ end
3420
+ @ds.all
3421
+ @ds.server(:i).insert(:a=>1)
3422
+ @ds.server(:d).delete
3423
+ @ds.server(:u).update(:a=>:a+1)
3424
+ sqls.should == [['SELECT * FROM items', :s],
3425
+ ['INSERT INTO items (a) VALUES (1)', :i],
3426
+ ['DELETE FROM items', :d],
3427
+ ['UPDATE items SET a = (a + 1)', :u]]
3428
+ end
3429
+ end
3430
+
3431
+ context "Sequel::Dataset #set_defaults" do
3432
+ before do
3433
+ @ds = Sequel::Dataset.new(nil).from(:items).set_defaults(:x=>1)
3434
+ end
3435
+
3436
+ specify "should set the default values for inserts" do
3437
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3438
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (2)"
3439
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3440
+ @ds.set_defaults(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3441
+ @ds.set_defaults(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (2)"
3442
+ end
3443
+
3444
+ specify "should set the default values for updates" do
3445
+ @ds.update_sql.should == "UPDATE items SET x = 1"
3446
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 2"
3447
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3448
+ @ds.set_defaults(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3449
+ @ds.set_defaults(:x=>2).update_sql.should == "UPDATE items SET x = 2"
3450
+ end
3451
+ end
3452
+
3453
+ context "Sequel::Dataset #set_overrides" do
3454
+ before do
3455
+ @ds = Sequel::Dataset.new(nil).from(:items).set_overrides(:x=>1)
3456
+ end
3457
+
3458
+ specify "should override the given values for inserts" do
3459
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3460
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (1)"
3461
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3462
+ @ds.set_overrides(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3463
+ @ds.set_overrides(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3464
+ end
3465
+
3466
+ specify "should override the given values for updates" do
3467
+ @ds.update_sql.should == "UPDATE items SET x = 1"
3468
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 1"
3469
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3470
+ @ds.set_overrides(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3471
+ @ds.set_overrides(:x=>2).update_sql.should == "UPDATE items SET x = 1"
3472
+ end
3473
+ end
3474
+
3475
+ context "Sequel::Dataset#each" do
3476
+ before do
3477
+ @ds = Sequel::Dataset.new(nil).from(:items)
3478
+ def @ds.fetch_rows(sql)
3479
+ @columns = /count/i.match(sql) ? [:count] : [:a]
3480
+ yield({@columns.first=>sql})
3481
+ end
3482
+ end
3483
+
3484
+ deprec_specify "should not set the columns if passing an option that modifies them" do
3485
+ @ds.each(:select=>[:count]){}
3486
+ @ds.columns.should == [:a]
3487
+ @ds.each(:from=>[:count]){}
3488
+ @ds.columns.should == [:a]
3489
+ @ds.each(:join=>[Sequel::SQL::JoinClause.new(:natural, :count)]){}
3490
+ @ds.columns.should == [:a]
3491
+ @ds.each(:sql=>'SELECT COUNT'){}
3492
+ @ds.columns.should == [:a]
3493
+ end
3494
+
3495
+ deprec_specify "should have the correct columns inside the block regardless" do
3496
+ @ds.each(:select=>[:count]) do |x|
3497
+ x[:count].should == 'SELECT count FROM items'
3498
+ @ds.columns.should == [:count]
3499
+ end
3500
+ @ds.each(:from=>[:count]) do |x|
3501
+ x[:count].should == 'SELECT * FROM count'
3502
+ @ds.columns.should == [:count]
3503
+ end
3504
+ @ds.each(:join=>[Sequel::SQL::JoinClause.new(:natural, :count)]) do |x|
3505
+ x[:count].should == 'SELECT * FROM items NATURAL JOIN count'
3506
+ @ds.columns.should == [:count]
3507
+ end
3508
+ @ds.each(:sql=>'SELECT COUNT') do |x|
3509
+ x[:count].should == 'SELECT COUNT'
3510
+ @ds.columns.should == [:count]
3511
+ end
3512
+ end
3513
+ end