sequel 3.10.0 → 3.11.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 (87) hide show
  1. data/CHANGELOG +68 -0
  2. data/COPYING +1 -1
  3. data/README.rdoc +87 -27
  4. data/bin/sequel +2 -4
  5. data/doc/association_basics.rdoc +1383 -0
  6. data/doc/dataset_basics.rdoc +106 -0
  7. data/doc/opening_databases.rdoc +45 -16
  8. data/doc/querying.rdoc +210 -0
  9. data/doc/release_notes/3.11.0.txt +254 -0
  10. data/doc/virtual_rows.rdoc +217 -31
  11. data/lib/sequel/adapters/ado.rb +28 -12
  12. data/lib/sequel/adapters/ado/mssql.rb +33 -1
  13. data/lib/sequel/adapters/amalgalite.rb +13 -8
  14. data/lib/sequel/adapters/db2.rb +1 -2
  15. data/lib/sequel/adapters/dbi.rb +7 -4
  16. data/lib/sequel/adapters/do.rb +14 -15
  17. data/lib/sequel/adapters/do/postgres.rb +4 -5
  18. data/lib/sequel/adapters/do/sqlite.rb +9 -0
  19. data/lib/sequel/adapters/firebird.rb +5 -10
  20. data/lib/sequel/adapters/informix.rb +2 -4
  21. data/lib/sequel/adapters/jdbc.rb +111 -49
  22. data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
  24. data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
  27. data/lib/sequel/adapters/mysql.rb +14 -5
  28. data/lib/sequel/adapters/odbc.rb +2 -4
  29. data/lib/sequel/adapters/odbc/mssql.rb +2 -4
  30. data/lib/sequel/adapters/openbase.rb +1 -2
  31. data/lib/sequel/adapters/oracle.rb +4 -8
  32. data/lib/sequel/adapters/postgres.rb +4 -11
  33. data/lib/sequel/adapters/shared/mssql.rb +22 -9
  34. data/lib/sequel/adapters/shared/mysql.rb +33 -30
  35. data/lib/sequel/adapters/shared/oracle.rb +0 -5
  36. data/lib/sequel/adapters/shared/postgres.rb +13 -11
  37. data/lib/sequel/adapters/shared/sqlite.rb +56 -10
  38. data/lib/sequel/adapters/sqlite.rb +16 -9
  39. data/lib/sequel/connection_pool.rb +6 -1
  40. data/lib/sequel/connection_pool/single.rb +1 -0
  41. data/lib/sequel/core.rb +6 -1
  42. data/lib/sequel/database.rb +52 -23
  43. data/lib/sequel/database/schema_generator.rb +6 -0
  44. data/lib/sequel/database/schema_methods.rb +5 -5
  45. data/lib/sequel/database/schema_sql.rb +1 -1
  46. data/lib/sequel/dataset.rb +4 -190
  47. data/lib/sequel/dataset/actions.rb +323 -1
  48. data/lib/sequel/dataset/features.rb +18 -2
  49. data/lib/sequel/dataset/graph.rb +7 -0
  50. data/lib/sequel/dataset/misc.rb +119 -0
  51. data/lib/sequel/dataset/mutation.rb +64 -0
  52. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  53. data/lib/sequel/dataset/query.rb +272 -6
  54. data/lib/sequel/dataset/sql.rb +186 -394
  55. data/lib/sequel/model.rb +4 -2
  56. data/lib/sequel/model/associations.rb +31 -14
  57. data/lib/sequel/model/base.rb +32 -13
  58. data/lib/sequel/model/exceptions.rb +8 -4
  59. data/lib/sequel/model/plugins.rb +3 -13
  60. data/lib/sequel/plugins/active_model.rb +26 -7
  61. data/lib/sequel/plugins/instance_filters.rb +98 -0
  62. data/lib/sequel/plugins/many_through_many.rb +1 -1
  63. data/lib/sequel/plugins/optimistic_locking.rb +25 -9
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +26 -0
  66. data/spec/adapters/mysql_spec.rb +33 -4
  67. data/spec/adapters/postgres_spec.rb +24 -1
  68. data/spec/adapters/spec_helper.rb +6 -0
  69. data/spec/adapters/sqlite_spec.rb +28 -0
  70. data/spec/core/connection_pool_spec.rb +17 -5
  71. data/spec/core/database_spec.rb +101 -1
  72. data/spec/core/dataset_spec.rb +42 -4
  73. data/spec/core/schema_spec.rb +13 -0
  74. data/spec/extensions/active_model_spec.rb +34 -11
  75. data/spec/extensions/caching_spec.rb +2 -0
  76. data/spec/extensions/instance_filters_spec.rb +55 -0
  77. data/spec/extensions/spec_helper.rb +2 -0
  78. data/spec/integration/dataset_test.rb +12 -1
  79. data/spec/integration/model_test.rb +12 -0
  80. data/spec/integration/plugin_test.rb +61 -1
  81. data/spec/integration/schema_test.rb +14 -3
  82. data/spec/model/base_spec.rb +27 -0
  83. data/spec/model/plugins_spec.rb +0 -22
  84. data/spec/model/record_spec.rb +32 -1
  85. data/spec/model/spec_helper.rb +2 -0
  86. metadata +14 -3
  87. data/lib/sequel/dataset/convenience.rb +0 -326
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  MAJOR = 3
3
- MINOR = 10
3
+ MINOR = 11
4
4
  TINY = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join('.')
