sequel 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGELOG +108 -0
  2. data/README.rdoc +25 -14
  3. data/Rakefile +20 -1
  4. data/doc/advanced_associations.rdoc +61 -64
  5. data/doc/cheat_sheet.rdoc +16 -7
  6. data/doc/opening_databases.rdoc +3 -3
  7. data/doc/prepared_statements.rdoc +1 -1
  8. data/doc/reflection.rdoc +2 -1
  9. data/doc/release_notes/3.6.0.txt +366 -0
  10. data/doc/schema.rdoc +19 -14
  11. data/lib/sequel/adapters/amalgalite.rb +5 -27
  12. data/lib/sequel/adapters/jdbc.rb +13 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
  15. data/lib/sequel/adapters/mysql.rb +4 -3
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +87 -28
  18. data/lib/sequel/adapters/shared/mssql.rb +47 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +12 -31
  20. data/lib/sequel/adapters/shared/postgres.rb +15 -12
  21. data/lib/sequel/adapters/shared/sqlite.rb +18 -0
  22. data/lib/sequel/adapters/sqlite.rb +1 -16
  23. data/lib/sequel/connection_pool.rb +1 -1
  24. data/lib/sequel/core.rb +1 -1
  25. data/lib/sequel/database.rb +1 -1
  26. data/lib/sequel/database/schema_generator.rb +2 -0
  27. data/lib/sequel/database/schema_sql.rb +1 -1
  28. data/lib/sequel/dataset.rb +5 -179
  29. data/lib/sequel/dataset/actions.rb +123 -0
  30. data/lib/sequel/dataset/convenience.rb +18 -10
  31. data/lib/sequel/dataset/features.rb +65 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +29 -23
  33. data/lib/sequel/dataset/query.rb +429 -0
  34. data/lib/sequel/dataset/sql.rb +67 -435
  35. data/lib/sequel/model/associations.rb +77 -13
  36. data/lib/sequel/model/base.rb +30 -8
  37. data/lib/sequel/model/errors.rb +4 -4
  38. data/lib/sequel/plugins/caching.rb +38 -15
  39. data/lib/sequel/plugins/force_encoding.rb +18 -7
  40. data/lib/sequel/plugins/hook_class_methods.rb +4 -0
  41. data/lib/sequel/plugins/many_through_many.rb +1 -1
  42. data/lib/sequel/plugins/nested_attributes.rb +40 -11
  43. data/lib/sequel/plugins/serialization.rb +17 -3
  44. data/lib/sequel/plugins/validation_helpers.rb +65 -18
  45. data/lib/sequel/sql.rb +23 -1
  46. data/lib/sequel/version.rb +1 -1
  47. data/spec/adapters/mssql_spec.rb +96 -10
  48. data/spec/adapters/mysql_spec.rb +19 -0
  49. data/spec/adapters/postgres_spec.rb +65 -2
  50. data/spec/adapters/sqlite_spec.rb +10 -0
  51. data/spec/core/core_sql_spec.rb +9 -0
  52. data/spec/core/database_spec.rb +8 -4
  53. data/spec/core/dataset_spec.rb +122 -29
  54. data/spec/core/expression_filters_spec.rb +17 -0
  55. data/spec/extensions/caching_spec.rb +43 -3
  56. data/spec/extensions/force_encoding_spec.rb +43 -1
  57. data/spec/extensions/nested_attributes_spec.rb +55 -2
  58. data/spec/extensions/validation_helpers_spec.rb +71 -0
  59. data/spec/integration/associations_test.rb +281 -0
  60. data/spec/integration/dataset_test.rb +383 -9
  61. data/spec/integration/eager_loader_test.rb +0 -65
  62. data/spec/integration/model_test.rb +110 -0
  63. data/spec/integration/plugin_test.rb +306 -0
  64. data/spec/integration/prepared_statement_test.rb +32 -0
  65. data/spec/integration/schema_test.rb +8 -3
  66. data/spec/integration/spec_helper.rb +1 -25
  67. data/spec/model/association_reflection_spec.rb +38 -0
  68. data/spec/model/associations_spec.rb +184 -8
  69. data/spec/model/eager_loading_spec.rb +23 -0
  70. data/spec/model/model_spec.rb +8 -0
  71. data/spec/model/record_spec.rb +84 -1
  72. metadata +9 -2
