sequel 3.27.0 → 3.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +96 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +1 -1
  4. data/doc/association_basics.rdoc +48 -0
  5. data/doc/opening_databases.rdoc +29 -5
  6. data/doc/prepared_statements.rdoc +1 -0
  7. data/doc/release_notes/3.28.0.txt +304 -0
  8. data/doc/testing.rdoc +42 -0
  9. data/doc/transactions.rdoc +97 -0
  10. data/lib/sequel/adapters/db2.rb +95 -65
  11. data/lib/sequel/adapters/firebird.rb +25 -219
  12. data/lib/sequel/adapters/ibmdb.rb +440 -0
  13. data/lib/sequel/adapters/jdbc.rb +12 -0
  14. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  15. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  16. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  18. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  19. data/lib/sequel/adapters/mysql.rb +10 -15
  20. data/lib/sequel/adapters/odbc.rb +1 -2
  21. data/lib/sequel/adapters/odbc/db2.rb +5 -5
  22. data/lib/sequel/adapters/postgres.rb +71 -11
  23. data/lib/sequel/adapters/shared/db2.rb +290 -0
  24. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +18 -75
  26. data/lib/sequel/adapters/shared/mysql.rb +13 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +52 -36
  28. data/lib/sequel/adapters/shared/sqlite.rb +32 -36
  29. data/lib/sequel/adapters/sqlite.rb +4 -8
  30. data/lib/sequel/adapters/tinytds.rb +7 -3
  31. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  32. data/lib/sequel/core.rb +1 -1
  33. data/lib/sequel/database/connecting.rb +1 -1
  34. data/lib/sequel/database/misc.rb +6 -5
  35. data/lib/sequel/database/query.rb +1 -1
  36. data/lib/sequel/database/schema_generator.rb +2 -1
  37. data/lib/sequel/dataset/actions.rb +149 -33
  38. data/lib/sequel/dataset/features.rb +44 -7
  39. data/lib/sequel/dataset/misc.rb +9 -1
  40. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  41. data/lib/sequel/dataset/query.rb +63 -10
  42. data/lib/sequel/dataset/sql.rb +22 -5
  43. data/lib/sequel/model.rb +3 -3
  44. data/lib/sequel/model/associations.rb +250 -27
  45. data/lib/sequel/model/base.rb +10 -16
  46. data/lib/sequel/plugins/many_through_many.rb +34 -2
  47. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  48. data/lib/sequel/sql.rb +94 -51
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/db2_spec.rb +146 -0
  51. data/spec/adapters/postgres_spec.rb +74 -6
  52. data/spec/adapters/spec_helper.rb +1 -0
  53. data/spec/adapters/sqlite_spec.rb +11 -0
  54. data/spec/core/database_spec.rb +7 -0
  55. data/spec/core/dataset_spec.rb +180 -17
  56. data/spec/core/expression_filters_spec.rb +107 -41
  57. data/spec/core/spec_helper.rb +11 -0
  58. data/spec/extensions/many_through_many_spec.rb +115 -1
  59. data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
  60. data/spec/integration/associations_test.rb +193 -15
  61. data/spec/integration/database_test.rb +4 -2
  62. data/spec/integration/dataset_test.rb +215 -19
  63. data/spec/integration/plugin_test.rb +8 -5
  64. data/spec/integration/prepared_statement_test.rb +91 -98
  65. data/spec/integration/schema_test.rb +27 -11
  66. data/spec/integration/spec_helper.rb +10 -0
  67. data/spec/integration/type_test.rb +2 -2
  68. data/spec/model/association_reflection_spec.rb +91 -0
  69. data/spec/model/associations_spec.rb +13 -0
  70. data/spec/model/base_spec.rb +8 -21
  71. data/spec/model/eager_loading_spec.rb +243 -9
  72. data/spec/model/model_spec.rb +15 -2
  73. metadata +16 -4
@@ -514,11 +514,6 @@ describe "Postgres::Dataset#insert" do
514
514
  @db.sqls.last.should == 'INSERT INTO test5 (value) VALUES (10) RETURNING xid'
515
515
  end
516
516
 