@@ -375,3 +375,29 @@ context "MSSSQL::Dataset#into" do
375
375
  @db.drop_table(:new)
376
376
  end
377
377
  end
378
+
379
+ context "A MSSQL database" do
380
+ before do
381
+ @db = MSSQL_DB
382
+ end
383
+ after do
384
+ @db.drop_table(:a)
385
+ end
386
+
387
+ specify "should handle many existing types for set_column_allow_null" do
388
+ @db.create_table!(:a){column :a, 'integer'}
389
+ @db.alter_table(:a){set_column_allow_null :a, false}
390
+ @db.create_table!(:a){column :a, 'decimal(24, 2)'}
391
+ @db.alter_table(:a){set_column_allow_null :a, false}
392
+ @db.schema(:a).first.last[:column_size].should == 24
393
+ @db.schema(:a).first.last[:scale].should == 2
394
+ @db.create_table!(:a){column :a, 'decimal(10)'}
395
+ @db.schema(:a).first.last[:column_size].should == 10
396
+ @db.schema(:a).first.last[:scale].should == 0
397
+ @db.alter_table(:a){set_column_allow_null :a, false}
398
+ @db.create_table!(:a){column :a, 'nchar(2)'}
399
+ @db.alter_table(:a){set_column_allow_null :a, false}
400
+ s = @db.schema(:a).first.last
401
+ (s[:max_chars] || s[:column_size]).should == 2
402
+ end
403
+ end
@@ -107,14 +107,20 @@ if MYSQL_DB.class.adapter_scheme == :mysql
107
107
  @db.schema(:booltest, :reload=>true).should == [[:b, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(1)"}, ], [:i, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}, ]]
108
108
  end
109
109
 
110
- specify "should return tinyints as bools when set" do
110
+ specify "should return tinyint(1)s as bools and tinyint(4)s as integers when set" do
111
+ Sequel::MySQL.convert_tinyint_to_bool = true
111
112
  @ds.delete
112
113
  @ds << {:b=>true, :i=>10}
113
- @ds.all.should == [{:b=>true, :i=>true}]
114
+ @ds.all.should == [{:b=>true, :i=>10}]
114
115
  @ds.delete
115
116
  @ds << {:b=>false, :i=>0}
116
- @ds.all.should == [{:b=>false, :i=>false}]
117
-
117
+ @ds.all.should == [{:b=>false, :i=>0}]
118
+ @ds.delete
119
+ @ds << {:b=>true, :i=>1}
120
+ @ds.all.should == [{:b=>true, :i=>1}]
121
+ end
122
+
123
+ specify "should return all tinyints as integers when unset" do
118
124
  Sequel::MySQL.convert_tinyint_to_bool = false
119
125
  @ds.delete
120
126
  @ds << {:b=>true, :i=>10}
@@ -244,6 +250,29 @@ context "MySQL datasets" do
244
250
  end
245
251
  end
246
252
 
253
+ describe "Dataset#distinct" do
254
+ before do
255
+ @db = MYSQL_DB
256
+ @db.create_table!(:a) do
257
+ Integer :a
258
+ Integer :b
259
+ end
260
+ @ds = @db[:a]
261
+ end
262
+ after do
263
+ @db.drop_table(:a)
264
+ end
265
+
266
+ it "#distinct with arguments should return results distinct on those arguments" do
267
+ @ds.insert(20, 10)
268
+ @ds.insert(30, 10)
269
+ @ds.order(:b, :a).distinct.map(:a).should == [20, 30]
270
+ @ds.order(:b, :a.desc).distinct.map(:a).should == [30, 20]
271
+ # MySQL doesn't respect orders when using the nonstandard GROUP BY
272
+ [[20], [30]].should include(@ds.order(:b, :a).distinct(:b).map(:a))
273
+ end
274
+ end
275
+
247
276
  context "MySQL join expressions" do
248
277
  before do
249
278
  @ds = MYSQL_DB[:nodes]
@@ -148,6 +148,29 @@ context "A PostgreSQL dataset" do
148
148
  end
149
149
  end
150
150
 
151
+ describe "Dataset#distinct" do
152
+ before do
153
+ @db = POSTGRES_DB
154
+ @db.create_table!(:a) do
155
+ Integer :a
156
+ Integer :b
157
+ end
158
+ @ds = @db[:a]
159
+ end
160
+ after do
161
+ @db.drop_table(:a)
162
+ end
163
+
164
+ it "#distinct with arguments should return results distinct on those arguments" do
165
+ @ds.insert(20, 10)
166
+ @ds.insert(30, 10)
167
+ @ds.order(:b, :a).distinct.map(:a).should == [20, 30]
168
+ @ds.order(:b, :a.desc).distinct.map(:a).should == [30, 20]
169
+ @ds.order(:b, :a).distinct(:b).map(:a).should == [20]
170
+ @ds.order(:b, :a.desc).distinct(:b).map(:a).should == [30]
171
+ end
172
+ end
173
+
151
174
  if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
152
175
  describe "Dataset#for_update support" do
153
176
  before do
@@ -194,7 +217,7 @@ if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
194
217
  c = @ds.for_share.filter(:id=>1).first
195
218
  end
196
219
  end
197
- sleep 0.05
220
+ sleep 0.1
198
221
  @ds.filter(:id=>1).update(:name=>'Jim')
199
222
  c.should == {:id=>1, :number=>20, :name=>nil}
200
223
  end
@@ -10,6 +10,12 @@ begin
10
10
  rescue LoadError
11
11
  end
12
12
 
13
+ class Sequel::Database
14
+ def log_duration(duration, message)
15
+ log_info(message)
16
+ end
17
+ end
18
+
13
19
  class Spec::Example::ExampleGroup
14
20
  def log
15
21
  begin
@@ -36,6 +36,34 @@ context "An SQLite database" do
36
36
  end
37
37
  end
38
38
 
39
+ specify "should provide the SQLite version as an integer" do
40
+ @db.sqlite_version.should be_a_kind_of(Integer)
41
+ end
42
+
43
+ specify "should support setting and getting the foreign_keys pragma" do
44
+ (@db.sqlite_version >= 30619 ? [true, false] : [nil]).should include(@db.foreign_keys)
45
+ @db.foreign_keys = true
46
+ @db.foreign_keys = false
47
+ end
48
+
49
+ if SQLITE_DB.sqlite_version >= 30619
50
+ specify "should enforce foreign key integrity if foreign_keys pragma is set" do
51
+ @db.foreign_keys = true
52
+ @db.create_table!(:fk){primary_key :id; foreign_key :parent_id, :fk}
53
+ @db[:fk].insert(1, nil)
54
+ @db[:fk].insert(2, 1)
55
+ @db[:fk].insert(3, 3)
56
+ proc{@db[:fk].insert(4, 5)}.should raise_error(Sequel::Error)
57
+ end
58
+ end
59
+
60
+ specify "should not enforce foreign key integrity if foreign_keys pragma is unset" do
61
+ @db.foreign_keys = false
62
+ @db.create_table!(:fk){primary_key :id; foreign_key :parent_id, :fk}
63
+ @db[:fk].insert(1, 2)
64
+ @db[:fk].all.should == [{:id=>1, :parent_id=>2}]
65
+ end
66
+
39
67
  specify "should provide a list of existing tables" do
40
68
  @db.drop_table(:testing) rescue nil
41
69
  @db.tables.should be_a_kind_of(Array)
@@ -240,8 +240,8 @@ shared_examples_for "A threaded connection pool" do
240
240
  threads = []
241
241
  stop = nil
242
242
 
243
- 5.times {|i| threads << Thread.new {@pool.hold {|c| cc[i] = c; while !stop;sleep 0.01;end}}; sleep 0.01}
244
- sleep 0.02
243
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| cc[i] = c; while !stop;sleep 0.02;end}}; sleep 0.02}
244
+ sleep 0.04
245
245
  threads.each {|t| t.should be_alive}