@@ -787,6 +787,25 @@ context "MySQL::Dataset#replace" do
787
787
  MYSQL_DB.drop_table(:items)
788
788
  end
789
789
 
790
+ specify "should use default values if they exist" do
791
+ MYSQL_DB.alter_table(:items){set_column_default :id, 1; set_column_default :value, 2}
792
+ @d.replace
793
+ @d.all.should == [{:id=>1, :value=>2}]
794
+ @d.replace([])
795
+ @d.all.should == [{:id=>1, :value=>2}]
796
+ @d.replace({})
797
+ @d.all.should == [{:id=>1, :value=>2}]
798
+ end
799
+
800
+ specify "should use support arrays, datasets, and multiple values" do
801
+ @d.replace([1, 2])
802
+ @d.all.should == [{:id=>1, :value=>2}]
803
+ @d.replace(1, 2)
804
+ @d.all.should == [{:id=>1, :value=>2}]
805
+ @d.replace(@d)
806
+ @d.all.should == [{:id=>1, :value=>2}]
807
+ end
808
+
790
809
  specify "should create a record if the condition is not met" do
791
810
  @d.replace(:id => 111, :value => 333)
792
811
  @d.all.should == [{:id => 111, :value => 333}]
@@ -129,6 +129,24 @@ context "A PostgreSQL dataset" do
129
129
  @d.filter(:name => /bc/).count.should == 2
130
130
  @d.filter(:name => /^bc/).count.should == 1
131
131
  end
132
+
133
+ specify "should support for_share and for_update" do
134
+ @d.for_share.all.should == []
135
+ @d.for_update.all.should == []
136
+ end
137
+
138
+ specify "#lock should lock tables and yield if a block is given" do
139
+ @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
140
+ end
141
+
142
+ specify "#lock should lock table if inside a transaction" do
143
+ POSTGRES_DB.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
144
+ end
145
+
146
+ specify "#lock should return nil" do
147
+ @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}.should == nil
148
+ POSTGRES_DB.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
149
+ end
132
150
  end
133
151
 
134
152
  context "A PostgreSQL dataset with a timestamp field" do
@@ -189,6 +207,11 @@ context "A PostgreSQL database" do
189
207
 
190
208
  @db[:test2].first[:xyz].should == 57
191
209
  end
210
+
211
+ specify "#locks should be a dataset returning database locks " do
212
+ @db.locks.should be_a_kind_of(Sequel::Dataset)
213
+ @db.locks.all.should be_a_kind_of(Array)
214
+ end
192
215
  end
193
216
 
194
217
  context "A PostgreSQL database" do
@@ -217,7 +240,6 @@ context "A PostgreSQL database" do
217
240
  @db.reset_primary_key_sequence(:posts).should == nil
218
241
  end
219
242
 
220
-
221
243
  specify "should support opclass specification" do
222
244
  @db.create_table(:posts){text :title; text :body; integer :user_id; index(:user_id, :opclass => :int4_ops, :type => :btree)}
223
245
  @db.sqls.should == [
@@ -287,6 +309,11 @@ context "A PostgreSQL database" do
287
309
  "CREATE INDEX posts_title_index ON posts (title) WHERE (title = '5')"
288
310
  ]
289
311
  end
312
+
313
+ specify "should support renaming tables" do
314
+ @db.create_table!(:posts1){primary_key :a}
315
+ @db.rename_table(:posts1, :posts)
316
+ end
290
317
  end
291
318
 