517
- specify "should have insert_returning_sql use the RETURNING keyword" do
518
- @ds.insert_returning_sql(:xid, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING xid"
519
- @ds.insert_returning_sql('*'.lit, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING *"
520
- end
521
-
522
517
  specify "should have insert_select return nil if server_version < 80200" do
523
518
  @ds.meta_def(:server_version){80100}
524
519
  @ds.insert_select(:value=>10).should == nil
@@ -976,7 +971,7 @@ if POSTGRES_DB.adapter_scheme == :postgres
976
971
  @db.transaction{1001.times{|i| @ds.insert(i)}}
977
972
  end
978
973
  after(:all) do
979
- @db.drop_table(:test) rescue nil
974
+ @db.drop_table(:test_cursor) rescue nil
980
975
  end
981
976
 
982
977
  specify "should return the same results as the non-cursor use" do
@@ -1026,3 +1021,76 @@ if POSTGRES_DB.adapter_scheme == :postgres
1026
1021
  end
1027
1022
  end
1028
1023
  end
1024
+
1025
+ if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && POSTGRES_DB.server_version >= 90000
1026
+ describe "Postgres::Database#copy_table" do
1027
+ before(:all) do
1028
+ @db = POSTGRES_DB
1029
+ @db.create_table!(:test_copy){Integer :x; Integer :y}
1030
+ ds = @db[:test_copy]
1031
+ ds.insert(1, 2)
1032
+ ds.insert(3, 4)
1033
+ end
1034
+ after(:all) do
1035
+ @db.drop_table(:test_copy) rescue nil
1036
+ end
1037
+
1038
+ specify "without a block or options should return a text version of the table as a single string" do
1039
+ @db.copy_table(:test_copy).should == "1\t2\n3\t4\n"
1040
+ end
1041
+
1042
+ specify "without a block and with :format=>:csv should return a csv version of the table as a single string" do
1043
+ @db.copy_table(:test_copy, :format=>:csv).should == "1,2\n3,4\n"
1044
+ end
1045
+
1046
+ specify "should treat string as SQL code" do
1047
+ @db.copy_table('COPY "test_copy" TO STDOUT').should == "1\t2\n3\t4\n"
1048
+ end
1049
+
1050
+ specify "should respect given :options options" do
1051
+ @db.copy_table(:test_copy, :options=>"FORMAT csv, HEADER TRUE").should == "x,y\n1,2\n3,4\n"
1052
+ end
1053
+
1054
+ specify "should respect given :options options when :format is used" do
1055
+ @db.copy_table(:test_copy, :format=>:csv, :options=>"QUOTE '''', FORCE_QUOTE *").should == "'1','2'\n'3','4'\n"
1056
+ end
1057
+
1058
+ specify "should accept dataset as first argument" do
1059
+ @db.copy_table(@db[:test_copy].cross_join(:test_copy___tc).order(1, 2, 3, 4)).should == "1\t2\t1\t2\n1\t2\t3\t4\n3\t4\t1\t2\n3\t4\t3\t4\n"
1060
+ end
1061
+
1062
+ specify "with a block and no options should yield each row as a string in text format" do
1063
+ buf = []
1064
+ @db.copy_table(:test_copy){|b| buf << b}
1065
+ buf.should == ["1\t2\n", "3\t4\n"]
1066
+ end
1067
+
1068
+ specify "with a block and :format=>:csv should yield each row as a string in csv format" do
1069
+ buf = []
1070
+ @db.copy_table(:test_copy, :format=>:csv){|b| buf << b}
1071
+ buf.should == ["1,2\n", "3,4\n"]
1072
+ end
1073
+
1074
+ specify "should work fine when using a block that is terminated early with a following copy_table" do
1075
+ buf = []
1076
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; break}}.should raise_error(Sequel::DatabaseDisconnectError)
1077
+ buf.should == ["1,2\n"]
1078
+ buf.clear
1079
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; raise ArgumentError}}.should raise_error(Sequel::DatabaseDisconnectError)
1080
+ buf.should == ["1,2\n"]
1081
+ buf.clear
1082
+ @db.copy_table(:test_copy){|b| buf << b}
1083
+ buf.should == ["1\t2\n", "3\t4\n"]
1084
+ end
1085
+
1086
+ specify "should work fine when using a block that is terminated early with a following regular query" do
1087
+ buf = []
1088
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; break}}.should raise_error(Sequel::DatabaseDisconnectError)
1089
+ buf.should == ["1,2\n"]
1090
+ buf.clear
1091
+ proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; raise ArgumentError}}.should raise_error(Sequel::DatabaseDisconnectError)
1092
+ buf.should == ["1,2\n"]
1093
+ @db[:test_copy].select_order_map(:x).should == [1, 3]
1094
+ end
1095
+ end
1096
+ end
@@ -36,6 +36,7 @@ end
36
36
  end
37
37
 
38
38
  def self.cspecify(message, *checked, &block)
39
+ return specify(message, &block) if ENV['SEQUEL_NO_PENDING']
39
40
  pending = false
40
41
  checked.each do |c|
41
42
  case c
@@ -66,6 +66,17 @@ describe "An SQLite database" do
66
66
  @db[:fk].all.should == [{:id=>1, :parent_id=>2}]
67
67
  end
68
68
 
