sequel 3.5.0 → 3.6.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 (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