292
319
  context "Postgres::Dataset#import" do
@@ -393,7 +420,7 @@ context "Postgres::Dataset#insert" do
393
420
  @ds.disable_insert_returning.insert_select(:value=>10).should == nil
394
421
  end
395
422
 
396
- specify "should have insert_select insert the record and return the inserted record if server_version < 80200" do
423
+ specify "should have insert_select insert the record and return the inserted record if server_version >= 80200" do
397
424
  @ds.meta_def(:server_version){80201}
398
425
  h = @ds.insert_select(:value=>10)
399
426
  h[:value].should == 10
@@ -635,3 +662,39 @@ context "Postgres::Database functions, languages, and triggers" do
635
662
  @d.drop_trigger(:test, :identity, :if_exists=>true, :cascade=>true)
636
663
  end
637
664
  end
665
+
666
+ if POSTGRES_DB.class.adapter_scheme == :postgres
667
+ context "Postgres::Dataset #use_cursor" do
668
+ before(:all) do
669
+ @db = POSTGRES_DB
670
+ @db.create_table!(:test_cursor){Integer :x}
671
+ @db.sqls.clear
672
+ @ds = @db[:test_cursor]
673
+ @db.transaction{1001.times{|i| @ds.insert(i)}}
674
+ end
675
+ after(:all) do
676
+ @db.drop_table(:test) rescue nil
677
+ end
678
+
679
+ specify "should return the same results as the non-cursor use" do
680
+ @ds.all.should == @ds.use_cursor.all
681
+ end
682
+
683
+ specify "should respect the :rows_per_fetch option" do
684
+ @db.sqls.clear
685
+ @ds.use_cursor.all
686
+ @db.sqls.length.should == 6
687
+ @db.sqls.clear
688
+ @ds.use_cursor(:rows_per_fetch=>100).all
689
+ @db.sqls.length.should == 15
690
+ end
691
+
692
+ specify "should handle returning inside block" do
693
+ def @ds.check_return
694
+ use_cursor.each{|r| return}
695
+ end
696
+ @ds.check_return
697
+ @ds.all.should == @ds.use_cursor.all
698
+ end
699
+ end
700
+ end
@@ -241,6 +241,16 @@ context "SQLite dataset" do
241
241
  SQLITE_DB[:test].select(:name, :value).order(:value).to_a.should == \
242
242
  @d.select(:name, :value).order(:value).to_a
243
243
  end
244
+
245
+ specify "should support #explain" do
246
+ SQLITE_DB[:test].explain.should be_a_kind_of(Array)
247
+ end
248
+
249
+ specify "should have #explain work when identifier_output_method is modified" do
250
+ ds = SQLITE_DB[:test]
251
+ ds.identifier_output_method = :upcase
252
+ ds.explain.should be_a_kind_of(Array)
253
+ end
244
254
  end
245
255
 
246
256
  context "A SQLite database" do
@@ -82,6 +82,15 @@ context "String#lit" do
82
82
  ds.quote_identifiers = true
83
83
  ds.literal(a).should == 'DISTINCT "a"'
84
84
  end
85
+
86
+ specify "should handle named placeholders if given a single argument hash" do
87
+ a = 'DISTINCT :b'.lit(:b=>:a)
88
+ a.should be_a_kind_of(Sequel::SQL::PlaceholderLiteralString)
89
+ ds = MockDatabase.new.dataset
90
+ ds.literal(a).should == 'DISTINCT a'
91
+ ds.quote_identifiers = true
92
+ ds.literal(a).should == 'DISTINCT "a"'
93
+ end
85
94
  end
86
95
 
87
96
  context "String#to_sequel_blob" do
@@ -1027,12 +1027,16 @@ context "Database#fetch" do
1027
1027
  @db.fetch('select * from xyz where x = ? and y = ?', 15, 'abc') {|r| sql = r[:sql]}