69
+ specify "should support a use_timestamp_timezones setting" do
70
+ @db.create_table!(:time){Time :time}
71
+ @db[:time].insert(Time.now)
72
+ @db[:time].get(:time.cast_string).should =~ /[-+]\d\d\d\d\z/
73
+ @db.use_timestamp_timezones = false
74
+ @db[:time].delete
75
+ @db[:time].insert(Time.now)
76
+ @db[:time].get(:time.cast_string).should_not =~ /[-+]\d\d\d\d\z/
77
+ @db.use_timestamp_timezones = true
78
+ end
79
+
69
80
  specify "should provide a list of existing tables" do
70
81
  @db.drop_table(:testing) rescue nil
71
82
  @db.tables.should be_a_kind_of(Array)
@@ -1544,6 +1544,13 @@ describe "Database#typecast_value" do
1544
1544
  end
1545
1545
  end
1546
1546
 
1547
+ specify "should correctly handle time value conversion to SQLTime with fractional seconds" do
1548
+ t = Time.now
1549
+ st = Sequel::SQLTime.local(t.year, t.month, t.day, 1, 2, 3, 500000)
1550
+ t = Time.local(t.year, t.month, t.day, 1, 2, 3, 500000)
1551
+ @db.typecast_value(:time, t).should == st
1552
+ end
1553
+
1547
1554
  specify "should have an underlying exception class available at wrapped_exception" do
1548
1555
  begin
1549
1556
  @db.typecast_value(:date, 'a')
@@ -622,10 +622,18 @@ describe "Dataset#where" do
622
622
  end
623
623
 
624
624
  specify "should accept true and false as arguments" do
625
- @dataset.filter(true).sql.should ==
626
- "SELECT * FROM test WHERE 't'"
627
- @dataset.filter(false).sql.should ==
628
- "SELECT * FROM test WHERE 'f'"
625
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE 't'"
626
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE 't'"
627
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE 'f'"
628
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE 'f'"
629
+ end
630
+
631
+ specify "should use boolean expression if dataset does not support where true/false" do
632
+ def @dataset.supports_where_true?() false end
633
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE (1 = 1)"
634
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE (1 = 1)"
635
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE (1 = 0)"
636
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE (1 = 0)"
629
637
  end
630
638
 
631
639
  specify "should allow the use of multiple arguments" do
@@ -1300,6 +1308,32 @@ describe "Dataset#select_all" do
1300
1308
  specify "should select all columns all tables if given a multiple arguments" do
1301
1309
  @d.select_all(:test, :foo).sql.should == 'SELECT test.*, foo.* FROM test'
1302
1310
  end
1311
+
1312
+ specify "should work correctly with qualified symbols" do
1313
+ @d.select_all(:sch__test).sql.should == 'SELECT sch.test.* FROM test'
1314
+ end
1315
+
1316
+ specify "should work correctly with aliased symbols" do
1317
+ @d.select_all(:test___al).sql.should == 'SELECT al.* FROM test'
1318
+ @d.select_all(:sch__test___al).sql.should == 'SELECT al.* FROM test'
1319
+ end
1320
+
1321
+ specify "should work correctly with SQL::Identifiers" do
1322
+ @d.select_all(:test.identifier).sql.should == 'SELECT test.* FROM test'
1323
+ end
1324
+
1325
+ specify "should work correctly with SQL::QualifiedIdentifier" do
1326
+ @d.select_all(:test.qualify(:sch)).sql.should == 'SELECT sch.test.* FROM test'
1327
+ end
1328
+
1329
+ specify "should work correctly with SQL::AliasedExpressions" do
1330
+ @d.select_all(:test.as(:al)).sql.should == 'SELECT al.* FROM test'
1331
+ end
1332
+
1333
+ specify "should work correctly with SQL::JoinClauses" do
1334
+ d = @d.cross_join(:foo).cross_join(:test___al)
1335
+ @d.select_all(*d.opts[:join]).sql.should == 'SELECT foo.*, al.* FROM test'
1336
+ end
1303
1337
  end
1304
1338
 
1305
1339
  describe "Dataset#select_more" do
@@ -1344,6 +1378,14 @@ describe "Dataset#select_append" do
1344
1378
  @d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1345
1379
  @d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1346
1380
  end