246
246
  cc.size.should == 5
247
247
  @invoked_count.should == 5
@@ -253,16 +253,16 @@ shared_examples_for "A threaded connection pool" do
253
253
  @pool.allocated.should == h
254
254
 
255
255
  threads[0].raise "your'e dead"
256
- sleep 0.01
256
+ sleep 0.02
257
257
  threads[3].raise "your'e dead too"
258
258
 
259
- sleep 0.01
259
+ sleep 0.02
260
260
 
261
261
  @pool.available_connections.should == [1, 4]
262
262
  @pool.allocated.should == {threads[1]=>2, threads[2]=>3, threads[4]=>5}
263
263
 
264
264
  stop = true
265
- sleep 0.02
265
+ sleep 0.04
266
266
 
267
267
  @pool.available_connections.size.should == 5
268
268
  @pool.allocated.should be_empty
@@ -734,6 +734,12 @@ context "A single threaded pool with multiple servers" do
734
734
  end
735
735
 
736
736
  shared_examples_for "All connection pools classes" do
737
+ specify "should not raise an error when disconnecting twice" do
738
+ c = @class.new({}){123}
739
+ proc{c.disconnect}.should_not raise_error
740
+ proc{c.disconnect}.should_not raise_error
741
+ end
742
+
737
743
  specify "should yield a connection created by the initialize block to hold" do