1028
1028
  sql.should == "select * from xyz where x = 15 and y = 'abc'"
1029
1029
 
1030
- # and Aman Gupta's example
1031
- @db.fetch('select name from table where name = ? or id in ?',
1032
- 'aman', [3,4,7]) {|r| sql = r[:sql]}
1030
+ @db.fetch('select name from table where name = ? or id in ?', 'aman', [3,4,7]) {|r| sql = r[:sql]}
1033
1031
  sql.should == "select name from table where name = 'aman' or id in (3, 4, 7)"
1034
1032
  end
1035
1033
 
1034
+ specify "should format the given sql with named arguments" do
1035
+ sql = nil
1036
+ @db.fetch('select * from xyz where x = :x and y = :y', :x=>15, :y=>'abc') {|r| sql = r[:sql]}
1037
+ sql.should == "select * from xyz where x = 15 and y = 'abc'"
1038
+ end
1039
+
1036
1040
  specify "should return the dataset if no block is given" do
1037
1041
  @db.fetch('select * from xyz').should be_a_kind_of(Sequel::Dataset)
1038
1042
 
@@ -1065,7 +1069,7 @@ context "Database#[]" do
1065
1069
  ds.opts[:from].should == [:items]
1066
1070
  end
1067
1071
 
1068
- specify "should return an enumerator when a string is given" do
1072
+ specify "should return a dataset when a string is given" do
1069
1073
  c = Class.new(Sequel::Dataset) do
1070
1074
  def fetch_rows(sql); yield({:sql => sql}); end
1071
1075
  end
@@ -300,15 +300,44 @@ context "Dataset#where" do
300
300
  should match(/WHERE \(\(name = 'xyz'\) AND \(price = 342\)\)|WHERE \(\(price = 342\) AND \(name = 'xyz'\)\)/)
301
301
  end
302
302
 
303
- specify "should work with arrays (ala ActiveRecord)" do
303
+ specify "should work with a string with placeholders and arguments for those placeholders" do
304
304
  @dataset.where('price < ? AND id in ?', 100, [1, 2, 3]).select_sql.should ==
305
305
  "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
306
306
  end
307
307
 
308
+ specify "should not modify passed array with placeholders" do
309
+ a = ['price < ? AND id in ?', 100, 1, 2, 3]
310
+ b = a.dup
311
+ @dataset.where(a)
312
+ b.should == a
313
+ end
314
+
308
315
  specify "should work with strings (custom SQL expressions)" do
309
316
  @dataset.where('(a = 1 AND b = 2)').select_sql.should ==
310
317
  "SELECT * FROM test WHERE ((a = 1 AND b = 2))"
311
318
  end
319
+
320
+ specify "should work with a string with named placeholders and a hash of placeholder value arguments" do
321
+ @dataset.where('price < :price AND id in :ids', :price=>100, :ids=>[1, 2, 3]).select_sql.should ==
322
+ "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
323
+ end
324
+
325
+ specify "should not modify passed array with named placeholders" do
326
+ a = ['price < :price AND id in :ids', {:price=>100}]
327
+ b = a.dup
328
+ @dataset.where(a)
329
+ b.should == a
330
+ end
331
+
332
+ specify "should not replace named placeholders that don't existin in the hash" do
333
+ @dataset.where('price < :price AND id in :ids', :price=>100).select_sql.should ==
334
+ "SELECT * FROM test WHERE (price < 100 AND id in :ids)"
335
+ end
336
+
337
+ specify "should handle partial names" do
338
+ @dataset.where('price < :price AND id = :p', :p=>2, :price=>100).select_sql.should ==
339
+ "SELECT * FROM test WHERE (price < 100 AND id = 2)"
340
+ end
312
341
 
313
342
  specify "should affect select, delete and update statements" do
314
343
  @d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
@@ -626,10 +655,6 @@ context "Dataset#having" do
626
655
  @columns = "region, sum(population), avg(gdp)"