1381
+
1382
+ specify "should select from all from and join tables if SELECT *, column not supported" do
1383
+ @d.meta_def(:supports_select_all_and_column?){false}
1384
+ @d.select_append(:b).sql.should == 'SELECT test.*, b FROM test'
1385
+ @d.from(:test, :c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test, c'
1386
+ @d.cross_join(:c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test CROSS JOIN c'
1387
+ @d.cross_join(:c).cross_join(:d).select_append(:b).sql.should == 'SELECT test.*, c.*, d.*, b FROM test CROSS JOIN c CROSS JOIN d'
1388
+ end
1347
1389
  end
1348
1390
 
1349
1391
  describe "Dataset#order" do
@@ -1445,6 +1487,13 @@ describe "Dataset#with_sql" do
1445
1487
  specify "should keep row_proc" do
1446
1488
  @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1447
1489
  end
1490
+
1491
+ specify "should work with method symbols and arguments" do
1492
+ @dataset.with_sql(:delete_sql).sql.should == 'DELETE FROM test'
1493
+ @dataset.with_sql(:insert_sql, :b=>1).sql.should == 'INSERT INTO test (b) VALUES (1)'
1494
+ @dataset.with_sql(:update_sql, :b=>1).sql.should == 'UPDATE test SET b = 1'
1495
+ end
1496
+
1448
1497
  end
1449
1498
 
1450
1499
  describe "Dataset#order_by" do
@@ -1661,17 +1710,6 @@ describe "Dataset#qualified_column_name" do
1661
1710
  end
1662
1711
  end
1663
1712
 
1664
- class DummyDataset < Sequel::Dataset
1665
- VALUES = [
1666
- {:a => 1, :b => 2},
1667
- {:a => 3, :b => 4},
1668
- {:a => 5, :b => 6}
1669
- ]
1670
- def fetch_rows(sql, &block)
1671
- VALUES.each(&block)
1672
- end
1673
- end
1674
-
1675
1713
  describe "Dataset#map" do
1676
1714
  before do
1677
1715
  @d = DummyDataset.new(nil).from(:items)
@@ -1685,6 +1723,10 @@ describe "Dataset#map" do
1685
1723
  @d.map(:a).should == [1, 3, 5]
1686
1724
  end
1687
1725
 
1726
+ specify "should support multiple column names if an array of column names is given" do
1727
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1728
+ end
1729
+
1688
1730
  specify "should return the complete dataset values if nothing is given" do
1689
1731
  @d.map.to_a.should == DummyDataset::VALUES
1690
1732
  end
@@ -1704,6 +1746,13 @@ describe "Dataset#to_hash" do
1704
1746
  @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1705
1747
  @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1706
1748
  end
1749
+
1750
+ specify "should support using an array of columns as either the key or the value" do
1751
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1752
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1753
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1754
+ @d.to_hash([:a, :b]).should == {[1, 2] => {:a => 1, :b => 2}, [3, 4] => {:a => 3, :b => 4}, [5, 6] => {:a => 5, :b => 6}}
1755
+ end
1707
1756
  end
1708
1757
 
1709
1758
  describe "Dataset#distinct" do
@@ -2177,6 +2226,12 @@ describe "Dataset#join_table" do
2177
2226
  @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
2178
2227
  end
2179
2228
 
2229
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
2230
+ @d.meta_def(:supports_cte?){true}
2231
+ @d.meta_def(:supports_cte_in_subselect?){false}
2232
+ @d.join(Sequel::Dataset.new(nil).from(:categories).with(:a, Sequel::Dataset.new(nil).from(:b)), [:id]).sql.should == 'WITH "a" AS (SELECT * FROM b) SELECT * FROM "items" INNER JOIN (SELECT * FROM categories) AS "t1" USING ("id")'
2233
+ end
2234
+
2180
2235
  specify "should raise an error if using an array of symbols with a block" do
2181
2236
  proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
2182
2237
  end
@@ -2251,7 +2306,6 @@ describe "Dataset#join_table" do
2251
2306
  'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2252
2307
  end
2253
2308
 
2254
-
2255
2309
  specify "should not allow insert, update, delete, or truncate" do
2256
2310
  proc{@d.join(:categories, :a=>:d).insert_sql}.should raise_error(Sequel::InvalidOperation)
2257
2311
  proc{@d.join(:categories, :a=>:d).update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
@@ -3805,6 +3859,15 @@ describe "Sequel::Dataset #with and #with_recursive" do
3805
3859
  proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
3806
3860
  proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
3807
3861
  end
3862
+
3863
+ specify "#with should work on insert, update, and delete statements if they support it" do
3864
+ [:insert, :update, :delete].each do |m|
3865
+ @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_with_sql"]}
3866
+ end
3867
+ @ds.with(:t, @db[:x]).insert_sql(1).should == 'WITH t AS (SELECT * FROM x) INSERT INTO t VALUES (1)'
3868
+ @ds.with(:t, @db[:x]).update_sql(:foo=>1).should == 'WITH t AS (SELECT * FROM x) UPDATE t SET foo = 1'
3869
+ @ds.with(:t, @db[:x]).delete_sql.should == 'WITH t AS (SELECT * FROM x) DELETE FROM t'
3870
+ end
3808
3871
  end
3809
3872
 
3810
3873
  describe Sequel::SQL::Constants do
@@ -4032,6 +4095,14 @@ describe "Sequel::Dataset#select_map" do
4032
4095
  @ds.select_map{a(t__c)}.should == [1, 2]
4033
4096
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t']
4034
4097
  end
4098
+
4099
+ specify "should handle an array of columns" do
4100
+ @ds.select_map([:c, :c]).should == [[1, 1], [2, 2]]
4101
+ @ds.db.sqls.should == ['SELECT c, c FROM t']
4102
+ @ds.db.reset
4103
+ @ds.select_order_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :a__c, :a__d___c]).should == [[1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2]]
4104
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, a.c, a.d AS c FROM t ORDER BY d, b.c, c, b.c, a.c, a.d']
4105
+ end
4035
4106
  end
4036
4107
 
4037
4108
  describe "Sequel::Dataset#select_order_map" do
@@ -4070,10 +4141,23 @@ describe "Sequel::Dataset#select_order_map" do
4070
4141
  @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
4071
4142
  end
4072
4143
 
4144
+ specify "should handle OrderedExpressions" do
4145
+ @ds.select_order_map(:a.desc).should == [1, 2]
4146
+ @ds.db.sqls.should == ['SELECT a FROM t ORDER BY a DESC']
4147
+ end
4148
+
4073
4149
  specify "should accept a block" do
4074
4150
  @ds.select_order_map{a(t__c)}.should == [1, 2]
4075
4151
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
4076
4152
  end
4153
+
4154
+ specify "should handle an array of columns" do
4155
+ @ds.select_order_map([:c, :c]).should == [[1, 1], [2, 2]]
4156
+ @ds.db.sqls.should == ['SELECT c, c FROM t ORDER BY c, c']
4157
+ @ds.db.reset
4158
+ @ds.select_order_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :c.identifier.qualify(:b).desc, :a__c, :a__d___c.desc]).should == [[1, 1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2, 2]]
4159
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, b.c, a.c, a.d AS c FROM t ORDER BY d, b.c, c, b.c, b.c DESC, a.c, a.d DESC']
4160
+ end
4077
4161
  end
