sequel 3.27.0 → 3.28.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.
- 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
|