627
656
  end
628
657
 
629
- specify "should raise if the dataset is not grouped" do
630
- proc {@dataset.having('avg(gdp) > 10')}.should raise_error(Sequel::InvalidOperation)
631
- end
632
-
633
658
  specify "should affect select statements" do
634
659
  @d1.select_sql.should ==
635
660
  "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
@@ -768,8 +793,20 @@ context "Dataset#literal" do
768
793
  @dataset.literal(:items__name).should == "items.name"
769
794
  end
770
795
 
771
- specify "should raise an error for unsupported types" do
772
- proc {@dataset.literal({})}.should raise_error
796
+ specify "should call sql_literal with dataset on type if not natively supported and the object responds to it" do
797
+ @a = Class.new do
798
+ def sql_literal(ds)
799
+ "called #{ds.blah}"
800
+ end
801
+ end
802
+ def @dataset.blah
803
+ "ds"
804
+ end
805
+ @dataset.literal(@a.new).should == "called ds"
806
+ end
807
+
808
+ specify "should raise an error for unsupported types with no sql_literal method" do
809
+ proc {@dataset.literal(Object.new)}.should raise_error
773
810
  end
774
811
 
775
812
  specify "should literalize datasets as subqueries" do
@@ -1106,10 +1143,18 @@ context "Dataset#with_sql" do
1106
1143
  @dataset = Sequel::Dataset.new(nil).from(:test)
1107
1144
  end
1108
1145
 
1109
- specify "should remove use static sql" do
1146
+ specify "should use static sql" do
1110
1147
  @dataset.with_sql('SELECT 1 FROM test').sql.should == 'SELECT 1 FROM test'
1111
1148
  end
1112
1149
 
1150
+ specify "should work with placeholders" do
1151
+ @dataset.with_sql('SELECT ? FROM test', 1).sql.should == 'SELECT 1 FROM test'
1152
+ end
1153
+
1154
+ specify "should work with named placeholders" do
1155
+ @dataset.with_sql('SELECT :x FROM test', :x=>1).sql.should == 'SELECT 1 FROM test'
1156
+ end
1157
+
1113
1158
  specify "should keep row_proc" do
1114
1159
  @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1115
1160
  end
@@ -1440,6 +1485,11 @@ context "Dataset#group_and_count" do
1440
1485
  @ds.group_and_count(:a, :b).sql.should ==
1441
1486
  "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1442
1487
  end
1488
+
1489
+ specify "should format column aliases in the select clause but not in the group clause" do
1490
+ @ds.group_and_count(:name___n).sql.should ==
1491
+ "SELECT name AS n, count(*) AS count FROM test GROUP BY name ORDER BY count"
1492
+ end
1443
1493
  end
1444
1494
 
1445
1495
  context "Dataset#empty?" do
@@ -1511,38 +1561,54 @@ context "Dataset#join_table" do
1511
1561
  'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
1512
1562
  end
1513
1563
 
1514
- specify "should support left outer joins" do
1515
- @d.join_table(:left_outer, :categories, :category_id=>:id).sql.should ==
1516
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1564
+ specify "should support arbitrary join types" do
1565
+ @d.join_table(:magic, :categories, :category_id=>:id).sql.should ==
1566
+ 'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1567
+ end
1517
1568
 
1569
+ specify "should support many join methods" do
1518
1570
  @d.left_outer_join(:categories, :category_id=>:id).sql.should ==
1519
1571
  'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1520
- end
1521
-
1522
- specify "should support right outer joins" do
1523
- @d.join_table(:right_outer, :categories, :category_id=>:id).sql.should ==
1524
- 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1525
-
1526
1572
  @d.right_outer_join(:categories, :category_id=>:id).sql.should ==
1527
1573
  'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1528