4078
4162
 
4079
4163
  describe "Sequel::Dataset#select_hash" do
@@ -4089,7 +4173,7 @@ describe "Sequel::Dataset#select_hash" do
4089
4173
  @ds.db.reset
4090
4174
  end
4091
4175
 
4092
- specify "should do select and map in one step" do
4176
+ specify "should do select and to_hash in one step" do
4093
4177
  @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4094
4178
  @ds.select_hash(:a, :b).should == {1=>2, 3=>4}
4095
4179
  @ds.db.sqls.should == ['SELECT a, b FROM t']
@@ -4112,6 +4196,36 @@ describe "Sequel::Dataset#select_hash" do
4112
4196
  @ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
4113
4197
  @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
4114
4198
  end
4199
+
4200
+ specify "should handle SQL::Identifiers in arguments" do
4201
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4202
+ @ds.select_hash(:a.identifier, :b.identifier).should == {1=>2, 3=>4}
4203
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
4204
+ end
4205
+
4206
+ specify "should handle SQL::QualifiedIdentifiers in arguments" do
4207
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4208
+ @ds.select_hash(:a.qualify(:t), :b.identifier.qualify(:t)).should == {1=>2, 3=>4}
4209
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
4210
+ end
4211
+
4212
+ specify "should handle SQL::AliasedExpressions in arguments" do
4213
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4214
+ @ds.select_hash(:c.as(:a), :t.as(:b)).should == {1=>2, 3=>4}
4215
+ @ds.db.sqls.should == ['SELECT c AS a, t AS b FROM t']
4216
+ end
4217
+
4218
+ specify "should work with arrays of columns" do
4219
+ @ds.set_fr_yield([{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}])
4220
+ @ds.select_hash([:a, :c], :b).should == {[1, 3]=>2, [4, 6]=>5}
4221
+ @ds.db.sqls.should == ['SELECT a, c, b FROM t']
4222
+ @ds.select_hash(:a, [:b, :c]).should == {1=>[2, 3], 4=>[5, 6]}
4223
+ @ds.select_hash([:a, :b], [:b, :c]).should == {[1, 2]=>[2, 3], [4, 5]=>[5, 6]}
4224
+ end
4225
+
4226
+ specify "should raise an error if the resulting symbol cannot be determined" do
4227
+ proc{@ds.select_hash(:c.as(:a), 'foo')}.should raise_error(Sequel::Error)
4228
+ end
4115
4229
  end
4116
4230
 
4117
4231
  describe "Modifying joined datasets" do
@@ -4163,3 +4277,52 @@ describe "Custom ASTTransformer" do
4163
4277
  'SELECT * FROM tt CROSS JOIN aa AS gg INNER JOIN bb AS hh USING (cc) INNER JOIN dd AS ii ON (ii.ee = hh.ff)'
4164
4278
  end
4165
4279
  end