738
744
  x = nil
739
745
  @class.new({}){123}.hold{|c| x = c}
@@ -746,6 +752,12 @@ shared_examples_for "All connection pools classes" do
746
752
  x.should == [:default, :default]
747
753
  end
748
754
 
755
+ specify "should have respect an :after_connect proc that is called with each newly created connection" do
756
+ x = nil
757
+ @class.new(:after_connect=>proc{|c| x = [c, c]}){|c| 123}.hold{}
758
+ x.should == [123, 123]
759
+ end
760
+
749
761
  specify "should raise a DatabaseConnectionError if the connection raises an exception" do
750
762
  proc{@class.new({}){|c| raise Exception}.hold{}}.should raise_error(Sequel::DatabaseConnectionError)
751
763
  end
@@ -209,6 +209,88 @@ context "Database#connect" do
209
209
  end
210
210
  end
211
211
 
212
+ context "Database#log_info" do
213
+ before do
214
+ @o = Object.new
215
+ def @o.logs; @logs || []; end
216
+ def @o.method_missing(*args); (@logs ||= []) << args; end
217
+ @db = Sequel::Database.new(:logger=>@o)
218
+ end
219
+
220
+ specify "should log message at info level to all loggers" do
221
+ @db.log_info('blah')
222
+ @o.logs.should == [[:info, 'blah']]
223
+ end
224
+
225
+ specify "should log message with args at info level to all loggers" do
226
+ @db.log_info('blah', [1, 2])
227
+ @o.logs.should == [[:info, 'blah; [1, 2]']]
228
+ end
229
+ end
230
+
231
+ context "Database#log_yield" do
232
+ before do
233
+ @o = Object.new
234
+ def @o.logs; @logs || []; end
235
+ def @o.warn(*args); (@logs ||= []) << [:warn] + args; end
236
+ def @o.method_missing(*args); (@logs ||= []) << args; end
237
+ @db = Sequel::Database.new(:logger=>@o)
238
+ end
239
+
240
+ specify "should yield to the passed block" do
241
+ a = nil
242
+ @db.log_yield('blah'){a = 1}
243
+ a.should == 1
244
+ end
245
+
246
+ specify "should raise an exception if a block is not passed" do
247
+ proc{@db.log_yield('blah')}.should raise_error
248
+ end
249
+
250
+ specify "should log message with duration at info level to all loggers" do
251
+ @db.log_yield('blah'){}
252
+ @o.logs.length.should == 1
253
+ @o.logs.first.length.should == 2
254
+ @o.logs.first.first.should == :info
255
+ @o.logs.first.last.should =~ /\A\(\d\.\d{6}s\) blah\z/
256
+ end
257
+
258
+ specify "should log message with duration at warn level if duration greater than log_warn_duration" do
259
+ @db.log_warn_duration = 0
260
+ @db.log_yield('blah'){}
261
+ @o.logs.length.should == 1
262
+ @o.logs.first.length.should == 2
263
+ @o.logs.first.first.should == :warn
264
+ @o.logs.first.last.should =~ /\A\(\d\.\d{6}s\) blah\z/
265
+ end
266
+
267
+ specify "should log message with duration at info level if duration less than log_warn_duration" do
268
+ @db.log_warn_duration = 1000
269
+ @db.log_yield('blah'){}
270
+ @o.logs.length.should == 1
271
+ @o.logs.first.length.should == 2
272
+ @o.logs.first.first.should == :info
273
+ @o.logs.first.last.should =~ /\A\(\d\.\d{6}s\) blah\z/
274
+ end
275
+
276
+ specify "should log message at error level if block raises an error" do
277
+ @db.log_warn_duration = 0
278
+ proc{@db.log_yield('blah'){raise Sequel::Error, 'adsf'}}.should raise_error
279
+ @o.logs.length.should == 1
280
+ @o.logs.first.length.should == 2
281
+ @o.logs.first.first.should == :error
282
+ @o.logs.first.last.should =~ /\ASequel::Error: adsf: blah\z/
283
+ end
284
+
285
+ specify "should include args with message if args passed" do
286
+ @db.log_yield('blah', [1, 2]){}
287
+ @o.logs.length.should == 1
288
+ @o.logs.first.length.should == 2
289
+ @o.logs.first.first.should == :info
290
+ @o.logs.first.last.should =~ /\A\(\d\.\d{6}s\) blah; \[1, 2\]\z/
291
+ end
292
+ end
293
+
212
294
  context "Database#uri" do