- end
1529
-
1530
- specify "should support full outer joins" do
1531
- @d.join_table(:full_outer, :categories, :category_id=>:id).sql.should ==
1532
- 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1533
-
1534
1574
  @d.full_outer_join(:categories, :category_id=>:id).sql.should ==
1535
1575
  'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1536
- end
1537
-
1538
- specify "should support inner joins" do
1539
- @d.join_table(:inner, :categories, :category_id=>:id).sql.should ==
1540
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1541
-
1542
1576
  @d.inner_join(:categories, :category_id=>:id).sql.should ==
1543
1577
  'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1578
+ @d.left_join(:categories, :category_id=>:id).sql.should ==
1579
+ 'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1580
+ @d.right_join(:categories, :category_id=>:id).sql.should ==
1581
+ 'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1582
+ @d.full_join(:categories, :category_id=>:id).sql.should ==
1583
+ 'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1584
+ @d.natural_join(:categories).sql.should ==
1585
+ 'SELECT * FROM "items" NATURAL JOIN "categories"'
1586
+ @d.natural_left_join(:categories).sql.should ==
1587
+ 'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
1588
+ @d.natural_right_join(:categories).sql.should ==
1589
+ 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
1590
+ @d.natural_full_join(:categories).sql.should ==
1591
+ 'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
1592
+ @d.cross_join(:categories).sql.should ==
1593
+ 'SELECT * FROM "items" CROSS JOIN "categories"'
1544
1594
  end
1545
1595
 