4280
+
4281
+ describe "Dataset#returning" do
4282
+ before do
4283
+ @ds = Sequel::Database.new[:t].returning(:foo)
4284
+ @pr = proc do
4285
+ [:insert, :update, :delete].each do |m|
4286
+ @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_returning_sql"]}
4287
+ end
4288
+ end
4289
+ end
4290
+
4291
+ specify "should use RETURNING clause in the SQL if the dataset supports it" do
4292
+ @pr.call
4293
+ @ds.delete_sql.should == "DELETE FROM t RETURNING foo"
4294
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1) RETURNING foo"
4295
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1 RETURNING foo"
4296
+ end
4297
+
4298
+ specify "should not use RETURNING clause in the SQL if the dataset does not support it" do
4299
+ @ds.delete_sql.should == "DELETE FROM t"
4300
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1)"
4301
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1"
4302
+ end
4303
+
4304
+ specify "should have insert, update, and delete yield to blocks if RETURNING is used" do
4305
+ @pr.call
4306
+ def @ds.fetch_rows(sql)
4307
+ yield(:foo=>sql)
4308
+ end
4309
+ h = {}
4310
+ @ds.delete{|r| h = r}
4311
+ h.should == {:foo=>"DELETE FROM t RETURNING foo"}
4312
+ @ds.insert(1){|r| h = r}
4313
+ h.should == {:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}
4314
+ @ds.update(:foo=>1){|r| h = r}
4315
+ h.should == {:foo=>"UPDATE t SET foo = 1 RETURNING foo"}
4316
+ end
4317
+
4318
+ specify "should have insert, update, and delete return arrays of hashes if RETURNING is used and a block is not given" do
4319
+ @pr.call
4320
+ def @ds.fetch_rows(sql)
4321
+ yield(:foo=>sql)
4322
+ end
4323
+ h = {}
4324
+ @ds.delete.should == [{:foo=>"DELETE FROM t RETURNING foo"}]
4325
+ @ds.insert(1).should == [{:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}]
4326
+ @ds.update(:foo=>1).should == [{:foo=>"UPDATE t SET foo = 1 RETURNING foo"}]
4327
+ end
4328
+ end
@@ -186,31 +186,26 @@ describe "Blockless Ruby Filters" do
186
186
  @d.l(~((((:x - :y)/(:x + :y))*:z) <= 100)).should == '((((x - y) / (x + y)) * z) > 100)'
187
187
  end
188
188
 
189
- it "should not allow negation of string expressions" do
190
- proc{~:x.sql_string}.should raise_error
191
- proc{~([:x, :y].sql_string_join)}.should raise_error
189
+ it "should not add ~ method to string expressions" do
190
+ proc{~:x.sql_string}.should raise_error(NoMethodError)
192
191
  end
193
192
 
194
- it "should not allow mathematical or string operations on true, false, or nil" do
195
- proc{:x + 1}.should_not raise_error
196
- proc{:x - true}.should raise_error(Sequel::Error)
197
- proc{:x / false}.should raise_error(Sequel::Error)
198
- proc{:x * nil}.should raise_error(Sequel::Error)
199
- proc{[:x, nil].sql_string_join}.should raise_error(Sequel::Error)
193
+ it "should allow mathematical or string operations on true, false, or nil" do
194
+ @d.lit(:x + 1).should == '(x + 1)'
195
+ @d.lit(:x - true).should == "(x - 't')"
196
+ @d.lit(:x / false).should == "(x / 'f')"
197
+ @d.lit(:x * nil).should == '(x * NULL)'
198
+ @d.lit([:x, nil].sql_string_join).should == '(x || NULL)'
200
199
  end
201
200
 
202
- it "should not allow mathematical or string operations on boolean complex expressions" do
203
- proc{:x + (:y + 1)}.should_not raise_error
204
- proc{:x - (~:y)}.should raise_error(Sequel::Error)
205
- proc{:x / (:y & :z)}.should raise_error(Sequel::Error)
206
- proc{:x * (:y | :z)}.should raise_error(Sequel::Error)
207
- proc{:x + :y.like('a')}.should raise_error(Sequel::Error)
208
- proc{:x - :y.like(/a/)}.should raise_error(Sequel::Error)
209
- proc{:x * :y.like(/a/i)}.should raise_error(Sequel::Error)
210
- proc{:x + ~:y.like('a')}.should raise_error(Sequel::Error)
211
- proc{:x - ~:y.like(/a/)}.should raise_error(Sequel::Error)
212
- proc{:x * ~:y.like(/a/i)}.should raise_error(Sequel::Error)
213
- proc{[:x, ~:y.like(/a/i)].sql_string_join}.should raise_error(Sequel::Error)
201
+ it "should allow mathematical or string operations on boolean complex expressions" do
202
+ @d.lit(:x + (:y + 1)).should == '(x + y + 1)'
203
+ @d.lit(:x - ~:y).should == '(x - NOT y)'
204
+ @d.lit(:x / (:y & :z)).should == '(x / (y AND z))'
205
+ @d.lit(:x * (:y | :z)).should == '(x * (y OR z))'
206
+ @d.lit(:x + :y.like('a')).should == "(x + (y LIKE 'a'))"
207
+ @d.lit(:x - ~:y.like('a')).should == "(x - (y NOT LIKE 'a'))"
208
+ @d.lit([:x, ~:y.like('a')].sql_string_join).should == "(x || (y NOT LIKE 'a'))"
214
209
  end