213
295
  before do
214
296
  @c = Class.new(Sequel::Database) do
@@ -342,7 +424,7 @@ context "Database#test_connection" do
342
424
  @db = Sequel::Database.new{@test = rand(100)}
343
425
  end
344
426
 
345
- specify "should call pool#hold" do
427
+ specify "should pool#hold" do
346
428
  @db.test_connection
347
429
  @test.should_not be_nil
348
430
  end
@@ -350,6 +432,10 @@ context "Database#test_connection" do
350
432
  specify "should return true if successful" do
351
433
  @db.test_connection.should be_true
352
434
  end
435
+
436
+ specify "should raise an error if the attempting to connect raises an error" do
437
+ proc{Sequel::Database.new{raise Sequel::Error, 'blah'}.test_connection}.should raise_error(Sequel::Error)
438
+ end
353
439
  end
354
440
 
355
441
  class DummyDataset < Sequel::Dataset
@@ -875,6 +961,20 @@ context "A Database adapter with a scheme" do
875
961
  c.opts[:database].should == 'd[b]'
876
962
  c.opts[:host].should == 'domain\\instance'
877
963
  end
964
+
965
+ specify "should test the connection if test parameter is truthy" do
966
+ proc{Sequel.connect 'ccc:///d%5bb%5d?test=t'}.should raise_error(Sequel::DatabaseConnectionError)
967
+ proc{Sequel.connect 'ccc:///d%5bb%5d?test=1'}.should raise_error(Sequel::DatabaseConnectionError)
968
+ proc{Sequel.connect 'ccc:///d%5bb%5d', :test=>true}.should raise_error(Sequel::DatabaseConnectionError)
969
+ proc{Sequel.connect 'ccc:///d%5bb%5d', :test=>'t'}.should raise_error(Sequel::DatabaseConnectionError)
970
+ end
971
+
972
+ specify "should not test the connection if test parameter is not truthy" do
973
+ proc{Sequel.connect 'ccc:///d%5bb%5d?test=f'}.should_not raise_error
974
+ proc{Sequel.connect 'ccc:///d%5bb%5d?test=0'}.should_not raise_error
975
+ proc{Sequel.connect 'ccc:///d%5bb%5d', :test=>false}.should_not raise_error
976
+ proc{Sequel.connect 'ccc:///d%5bb%5d', :test=>'f'}.should_not raise_error
977
+ end
878
978
  end