1596
+ specify "should raise an error if additional arguments are provided to join methods that don't take conditions" do
1597
+ proc{@d.natural_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1598
+ proc{@d.natural_left_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1599
+ proc{@d.natural_right_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1600
+ proc{@d.natural_full_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1601
+ proc{@d.cross_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1602
+ end
1603
+
1604
+ specify "should raise an error if blocks are provided to join methods that don't pass them" do
1605
+ proc{@d.natural_join(:categories){}}.should raise_error(Sequel::Error)
1606
+ proc{@d.natural_left_join(:categories){}}.should raise_error(Sequel::Error)
1607
+ proc{@d.natural_right_join(:categories){}}.should raise_error(Sequel::Error)
1608
+ proc{@d.natural_full_join(:categories){}}.should raise_error(Sequel::Error)
1609
+ proc{@d.cross_join(:categories){}}.should raise_error(Sequel::Error)
1610
+ end
1611
+
1546
1612
  specify "should default to a plain join if nil is used for the type" do
1547
1613
  @d.join_table(nil, :categories, :category_id=>:id).sql.should ==
1548
1614
  'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
@@ -1680,6 +1746,11 @@ context "Dataset#join_table" do
1680
1746
  'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
1681
1747
  end
1682
1748
 
1749
+ specify "should emulate JOIN USING (poorly) if the dataset doesn't support it" do
1750
+ @d.meta_def(:supports_join_using?){false}
1751
+ @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
1752
+ end
1753
+
1683
1754
  specify "should raise an error if using an array of symbols with a block" do
1684
1755
  proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
1685
1756
  end
@@ -1852,6 +1923,14 @@ context "Dataset aggregate methods" do
1852
1923
  specify "should accept qualified columns" do
1853
1924
  @d.avg(:test__bc).should == 'SELECT avg(test.bc) FROM test LIMIT 1'
1854
1925
  end
1926
+
1927
+ specify "should use a subselect for the same conditions as count" do
1928
+ d = @d.order(:a).limit(5)
1929
+ d.avg(:a).should == 'SELECT avg(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
1930
+ d.sum(:a).should == 'SELECT sum(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
1931
+ d.min(:a).should == 'SELECT min(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
1932
+ d.max(:a).should == 'SELECT max(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
1933
+ end
1855
1934
  end
1856
1935
 
1857
1936
  context "Dataset#range" do
@@ -1880,6 +1959,11 @@ context "Dataset#range" do
1880
1959
  specify "should return a range object" do
1881
1960
  @d.range(:tryme).should == (1..10)
1882
1961
  end
1962
+
1963
+ specify "should use a subselect for the same conditions as count" do
1964
+ @d.order(:stamp).limit(5).range(:stamp).should == (1..10)
1965
+ @d.last_sql.should == 'SELECT min(stamp) AS v1, max(stamp) AS v2 FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
1966
+ end
1883
1967
  end
1884
1968
 
1885
1969
  context "Dataset#interval" do
@@ -1908,6 +1992,11 @@ context "Dataset#interval" do
1908
1992
  specify "should return an integer" do
1909
1993
  @d.interval(:tryme).should == 1234
1910
1994
  end
1995
+
1996
+ specify "should use a subselect for the same conditions as count" do
1997
+ @d.order(:stamp).limit(5).interval(:stamp).should == 1234
1998
+ @d.last_sql.should == 'SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
1999
+ end
1911
2000
  end
1912
2001
 
1913
2002
  context "Dataset #first and #last" do
@@ -3080,6 +3169,10 @@ context "Sequel::Dataset#qualify_to_first_source" do
3080
3169
  @ds.filter('? > ?', :a, 1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a > 1)'
3081
3170
  end
3082
3171
 
3172
+ specify "should handle SQL::PlaceholderLiteralStrings with named placeholders" do
3173
+ @ds.filter(':a > :b', :a=>:c, :b=>1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.c > 1)'
3174
+ end
3175
+
3083
3176
  specify "should handle SQL::WindowFunctions" do
3084
3177
  @ds.meta_def(:supports_window_functions?){true}
3085
3178
  @ds.select{sum(:over, :args=>:a, :partition=>:b, :order=>:c){}}.qualify_to_first_source.sql.should == 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
@@ -327,6 +327,17 @@ context "Blockless Ruby Filters" do
327
327
  @d.l([[:x, nil], [:y, [1,2,3]]].sql_or).should == '((x IS NULL) OR (y IN (1, 2, 3)))'
328
328
  end
329
329
 
330
+ it "should emulate columns for array values" do
331
+ @d.l([:x, :y]=>[[1,2], [3,4]].sql_array).should == '((x, y) IN ((1, 2), (3, 4)))'
332
+ @d.l([:x, :y, :z]=>[[1,2,5], [3,4,6]]).should == '((x, y, z) IN ((1, 2, 5), (3, 4, 6)))'
333
+ end
334
+
335
+ it "should emulate multiple column in if not supported" do
336
+ @d.meta_def(:supports_multiple_column_in?){false}
337
+ @d.l([:x, :y]=>[[1,2], [3,4]].sql_array).should == '(((x = 1) AND (y = 2)) OR ((x = 3) AND (y = 4)))'
338
+ @d.l([:x, :y, :z]=>[[1,2,5], [3,4,6]]).should == '(((x = 1) AND (y = 2) AND (z = 5)) OR ((x = 3) AND (y = 4) AND (z = 6)))'
339
+ end
340
+
330
341
  it "should support Array#sql_string_join for concatenation of SQL strings" do
331
342
  @d.lit([:x].sql_string_join).should == '(x)'
332
343
  @d.lit([:x].sql_string_join(', ')).should == '(x)'
@@ -533,4 +544,10 @@ context Sequel::SQL::VirtualRow do
533
544
  proc{@d.l{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}}.should raise_error(Sequel::Error)
534
545
  proc{Sequel::Dataset.new(nil).filter{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}.sql}.should raise_error(Sequel::Error)
535
546
  end
547
+
548
+ it "should deal with classes without requiring :: prefix" do
549
+ @d.l{date < Date.today}.should == "(\"date\" < '#{Date.today}')"
550
+ @d.l{date < Sequel::CURRENT_DATE}.should == "(\"date\" < CURRENT_DATE)"
551
+ @d.l{num < Math::PI.to_i}.should == "(\"num\" < 3)"
552
+ end
536
553
  end