215
210
 
216
211
  it "should support AND conditions via &" do
@@ -382,12 +377,12 @@ describe "Blockless Ruby Filters" do
382
377
  @d.l((:x + 1) & (:x + 2) > 100).should == '(((x + 1) & (x + 2)) > 100)'
383
378
  end
384
379
 
385
- it "should raise an error if use a Bitwise method on a ComplexExpression that isn't a NumericExpression" do
386
- proc{(:x + 1) & (:x & 2)}.should raise_error(Sequel::Error)
380
+ it "should allow using a Bitwise method on a ComplexExpression that isn't a NumericExpression" do
381
+ @d.lit((:x + 1) & (:x + '2')).should == "((x + 1) & (x || '2'))"
387
382
  end
388
383
 
389
- it "should raise an error if use a Boolean method on a ComplexExpression that isn't a BooleanExpression" do
390
- proc{:x & (:x + 2)}.should raise_error(Sequel::Error)
384
+ it "should allow using a Boolean method on a ComplexExpression that isn't a BooleanExpression" do
385
+ @d.l(:x & (:x + '2')).should == "(x AND (x || '2'))"
391
386
  end
392
387
 
393
388
  it "should raise an error if attempting to invert a ComplexExpression that isn't a BooleanExpression" do
@@ -441,6 +436,77 @@ describe "Blockless Ruby Filters" do
441
436
  proc{Sequel::SQL::ComplexExpression.new(:BANG, 1, 2)}.should raise_error(Sequel::Error)
442
437
  end
443
438
 
439
+ it "should use a string concatentation for + if given a string" do
440
+ @d.lit(:x + '1').should == "(x || '1')"
441
+ @d.lit(:x + '1' + '1').should == "(x || '1' || '1')"
442
+ end
443
+
444
+ it "should use an addition for + if given a literal string" do
445
+ @d.lit(:x + '1'.lit).should == "(x + 1)"
446
+ @d.lit(:x + '1'.lit + '1'.lit).should == "(x + 1 + 1)"
447
+ end
448
+
449
+ it "should use a bitwise operator for & and | if given an integer" do
450
+ @d.lit(:x & 1).should == "(x & 1)"
451
+ @d.lit(:x | 1).should == "(x | 1)"
452
+ @d.lit(:x & 1 & 1).should == "(x & 1 & 1)"
453
+ @d.lit(:x | 1 | 1).should == "(x | 1 | 1)"
454
+ end
455
+
456
+ it "should allow adding a string to an integer expression" do
457
+ @d.lit(:x + 1 + 'a').should == "(x + 1 + 'a')"
458
+ end
459
+
460
+ it "should allow adding an integer to an string expression" do
461
+ @d.lit(:x + 'a' + 1).should == "(x || 'a' || 1)"
462
+ end
463
+
464
+ it "should allow adding a boolean to an integer expression" do
465
+ @d.lit(:x + 1 + true).should == "(x + 1 + 't')"
466
+ end
467
+
468
+ it "should allow adding a boolean to an string expression" do
469
+ @d.lit(:x + 'a' + true).should == "(x || 'a' || 't')"
470
+ end
471
+
472
+ it "should allow using a boolean operation with an integer on an boolean expression" do
473
+ @d.lit(:x & :a & 1).should == "(x AND a AND 1)"
474
+ end
475
+
476
+ it "should allow using a boolean operation with a string on an boolean expression" do
477
+ @d.lit(:x & :a & 'a').should == "(x AND a AND 'a')"
478
+ end
479
+
480
+ it "should allowing AND of boolean expression and literal string" do
481
+ @d.lit(:x & :a & 'a'.lit).should == "(x AND a AND a)"
482
+ end
483
+
484
+ it "should allowing + of integer expression and literal string" do
485
+ @d.lit(:x + :a + 'a'.lit).should == "(x + a + a)"
486
+ end
487
+
488
+ it "should allowing + of string expression and literal string" do
489
+ @d.lit(:x + 'a' + 'a'.lit).should == "(x || 'a' || a)"
490
+ end
491
+
492
+ it "should allow sql_{string,boolean,number} methods on numeric expressions" do
493
+ @d.lit((:x + 1).sql_string + 'a').should == "((x + 1) || 'a')"
494
+ @d.lit((:x + 1).sql_boolean & 1).should == "((x + 1) AND 1)"
495
+ @d.lit((:x + 1).sql_number + 'a').should == "(x + 1 + 'a')"
496
+ end
497
+
498
+ it "should allow sql_{string,boolean,number} methods on string expressions" do
499
+ @d.lit((:x + 'a').sql_string + 'a').should == "(x || 'a' || 'a')"
500
+ @d.lit((:x + 'a').sql_boolean & 1).should == "((x || 'a') AND 1)"
501
+ @d.lit((:x + 'a').sql_number + 'a').should == "((x || 'a') + 'a')"
502
+ end
503
+
504
+ it "should allow sql_{string,boolean,number} methods on boolean expressions" do
505
+ @d.lit((:x & :y).sql_string + 'a').should == "((x AND y) || 'a')"
506
+ @d.lit((:x & :y).sql_boolean & 1).should == "(x AND y AND 1)"
507
+ @d.lit((:x & :y).sql_number + 'a').should == "((x AND y) + 'a')"
508
+ end
509
+
444
510
  it "should raise an error if trying to literalize an invalid complex expression" do