879
979
 
880
980
  context "Sequel::Database.connect" do
@@ -322,6 +322,16 @@ context "Dataset#where" do
322
322
  @d3 = @dataset.where("a = 1")
323
323
  end
324
324
 
325
+ specify "should just clone if given an empty argument" do
326
+ @dataset.where({}).sql.should == @dataset.sql
327
+ @dataset.where([]).sql.should == @dataset.sql
328
+ @dataset.where('').sql.should == @dataset.sql
329
+
330
+ @dataset.filter({}).sql.should == @dataset.sql
331
+ @dataset.filter([]).sql.should == @dataset.sql
332
+ @dataset.filter('').sql.should == @dataset.sql
333
+ end
334
+
325
335
  specify "should work with hashes" do
326
336
  @dataset.where(:name => 'xyz', :price => 342).select_sql.
327
337
  should match(/WHERE \(\(name = 'xyz'\) AND \(price = 342\)\)|WHERE \(\(price = 342\) AND \(name = 'xyz'\)\)/)
@@ -735,6 +745,12 @@ context "Dataset#having" do
735
745
  @columns = "region, sum(population), avg(gdp)"
736
746
  end
737
747
 
748
+ specify "should just clone if given an empty argument" do
749
+ @dataset.having({}).sql.should == @dataset.sql
750
+ @dataset.having([]).sql.should == @dataset.sql
751
+ @dataset.having('').sql.should == @dataset.sql
752
+ end
753
+
738
754
  specify "should affect select statements" do
739
755
  @d1.select_sql.should ==
740
756
  "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
@@ -1151,6 +1167,28 @@ context "Dataset#select_more" do
1151
1167
  end
1152
1168
  end
1153
1169
 
1170
+ context "Dataset#select_append" do
1171
+ before do
1172
+ @d = Sequel::Dataset.new(nil).from(:test)
1173
+ end
1174
+
1175
+ specify "should select * in addition to columns if no columns selected" do
1176
+ @d.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
1177
+ @d.select_all.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
1178
+ @d.select(:blah).select_all.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
1179
+ end
1180
+
1181
+ specify "should add to the currently selected columns" do
1182
+ @d.select(:a).select_append(:b).sql.should == 'SELECT a, b FROM test'
1183
+ @d.select(:a.*).select_append(:b.*).sql.should == 'SELECT a.*, b.* FROM test'
1184
+ end
1185
+
1186
+ specify "should accept a block that yields a virtual row" do
1187
+ @d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1188
+ @d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1189
+ end
1190
+ end
1191
+
1154
1192
  context "Dataset#order" do
1155
1193
  before do
1156
1194
  @dataset = Sequel::Dataset.new(nil).from(:test)
@@ -1484,13 +1522,13 @@ context "Dataset#distinct" do
1484
1522
  @dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
1485
1523
  end
1486
1524
 
1487
- specify "should raise an error if columns given and distinct on not supported" do
1488
- @dataset.meta_def(:supports_distinct_on?){false}
1525
+ specify "should raise an error if columns given and DISTINCT ON is not supported" do
1489
1526
  proc{@dataset.distinct}.should_not raise_error
1490
1527
  proc{@dataset.distinct(:a)}.should raise_error(Sequel::InvalidOperation)
1491
1528
  end
1492
1529
 
1493
- specify "should accept an expression list" do
1530
+ specify "should use DISTINCT ON if columns are given and DISTINCT ON is supported" do
1531
+ @dataset.meta_def(:supports_distinct_on?){true}
1494
1532
  @dataset.distinct(:a, :b).sql.should == 'SELECT DISTINCT ON (a, b) name FROM test'
1495
1533
  @dataset.distinct(:stamp.cast(:integer), :node_id=>nil).sql.should == 'SELECT DISTINCT ON (CAST(stamp AS integer), (node_id IS NULL)) name FROM test'
1496
1534
  end
@@ -2541,7 +2579,7 @@ context "Dataset#columns" do
2541
2579
  end
2542
2580
 
2543
2581
  specify "should ignore any filters, orders, or DISTINCT clauses" do
2544
- @dataset.filter!(:b=>100).order!(:b).distinct!(:b)
2582
+ @dataset.filter!(:b=>100).order!(:b).distinct!
2545
2583
  @dataset.columns = nil
2546
2584
  @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2547
2585
  end