sequel 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +76 -0
- data/Rakefile +2 -2
- data/bin/sequel +9 -4
- data/doc/opening_databases.rdoc +279 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/virtual_rows.rdoc +42 -51
- data/lib/sequel/adapters/ado.rb +2 -5
- data/lib/sequel/adapters/db2.rb +5 -0
- data/lib/sequel/adapters/do.rb +3 -0
- data/lib/sequel/adapters/firebird.rb +6 -4
- data/lib/sequel/adapters/informix.rb +5 -3
- data/lib/sequel/adapters/jdbc.rb +10 -8
- data/lib/sequel/adapters/jdbc/h2.rb +17 -4
- data/lib/sequel/adapters/mysql.rb +6 -19
- data/lib/sequel/adapters/odbc.rb +14 -18
- data/lib/sequel/adapters/openbase.rb +8 -0
- data/lib/sequel/adapters/shared/mssql.rb +14 -8
- data/lib/sequel/adapters/shared/mysql.rb +53 -28
- data/lib/sequel/adapters/shared/oracle.rb +21 -12
- data/lib/sequel/adapters/shared/postgres.rb +46 -26
- data/lib/sequel/adapters/shared/progress.rb +10 -5
- data/lib/sequel/adapters/shared/sqlite.rb +28 -12
- data/lib/sequel/adapters/sqlite.rb +4 -3
- data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
- data/lib/sequel/connection_pool.rb +4 -3
- data/lib/sequel/database.rb +110 -10
- data/lib/sequel/database/schema_sql.rb +12 -3
- data/lib/sequel/dataset.rb +40 -3
- data/lib/sequel/dataset/convenience.rb +0 -11
- data/lib/sequel/dataset/graph.rb +25 -11
- data/lib/sequel/dataset/sql.rb +176 -68
- data/lib/sequel/extensions/migration.rb +37 -21
- data/lib/sequel/extensions/schema_dumper.rb +8 -61
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +9 -1
- data/lib/sequel/model/base.rb +8 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/sql.rb +125 -18
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/ado_spec.rb +1 -0
- data/spec/adapters/firebird_spec.rb +1 -0
- data/spec/adapters/informix_spec.rb +1 -0
- data/spec/adapters/mysql_spec.rb +23 -8
- data/spec/adapters/oracle_spec.rb +1 -0
- data/spec/adapters/postgres_spec.rb +52 -4
- data/spec/adapters/spec_helper.rb +2 -2
- data/spec/adapters/sqlite_spec.rb +2 -1
- data/spec/core/connection_pool_spec.rb +16 -0
- data/spec/core/database_spec.rb +174 -0
- data/spec/core/dataset_spec.rb +121 -26
- data/spec/core/expression_filters_spec.rb +156 -0
- data/spec/core/object_graph_spec.rb +20 -1
- data/spec/core/schema_spec.rb +5 -5
- data/spec/extensions/migration_spec.rb +140 -74
- data/spec/extensions/schema_dumper_spec.rb +3 -69
- data/spec/extensions/single_table_inheritance_spec.rb +6 -0
- data/spec/integration/dataset_test.rb +84 -2
- data/spec/integration/schema_test.rb +24 -5
- data/spec/integration/spec_helper.rb +8 -6
- data/spec/model/eager_loading_spec.rb +9 -0
- data/spec/model/record_spec.rb +35 -8
- metadata +8 -7
- data/lib/sequel/adapters/utils/date_format.rb +0 -21
- data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
- data/lib/sequel/adapters/utils/unsupported.rb +0 -50
@@ -3,6 +3,7 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
|
3
3
|
unless defined?(ORACLE_DB)
|
4
4
|
ORACLE_DB = Sequel.connect('oracle://hr:hr@localhost/XE')
|
5
5
|
end
|
6
|
+
INTEGRATION_DB = ORACLE_DB unless defined?(INTEGRATION_DB)
|
6
7
|
|
7
8
|
if ORACLE_DB.table_exists?(:items)
|
8
9
|
ORACLE_DB.drop_table :items
|
@@ -4,6 +4,7 @@ unless defined?(POSTGRES_DB)
|
|
4
4
|
POSTGRES_URL = 'postgres://postgres:postgres@localhost:5432/reality_spec' unless defined? POSTGRES_URL
|
5
5
|
POSTGRES_DB = Sequel.connect(ENV['SEQUEL_PG_SPEC_DB']||POSTGRES_URL)
|
6
6
|
end
|
7
|
+
INTEGRATION_DB = POSTGRES_DB unless defined?(INTEGRATION_DB)
|
7
8
|
|
8
9
|
def POSTGRES_DB.sqls
|
9
10
|
(@sqls ||= [])
|
@@ -43,12 +44,12 @@ context "A PostgreSQL database" do
|
|
43
44
|
|
44
45
|
specify "should correctly parse the schema" do
|
45
46
|
@db.schema(:test3, :reload=>true).should == [
|
46
|
-
[:value, {:type=>:integer, :allow_null=>true, :default=>nil, :db_type=>"integer", :primary_key=>false}],
|
47
|
-
[:time, {:type=>:datetime, :allow_null=>true, :default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
|
47
|
+
[:value, {:type=>:integer, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"integer", :primary_key=>false}],
|
48
|
+
[:time, {:type=>:datetime, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
|
48
49
|
]
|
49
50
|
@db.schema(:test4, :reload=>true).should == [
|
50
|
-
[:name, {:type=>:string, :allow_null=>true, :default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
|
51
|
-
[:value, {:type=>:blob, :allow_null=>true, :default=>nil, :db_type=>"bytea", :primary_key=>false}]
|
51
|
+
[:name, {:type=>:string, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
|
52
|
+
[:value, {:type=>:blob, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"bytea", :primary_key=>false}]
|
52
53
|
]
|
53
54
|
end
|
54
55
|
end
|
@@ -144,6 +145,14 @@ context "A PostgreSQL dataset with a timestamp field" do
|
|
144
145
|
end
|
145
146
|
end
|
146
147
|
|
148
|
+
context "PostgreSQL's EXPLAIN and ANALYZE" do
|
149
|
+
specify "should not raise errors" do
|
150
|
+
@d = POSTGRES_DB[:test3]
|
151
|
+
proc{@d.explain}.should_not raise_error
|
152
|
+
proc{@d.analyze}.should_not raise_error
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
147
156
|
context "A PostgreSQL database" do
|
148
157
|
before do
|
149
158
|
@db = POSTGRES_DB
|
@@ -208,6 +217,15 @@ context "A PostgreSQL database" do
|
|
208
217
|
@db.reset_primary_key_sequence(:posts).should == nil
|
209
218
|
end
|
210
219
|
|
220
|
+
|
221
|
+
specify "should support opclass specification" do
|
222
|
+
@db.create_table(:posts){text :title; text :body; integer :user_id; index(:user_id, :opclass => :int4_ops, :type => :btree)}
|
223
|
+
@db.sqls.should == [
|
224
|
+
"CREATE TABLE posts (title text, body text, user_id integer)",
|
225
|
+
"CREATE INDEX posts_user_id_index ON posts USING btree (user_id int4_ops)"
|
226
|
+
]
|
227
|
+
end
|
228
|
+
|
211
229
|
specify "should support fulltext indexes and searching" do
|
212
230
|
@db.create_table(:posts){text :title; text :body; full_text_index [:title, :body]; full_text_index :title, :language => 'french'}
|
213
231
|
@db.sqls.should == [
|
@@ -515,6 +533,36 @@ if POSTGRES_DB.server_version >= 80300
|
|
515
533
|
end
|
516
534
|
end
|
517
535
|
|
536
|
+
if POSTGRES_DB.dataset.supports_window_functions?
|
537
|
+
context "Postgres::Dataset named windows" do
|
538
|
+
before do
|
539
|
+
@db = POSTGRES_DB
|
540
|
+
@db.create_table!(:i1){Integer :id; Integer :group_id; Integer :amount}
|
541
|
+
@ds = @db[:i1].order(:id)
|
542
|
+
@ds.insert(:id=>1, :group_id=>1, :amount=>1)
|
543
|
+
@ds.insert(:id=>2, :group_id=>1, :amount=>10)
|
544
|
+
@ds.insert(:id=>3, :group_id=>1, :amount=>100)
|
545
|
+
@ds.insert(:id=>4, :group_id=>2, :amount=>1000)
|
546
|
+
@ds.insert(:id=>5, :group_id=>2, :amount=>10000)
|
547
|
+
@ds.insert(:id=>6, :group_id=>2, :amount=>100000)
|
548
|
+
end
|
549
|
+
after do
|
550
|
+
@db.drop_table(:i1)
|
551
|
+
end
|
552
|
+
|
553
|
+
specify "should give correct results for window functions" do
|
554
|
+
@ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:over, :args=>amount, :window=>win){}}.all.should ==
|
555
|
+
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
556
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>win, :order=>id){}}.all.should ==
|
557
|
+
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
558
|
+
@ds.window(:win, {}).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id){}}.all.should ==
|
559
|
+
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
560
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id, :frame=>:all){}}.all.should ==
|
561
|
+
[{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
518
566
|
context "Postgres::Database functions, languages, and triggers" do
|
519
567
|
before do
|
520
568
|
@d = POSTGRES_DB
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
unless Object.const_defined?('Sequel')
|
3
3
|
$:.unshift(File.join(File.dirname(__FILE__), "../../lib/"))
|
4
|
-
require 'sequel
|
4
|
+
require 'sequel'
|
5
5
|
Sequel.quote_identifiers = false
|
6
6
|
end
|
7
7
|
begin
|
8
|
-
require File.join(File.dirname(__FILE__), '
|
8
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'spec_config.rb')
|
9
9
|
rescue LoadError
|
10
10
|
end
|
@@ -4,6 +4,7 @@ unless defined?(SQLITE_DB)
|
|
4
4
|
SQLITE_URL = 'sqlite:/' unless defined? SQLITE_URL
|
5
5
|
SQLITE_DB = Sequel.connect(ENV['SEQUEL_SQLITE_SPEC_DB']||SQLITE_URL)
|
6
6
|
end
|
7
|
+
INTEGRATION_DB = SQLITE_DB unless defined?(INTEGRATION_DB)
|
7
8
|
|
8
9
|
context "An SQLite database" do
|
9
10
|
before do
|
@@ -94,7 +95,7 @@ context "An SQLite database" do
|
|
94
95
|
|
95
96
|
specify "should correctly parse the schema" do
|
96
97
|
@db.create_table!(:time2) {timestamp :t}
|
97
|
-
@db.schema(:time2, :reload=>true).should == [[:t, {:type=>:datetime, :allow_null=>true, :default=>nil, :db_type=>"timestamp", :primary_key=>false}]]
|
98
|
+
@db.schema(:time2, :reload=>true).should == [[:t, {:type=>:datetime, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"timestamp", :primary_key=>false}]]
|
98
99
|
end
|
99
100
|
end
|
100
101
|
|
@@ -20,6 +20,22 @@ context "An empty ConnectionPool" do
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
context "ConnectionPool options" do
|
24
|
+
specify "should support string option values" do
|
25
|
+
cpool = Sequel::ConnectionPool.new({:max_connections=>'5', :pool_timeout=>'3', :pool_sleep_time=>'0.01'})
|
26
|
+
cpool.max_size.should == 5
|
27
|
+
cpool.instance_variable_get(:@timeout).should == 3
|
28
|
+
cpool.instance_variable_get(:@sleep_time).should == 0.01
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "should raise an error unless size is positive" do
|
32
|
+
lambda{Sequel::ConnectionPool.new(:max_connections=>0)}.should raise_error(Sequel::Error)
|
33
|
+
lambda{Sequel::ConnectionPool.new(:max_connections=>-10)}.should raise_error(Sequel::Error)
|
34
|
+
lambda{Sequel::ConnectionPool.new(:max_connections=>'-10')}.should raise_error(Sequel::Error)
|
35
|
+
lambda{Sequel::ConnectionPool.new(:max_connections=>'0')}.should raise_error(Sequel::Error)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
23
39
|
context "A connection pool handling connections" do
|
24
40
|
before do
|
25
41
|
@max_size = 2
|
data/spec/core/database_spec.rb
CHANGED
@@ -39,8 +39,16 @@ context "A new Database" do
|
|
39
39
|
specify "should respect the :single_threaded option" do
|
40
40
|
db = Sequel::Database.new(:single_threaded=>true)
|
41
41
|
db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
|
42
|
+
db = Sequel::Database.new(:single_threaded=>'t')
|
43
|
+
db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
|
44
|
+
db = Sequel::Database.new(:single_threaded=>'1')
|
45
|
+
db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
|
42
46
|
db = Sequel::Database.new(:single_threaded=>false)
|
43
47
|
db.pool.should be_a_kind_of(Sequel::ConnectionPool)
|
48
|
+
db = Sequel::Database.new(:single_threaded=>'f')
|
49
|
+
db.pool.should be_a_kind_of(Sequel::ConnectionPool)
|
50
|
+
db = Sequel::Database.new(:single_threaded=>'0')
|
51
|
+
db.pool.should be_a_kind_of(Sequel::ConnectionPool)
|
44
52
|
end
|
45
53
|
|
46
54
|
specify "should respect the :quote_identifiers option" do
|
@@ -640,6 +648,106 @@ context "Database#transaction" do
|
|
640
648
|
end
|
641
649
|
end
|
642
650
|
|
651
|
+
context "Database#transaction with savepoints" do
|
652
|
+
before do
|
653
|
+
@db = Dummy3Database.new
|
654
|
+
@db.meta_def(:supports_savepoints?){true}
|
655
|
+
@db.pool.connection_proc = proc {Dummy3Database::DummyConnection.new(@db)}
|
656
|
+
end
|
657
|
+
|
658
|
+
specify "should wrap the supplied block with BEGIN + COMMIT statements" do
|
659
|
+
@db.transaction {@db.execute 'DROP TABLE test;'}
|
660
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test;', 'COMMIT']
|
661
|
+
end
|
662
|
+
|
663
|
+
specify "should use savepoints if given the :savepoint option" do
|
664
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.execute 'DROP TABLE test;'}}
|
665
|
+
@db.sql.should == ['BEGIN', 'SAVEPOINT autopoint_1', 'DROP TABLE test;', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
666
|
+
end
|
667
|
+
|
668
|
+
specify "should not use a savepoints if no transaction is in progress" do
|
669
|
+
@db.transaction(:savepoint=>true){@db.execute 'DROP TABLE test;'}
|
670
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test;', 'COMMIT']
|
671
|
+
end
|
672
|
+
|
673
|
+
specify "should reuse the current transaction if no :savepoint option is given" do
|
674
|
+
@db.transaction{@db.transaction{@db.execute 'DROP TABLE test;'}}
|
675
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test;', 'COMMIT']
|
676
|
+
end
|
677
|
+
|
678
|
+
specify "should handle returning inside of the block by committing" do
|
679
|
+
def @db.ret_commit
|
680
|
+
transaction do
|
681
|
+
execute 'DROP TABLE test;'
|
682
|
+
return
|
683
|
+
execute 'DROP TABLE test2;';
|
684
|
+
end
|
685
|
+
end
|
686
|
+
@db.ret_commit
|
687
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test;', 'COMMIT']
|
688
|
+
end
|
689
|
+
|
690
|
+
specify "should handle returning inside of a savepoint by committing" do
|
691
|
+
def @db.ret_commit
|
692
|
+
transaction do
|
693
|
+
transaction(:savepoint=>true) do
|
694
|
+
execute 'DROP TABLE test;'
|
695
|
+
return
|
696
|
+
execute 'DROP TABLE test2;';
|
697
|
+
end
|
698
|
+
end
|
699
|
+
end
|
700
|
+
@db.ret_commit
|
701
|
+
@db.sql.should == ['BEGIN', 'SAVEPOINT autopoint_1', 'DROP TABLE test;', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
702
|
+
end
|
703
|
+
|
704
|
+
specify "should issue ROLLBACK if an exception is raised, and re-raise" do
|
705
|
+
@db.transaction {@db.execute 'DROP TABLE test'; raise RuntimeError} rescue nil
|
706
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test', 'ROLLBACK']
|
707
|
+
|
708
|
+
proc {@db.transaction {raise RuntimeError}}.should raise_error(RuntimeError)
|
709
|
+
end
|
710
|
+
|
711
|
+
specify "should issue ROLLBACK SAVEPOINT if an exception is raised inside a savepoint, and re-raise" do
|
712
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.execute 'DROP TABLE test'; raise RuntimeError}} rescue nil
|
713
|
+
@db.sql.should == ['BEGIN', 'SAVEPOINT autopoint_1', 'DROP TABLE test', 'ROLLBACK TO SAVEPOINT autopoint_1', 'ROLLBACK']
|
714
|
+
|
715
|
+
proc {@db.transaction {raise RuntimeError}}.should raise_error(RuntimeError)
|
716
|
+
end
|
717
|
+
|
718
|
+
specify "should issue ROLLBACK if Sequel::Rollback is called in the transaction" do
|
719
|
+
@db.transaction do
|
720
|
+
@db.drop_table(:a)
|
721
|
+
raise Sequel::Rollback
|
722
|
+
@db.drop_table(:b)
|
723
|
+
end
|
724
|
+
|
725
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE a', 'ROLLBACK']
|
726
|
+
end
|
727
|
+
|
728
|
+
specify "should issue ROLLBACK SAVEPOINT if Sequel::Rollback is called in a savepoint" do
|
729
|
+
@db.transaction do
|
730
|
+
@db.transaction(:savepoint=>true) do
|
731
|
+
@db.drop_table(:a)
|
732
|
+
raise Sequel::Rollback
|
733
|
+
end
|
734
|
+
@db.drop_table(:b)
|
735
|
+
end
|
736
|
+
|
737
|
+
@db.sql.should == ['BEGIN', 'SAVEPOINT autopoint_1', 'DROP TABLE a', 'ROLLBACK TO SAVEPOINT autopoint_1', 'DROP TABLE b', 'COMMIT']
|
738
|
+
end
|
739
|
+
|
740
|
+
specify "should raise database errors when commiting a transaction as Sequel::DatabaseError" do
|
741
|
+
@db.meta_def(:commit_transaction){raise ArgumentError}
|
742
|
+
lambda{@db.transaction{}}.should raise_error(ArgumentError)
|
743
|
+
lambda{@db.transaction{@db.transaction(:savepoint=>true){}}}.should raise_error(ArgumentError)
|
744
|
+
|
745
|
+
@db.meta_def(:database_error_classes){[ArgumentError]}
|
746
|
+
lambda{@db.transaction{}}.should raise_error(Sequel::DatabaseError)
|
747
|
+
lambda{@db.transaction{@db.transaction(:savepoint=>true){}}}.should raise_error(Sequel::DatabaseError)
|
748
|
+
end
|
749
|
+
end
|
750
|
+
|
643
751
|
context "A Database adapter with a scheme" do
|
644
752
|
before do
|
645
753
|
class CCC < Sequel::Database
|
@@ -740,6 +848,13 @@ context "A Database adapter with a scheme" do
|
|
740
848
|
|
741
849
|
end
|
742
850
|
|
851
|
+
context "Sequel::Database.connect" do
|
852
|
+
specify "should raise an Error if not given a String or Hash" do
|
853
|
+
proc{Sequel::Database.connect(nil)}.should raise_error(Sequel::Error)
|
854
|
+
proc{Sequel::Database.connect(Object.new)}.should raise_error(Sequel::Error)
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
743
858
|
context "An unknown database scheme" do
|
744
859
|
specify "should raise an error in Sequel::Database.connect" do
|
745
860
|
proc {Sequel::Database.connect('ddd://localhost/db')}.should raise_error(Sequel::AdapterNotFound)
|
@@ -1181,3 +1296,62 @@ context "Database#metadata_dataset" do
|
|
1181
1296
|
ds.send(:output_identifier, 'A').should == :a
|
1182
1297
|
end
|
1183
1298
|
end
|
1299
|
+
|
1300
|
+
context "Database#column_schema_to_ruby_default" do
|
1301
|
+
specify "should handle converting many default formats" do
|
1302
|
+
db = Sequel::Database.new
|
1303
|
+
m = db.method(:column_schema_to_ruby_default)
|
1304
|
+
p = lambda{|d,t| m.call(d,t)}
|
1305
|
+
p[nil, :integer].should == nil
|
1306
|
+
p['1', :integer].should == 1
|
1307
|
+
p['-1', :integer].should == -1
|
1308
|
+
p['1.0', :float].should == 1.0
|
1309
|
+
p['-1.0', :float].should == -1.0
|
1310
|
+
p['1.0', :decimal].should == BigDecimal.new('1.0')
|
1311
|
+
p['-1.0', :decimal].should == BigDecimal.new('-1.0')
|
1312
|
+
p['1', :boolean].should == true
|
1313
|
+
p['0', :boolean].should == false
|
1314
|
+
p['true', :boolean].should == true
|
1315
|
+
p['false', :boolean].should == false
|
1316
|
+
p["'t'", :boolean].should == true
|
1317
|
+
p["'f'", :boolean].should == false
|
1318
|
+
p["'a'", :string].should == 'a'
|
1319
|
+
p["'a'", :blob].should == 'a'.to_sequel_blob
|
1320
|
+
p["'a'", :blob].should be_a_kind_of(Sequel::SQL::Blob)
|
1321
|
+
p["''", :string].should == ''
|
1322
|
+
p["'\\a''b'", :string].should == "\\a'b"
|
1323
|
+
p["'NULL'", :string].should == "NULL"
|
1324
|
+
p["'2009-10-29'", :date].should == Date.new(2009,10,29)
|
1325
|
+
p["CURRENT_TIMESTAMP", :date].should == nil
|
1326
|
+
p["today()", :date].should == nil
|
1327
|
+
p["'2009-10-29T10:20:30-07:00'", :datetime].should == DateTime.parse('2009-10-29T10:20:30-07:00')
|
1328
|
+
p["'2009-10-29 10:20:30'", :datetime].should == DateTime.parse('2009-10-29 10:20:30')
|
1329
|
+
p["'10:20:30'", :time].should == Time.parse('10:20:30')
|
1330
|
+
p["NaN", :float].should == nil
|
1331
|
+
|
1332
|
+
db.meta_def(:database_type){:postgres}
|
1333
|
+
p["''::text", :string].should == ""
|
1334
|
+
p["'\\a''b'::character varying", :string].should == "\\a'b"
|
1335
|
+
p["'a'::bpchar", :string].should == "a"
|
1336
|
+
p["(-1)", :integer].should == -1
|
1337
|
+
p["(-1.0)", :float].should == -1.0
|
1338
|
+
p['(-1.0)', :decimal].should == BigDecimal.new('-1.0')
|
1339
|
+
p["'a'::bytea", :blob].should == 'a'.to_sequel_blob
|
1340
|
+
p["'a'::bytea", :blob].should be_a_kind_of(Sequel::SQL::Blob)
|
1341
|
+
p["'2009-10-29'::date", :date].should == Date.new(2009,10,29)
|
1342
|
+
p["'2009-10-29 10:20:30.241343'::timestamp without time zone", :datetime].should == DateTime.parse('2009-10-29 10:20:30.241343')
|
1343
|
+
p["'10:20:30'::time without time zone", :time].should == Time.parse('10:20:30')
|
1344
|
+
|
1345
|
+
db.meta_def(:database_type){:mysql}
|
1346
|
+
p["\\a'b", :string].should == "\\a'b"
|
1347
|
+
p["a", :string].should == "a"
|
1348
|
+
p["NULL", :string].should == "NULL"
|
1349
|
+
p["-1", :float].should == -1.0
|
1350
|
+
p['-1', :decimal].should == BigDecimal.new('-1.0')
|
1351
|
+
p["2009-10-29", :date].should == Date.new(2009,10,29)
|
1352
|
+
p["2009-10-29 10:20:30", :datetime].should == DateTime.parse('2009-10-29 10:20:30')
|
1353
|
+
p["10:20:30", :time].should == Time.parse('10:20:30')
|
1354
|
+
p["CURRENT_DATE", :date].should == nil
|
1355
|
+
p["CURRENT_TIMESTAMP", :datetime].should == nil
|
1356
|
+
end
|
1357
|
+
end
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -758,6 +758,22 @@ context "Dataset#literal" do
|
|
758
758
|
s = d.strftime("'%Y-%m-%d'")
|
759
759
|
@dataset.literal(d).should == s
|
760
760
|
end
|
761
|
+
|
762
|
+
specify "should literalize Time, DateTime, Date properly if SQL standard format is required" do
|
763
|
+
@dataset.meta_def(:requires_sql_standard_datetimes?){true}
|
764
|
+
|
765
|
+
t = Time.now
|
766
|
+
s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'")
|
767
|
+
@dataset.literal(t).should == s
|
768
|
+
|
769
|
+
t = DateTime.now
|
770
|
+
s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'")
|
771
|
+
@dataset.literal(t).should == s
|
772
|
+
|
773
|
+
d = Date.today
|
774
|
+
s = d.strftime("DATE '%Y-%m-%d'")
|
775
|
+
@dataset.literal(d).should == s
|
776
|
+
end
|
761
777
|
|
762
778
|
specify "should not modify literal strings" do
|
763
779
|
@dataset.literal('col1 + 2'.lit).should == 'col1 + 2'
|
@@ -1249,6 +1265,12 @@ context "Dataset#distinct" do
|
|
1249
1265
|
@dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
|
1250
1266
|
end
|
1251
1267
|
|
1268
|
+
specify "should raise an error if columns given and distinct on not supported" do
|
1269
|
+
@dataset.meta_def(:supports_distinct_on?){false}
|
1270
|
+
proc{@dataset.distinct}.should_not raise_error
|
1271
|
+
proc{@dataset.distinct(:a)}.should raise_error(Sequel::InvalidOperation)
|
1272
|
+
end
|
1273
|
+
|
1252
1274
|
specify "should accept an expression list" do
|
1253
1275
|
@dataset.distinct(:a, :b).sql.should == 'SELECT DISTINCT ON (a, b) name FROM test'
|
1254
1276
|
@dataset.distinct(:stamp.cast(:integer), :node_id=>nil).sql.should == 'SELECT DISTINCT ON (CAST(stamp AS integer), (node_id IS NULL)) name FROM test'
|
@@ -1505,6 +1527,9 @@ context "Dataset#join_table" do
|
|
1505
1527
|
|
1506
1528
|
@d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
|
1507
1529
|
'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
|
1530
|
+
|
1531
|
+
@d.from_self.join_table(:left_outer, ds, :item_id => :id).sql.should ==
|
1532
|
+
'SELECT * FROM (SELECT * FROM "items") AS "t1" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t2" ON ("t2"."item_id" = "t1"."id")'
|
1508
1533
|
end
|
1509
1534
|
|
1510
1535
|
specify "should support joining datasets and aliasing the join" do
|
@@ -1861,6 +1886,8 @@ context "Dataset compound operations" do
|
|
1861
1886
|
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
|
1862
1887
|
@b.union(@a, true).sql.should == \
|
1863
1888
|
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
1889
|
+
@b.union(@a, :all=>true).sql.should == \
|
1890
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
1864
1891
|
end
|
1865
1892
|
|
1866
1893
|
specify "should support INTERSECT and INTERSECT ALL" do
|
@@ -1868,6 +1895,8 @@ context "Dataset compound operations" do
|
|
1868
1895
|
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
|
1869
1896
|
@b.intersect(@a, true).sql.should == \
|
1870
1897
|
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
1898
|
+
@b.intersect(@a, :all=>true).sql.should == \
|
1899
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
1871
1900
|
end
|
1872
1901
|
|
1873
1902
|
specify "should support EXCEPT and EXCEPT ALL" do
|
@@ -1875,6 +1904,40 @@ context "Dataset compound operations" do
|
|
1875
1904
|
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
|
1876
1905
|
@b.except(@a, true).sql.should == \
|
1877
1906
|
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
1907
|
+
@b.except(@a, :all=>true).sql.should == \
|
1908
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
1909
|
+
end
|
1910
|
+
|
1911
|
+
specify "should support :from_self=>false option to not wrap the compound in a SELECT * FROM (...)" do
|
1912
|
+
@b.union(@a, :from_self=>false).sql.should == \
|
1913
|
+
"SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
|
1914
|
+
@b.intersect(@a, :from_self=>false).sql.should == \
|
1915
|
+
"SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
|
1916
|
+
@b.except(@a, :from_self=>false).sql.should == \
|
1917
|
+
"SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
|
1918
|
+
|
1919
|
+
@b.union(@a, :from_self=>false, :all=>true).sql.should == \
|
1920
|
+
"SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
|
1921
|
+
@b.intersect(@a, :from_self=>false, :all=>true).sql.should == \
|
1922
|
+
"SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
|
1923
|
+
@b.except(@a, :from_self=>false, :all=>true).sql.should == \
|
1924
|
+
"SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
|
1925
|
+
end
|
1926
|
+
|
1927
|
+
specify "should raise an InvalidOperation if INTERSECT or EXCEPT is used and they are not supported" do
|
1928
|
+
@a.meta_def(:supports_intersect_except?){false}
|
1929
|
+
proc{@a.intersect(@b)}.should raise_error(Sequel::InvalidOperation)
|
1930
|
+
proc{@a.intersect(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
1931
|
+
proc{@a.except(@b)}.should raise_error(Sequel::InvalidOperation)
|
1932
|
+
proc{@a.except(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
1933
|
+
end
|
1934
|
+
|
1935
|
+
specify "should raise an InvalidOperation if INTERSECT ALL or EXCEPT ALL is used and they are not supported" do
|
1936
|
+
@a.meta_def(:supports_intersect_except_all?){false}
|
1937
|
+
proc{@a.intersect(@b)}.should_not raise_error
|
1938
|
+
proc{@a.intersect(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
1939
|
+
proc{@a.except(@b)}.should_not raise_error
|
1940
|
+
proc{@a.except(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
1878
1941
|
end
|
1879
1942
|
|
1880
1943
|
specify "should handle chained compound operations" do
|
@@ -2449,29 +2512,6 @@ class DummyMummyDatabase < Sequel::Database
|
|
2449
2512
|
end
|
2450
2513
|
end
|
2451
2514
|
|
2452
|
-
context "Dataset#table_exists?" do
|
2453
|
-
before do
|
2454
|
-
@db = DummyMummyDatabase.new
|
2455
|
-
end
|
2456
|
-
|
2457
|
-
specify "should otherwise try to select the first record from the table's dataset" do
|
2458
|
-
@db[:a].table_exists?.should be_false
|
2459
|
-
@db[:b].table_exists?.should be_true
|
2460
|
-
end
|
2461
|
-
|
2462
|
-
specify "should raise Sequel::Error if dataset references more than one table" do
|
2463
|
-
proc {@db.from(:a, :b).table_exists?}.should raise_error(Sequel::Error)
|
2464
|
-
end
|
2465
|
-
|
2466
|
-
specify "should raise Sequel::Error if dataset is from a subquery" do
|
2467
|
-
proc {@db.from(@db[:a]).table_exists?}.should raise_error(Sequel::Error)
|
2468
|
-
end
|
2469
|
-
|
2470
|
-
specify "should raise Sequel::Error if dataset has fixed sql" do
|
2471
|
-
proc {@db['select * from blah'].table_exists?}.should raise_error(Sequel::Error)
|
2472
|
-
end
|
2473
|
-
end
|
2474
|
-
|
2475
2515
|
context "Dataset#inspect" do
|
2476
2516
|
before do
|
2477
2517
|
@ds = Sequel::Dataset.new(nil).from(:blah)
|
@@ -2782,8 +2822,18 @@ context "Sequel::Dataset #set_overrides" do
|
|
2782
2822
|
end
|
2783
2823
|
end
|
2784
2824
|
|
2825
|
+
context "Sequel::Dataset#qualify" do
|
2826
|
+
specify "should qualify to the given table" do
|
2827
|
+
MockDatabase.new[:t].filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
|
2828
|
+
end
|
2829
|
+
|
2830
|
+
specify "should qualify to the first source if no table if given" do
|
2831
|
+
MockDatabase.new[:t].filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
|
2832
|
+
end
|
2833
|
+
end
|
2834
|
+
|
2785
2835
|
context "Sequel::Dataset#qualify_to" do
|
2786
|
-
specify "should
|
2836
|
+
specify "should qualify to the given table" do
|
2787
2837
|
MockDatabase.new[:t].filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
|
2788
2838
|
end
|
2789
2839
|
end
|
@@ -2861,8 +2911,53 @@ context "Sequel::Dataset#qualify_to_first_source" do
|
|
2861
2911
|
@ds.filter{a.sql_subscript(b,3)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE t.a[t.b, 3]'
|
2862
2912
|
end
|
2863
2913
|
|
2914
|
+
specify "should handle SQL::PlaceholderLiteralStrings" do
|
2915
|
+
@ds.filter('? > ?', :a, 1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a > 1)'
|
2916
|
+
end
|
2917
|
+
|
2918
|
+
specify "should handle SQL::WindowFunctions" do
|
2919
|
+
@ds.meta_def(:supports_window_functions?){true}
|
2920
|
+
@ds.select{sum(:over, :args=>:a, :partition=>:b, :order=>:c){}}.qualify_to_first_source.sql.should == 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
|
2921
|
+
end
|
2922
|
+
|
2864
2923
|
specify "should handle all other objects by returning them unchanged" do
|
2865
|
-
@ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('?'.lit(
|
2866
|
-
"SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a HAVING 'f' ORDER BY true"
|
2924
|
+
@ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('a > ?'.lit(1)).having(false).qualify_to_first_source.sql.should == \
|
2925
|
+
"SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
|
2867
2926
|
end
|
2868
2927
|
end
|
2928
|
+
|
2929
|
+
context "Sequel::Dataset #with and #with_recursive" do
|
2930
|
+
before do
|
2931
|
+
@db = MockDatabase.new
|
2932
|
+
@ds = @db[:t]
|
2933
|
+
end
|
2934
|
+
|
2935
|
+
specify "#with should take a name and dataset and use a WITH clause" do
|
2936
|
+
@ds.with(:t, @db[:x]).sql.should == 'WITH t AS (SELECT * FROM x) SELECT * FROM t'
|
2937
|
+
end
|
2938
|
+
|
2939
|
+
specify "#with_recursive should take a name, nonrecursive dataset, and recursive dataset, and use a WITH clause" do
|
2940
|
+
@ds.with_recursive(:t, @db[:x], @db[:t]).sql.should == 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
|
2941
|
+
end
|
2942
|
+
|
2943
|
+
specify "#with and #with_recursive should add to existing WITH clause if called multiple times" do
|
2944
|
+
@ds.with(:t, @db[:x]).with(:j, @db[:y]).sql.should == 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
|
2945
|
+
@ds.with_recursive(:t, @db[:x], @db[:t]).with_recursive(:j, @db[:y], @db[:j]).sql.should == 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
|
2946
|
+
@ds.with(:t, @db[:x]).with_recursive(:j, @db[:y], @db[:j]).sql.should == 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
|
2947
|
+
end
|
2948
|
+
|
2949
|
+
specify "#with and #with_recursive should take an :args option" do
|
2950
|
+
@ds.with(:t, @db[:x], :args=>[:b]).sql.should == 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
|
2951
|
+
@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c]).sql.should == 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
|
2952
|
+
end
|
2953
|
+
|
2954
|
+
specify "#with_recursive should take an :union_all=>false option" do
|
2955
|
+
@ds.with_recursive(:t, @db[:x], @db[:t], :union_all=>false).sql.should == 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t'
|
2956
|
+
end
|
2957
|
+
|
2958
|
+
specify "#with and #with_recursive should raise an error unless the dataset supports CTEs" do
|
2959
|
+
@ds.meta_def(:supports_cte?){false}
|
2960
|
+
proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
|
2961
|
+
proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
|
2962
|
+
end
|
2963
|
+
end
|