sequel 3.27.0 → 3.28.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +96 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/doc/association_basics.rdoc +48 -0
- data/doc/opening_databases.rdoc +29 -5
- data/doc/prepared_statements.rdoc +1 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/testing.rdoc +42 -0
- data/doc/transactions.rdoc +97 -0
- data/lib/sequel/adapters/db2.rb +95 -65
- data/lib/sequel/adapters/firebird.rb +25 -219
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/jdbc.rb +12 -0
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/mysql.rb +10 -15
- data/lib/sequel/adapters/odbc.rb +1 -2
- data/lib/sequel/adapters/odbc/db2.rb +5 -5
- data/lib/sequel/adapters/postgres.rb +71 -11
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/mssql.rb +18 -75
- data/lib/sequel/adapters/shared/mysql.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +52 -36
- data/lib/sequel/adapters/shared/sqlite.rb +32 -36
- data/lib/sequel/adapters/sqlite.rb +4 -8
- data/lib/sequel/adapters/tinytds.rb +7 -3
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -5
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -1
- data/lib/sequel/dataset/actions.rb +149 -33
- data/lib/sequel/dataset/features.rb +44 -7
- data/lib/sequel/dataset/misc.rb +9 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -2
- data/lib/sequel/dataset/query.rb +63 -10
- data/lib/sequel/dataset/sql.rb +22 -5
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +250 -27
- data/lib/sequel/model/base.rb +10 -16
- data/lib/sequel/plugins/many_through_many.rb +34 -2
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/sql.rb +94 -51
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/postgres_spec.rb +74 -6
- data/spec/adapters/spec_helper.rb +1 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/database_spec.rb +7 -0
- data/spec/core/dataset_spec.rb +180 -17
- data/spec/core/expression_filters_spec.rb +107 -41
- data/spec/core/spec_helper.rb +11 -0
- data/spec/extensions/many_through_many_spec.rb +115 -1
- data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
- data/spec/integration/associations_test.rb +193 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +215 -19
- data/spec/integration/plugin_test.rb +8 -5
- data/spec/integration/prepared_statement_test.rb +91 -98
- data/spec/integration/schema_test.rb +27 -11
- data/spec/integration/spec_helper.rb +10 -0
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +13 -0
- data/spec/model/base_spec.rb +8 -21
- data/spec/model/eager_loading_spec.rb +243 -9
- data/spec/model/model_spec.rb +15 -2
- 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(:
|
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
|
@@ -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)
|
data/spec/core/database_spec.rb
CHANGED
@@ -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')
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -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
|
-
|
627
|
-
@dataset.filter(false).sql.should ==
|
628
|
-
|
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
|
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
|
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
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
386
|
-
|
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
|
390
|
-
|
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
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
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
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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
|