445
511
  ce = :x + 1
446
512
  ce.instance_variable_set(:@op, :BANG)
@@ -494,23 +560,23 @@ describe "Blockless Ruby Filters" do
494
560
 
495
561
  if RUBY_VERSION < '1.9.0'
496
562
  it "should not allow inequality operations on true, false, or nil" do
497
- proc{:x > 1}.should_not raise_error
498
- proc{:x < true}.should raise_error(Sequel::Error)
499
- proc{:x >= false}.should raise_error(Sequel::Error)
500
- proc{:x <= nil}.should raise_error(Sequel::Error)
563
+ @d.lit(:x > 1).should == "(x > 1)"
564
+ @d.lit(:x < true).should == "(x < 't')"
565
+ @d.lit(:x >= false).should == "(x >= 'f')"
566
+ @d.lit(:x <= nil).should == "(x <= NULL)"
501
567
  end
502
568
 
503
569
  it "should not allow inequality operations on boolean complex expressions" do
504
- proc{:x > (:y > 5)}.should raise_error(Sequel::Error)
505
- proc{:x < (:y < 5)}.should raise_error(Sequel::Error)
506
- proc{:x >= (:y >= 5)}.should raise_error(Sequel::Error)
507
- proc{:x <= (:y <= 5)}.should raise_error(Sequel::Error)
508
- proc{:x > {:y => nil}}.should raise_error(Sequel::Error)
509
- proc{:x < ~{:y => nil}}.should raise_error(Sequel::Error)
510
- proc{:x >= {:y => 5}}.should raise_error(Sequel::Error)
511
- proc{:x <= ~{:y => 5}}.should raise_error(Sequel::Error)
512
- proc{:x >= {:y => [1,2,3]}}.should raise_error(Sequel::Error)
513
- proc{:x <= ~{:y => [1,2,3]}}.should raise_error(Sequel::Error)
570
+ @d.lit(:x > (:y > 5)).should == "(x > (y > 5))"
571
+ @d.lit(:x < (:y < 5)).should == "(x < (y < 5))"
572
+ @d.lit(:x >= (:y >= 5)).should == "(x >= (y >= 5))"
573
+ @d.lit(:x <= (:y <= 5)).should == "(x <= (y <= 5))"
574
+ @d.lit(:x > {:y => nil}).should == "(x > (y IS NULL))"
575
+ @d.lit(:x < ~{:y => nil}).should == "(x < (y IS NOT NULL))"
576
+ @d.lit(:x >= {:y => 5}).should == "(x >= (y = 5))"
577
+ @d.lit(:x <= ~{:y => 5}).should == "(x <= (y != 5))"
578
+ @d.lit(:x >= {:y => [1,2,3]}).should == "(x >= (y IN (1, 2, 3)))"
579
+ @d.lit(:x <= ~{:y => [1,2,3]}).should == "(x <= (y NOT IN (1, 2, 3)))"
514
580
  end
515
581
 
516
582
  it "should support >, <, >=, and <= via Symbol#>,<,>=,<=" do
@@ -605,7 +671,7 @@ describe Sequel::SQL::VirtualRow do
605
671
  end
606
672
 
607
673
  it "should support :frame=>:rows option for window function calls" do
608
- @d.l{rank(:over, :frame=>:rows){}}.should == 'rank() OVER (ROWS UNBOUNDED PRECEDING)'
674
+ @d.l{rank(:over, :frame=>:rows){}}.should == 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
609
675
  end
610
676
 
611
677
  it "should support :frame=>'some string' option for window function calls" do
@@ -617,7 +683,7 @@ describe Sequel::SQL::VirtualRow do
617
683
  end
618
684
 
619
685
  it "should support all these options together" do
620
- @d.l{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}.should == 'count(*) OVER ("win" PARTITION BY "a" ORDER BY "b" ROWS UNBOUNDED PRECEDING)'
686
+ @d.l{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}.should == 'count(*) OVER ("win" PARTITION BY "a" ORDER BY "b" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
621
687
  end
622
688
 
623
689
  it "should raise an error if window functions are not supported" do