sequel 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG +76 -0
  2. data/Rakefile +2 -2
  3. data/bin/sequel +9 -4
  4. data/doc/opening_databases.rdoc +279 -0
  5. data/doc/release_notes/3.2.0.txt +268 -0
  6. data/doc/virtual_rows.rdoc +42 -51
  7. data/lib/sequel/adapters/ado.rb +2 -5
  8. data/lib/sequel/adapters/db2.rb +5 -0
  9. data/lib/sequel/adapters/do.rb +3 -0
  10. data/lib/sequel/adapters/firebird.rb +6 -4
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +10 -8
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -4
  14. data/lib/sequel/adapters/mysql.rb +6 -19
  15. data/lib/sequel/adapters/odbc.rb +14 -18
  16. data/lib/sequel/adapters/openbase.rb +8 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +14 -8
  18. data/lib/sequel/adapters/shared/mysql.rb +53 -28
  19. data/lib/sequel/adapters/shared/oracle.rb +21 -12
  20. data/lib/sequel/adapters/shared/postgres.rb +46 -26
  21. data/lib/sequel/adapters/shared/progress.rb +10 -5
  22. data/lib/sequel/adapters/shared/sqlite.rb +28 -12
  23. data/lib/sequel/adapters/sqlite.rb +4 -3
  24. data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
  25. data/lib/sequel/connection_pool.rb +4 -3
  26. data/lib/sequel/database.rb +110 -10
  27. data/lib/sequel/database/schema_sql.rb +12 -3
  28. data/lib/sequel/dataset.rb +40 -3
  29. data/lib/sequel/dataset/convenience.rb +0 -11
  30. data/lib/sequel/dataset/graph.rb +25 -11
  31. data/lib/sequel/dataset/sql.rb +176 -68
  32. data/lib/sequel/extensions/migration.rb +37 -21
  33. data/lib/sequel/extensions/schema_dumper.rb +8 -61
  34. data/lib/sequel/model.rb +3 -3
  35. data/lib/sequel/model/associations.rb +9 -1
  36. data/lib/sequel/model/base.rb +8 -1
  37. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  38. data/lib/sequel/sql.rb +125 -18
  39. data/lib/sequel/version.rb +1 -1
  40. data/spec/adapters/ado_spec.rb +1 -0
  41. data/spec/adapters/firebird_spec.rb +1 -0
  42. data/spec/adapters/informix_spec.rb +1 -0
  43. data/spec/adapters/mysql_spec.rb +23 -8
  44. data/spec/adapters/oracle_spec.rb +1 -0
  45. data/spec/adapters/postgres_spec.rb +52 -4
  46. data/spec/adapters/spec_helper.rb +2 -2
  47. data/spec/adapters/sqlite_spec.rb +2 -1
  48. data/spec/core/connection_pool_spec.rb +16 -0
  49. data/spec/core/database_spec.rb +174 -0
  50. data/spec/core/dataset_spec.rb +121 -26
  51. data/spec/core/expression_filters_spec.rb +156 -0
  52. data/spec/core/object_graph_spec.rb +20 -1
  53. data/spec/core/schema_spec.rb +5 -5
  54. data/spec/extensions/migration_spec.rb +140 -74
  55. data/spec/extensions/schema_dumper_spec.rb +3 -69
  56. data/spec/extensions/single_table_inheritance_spec.rb +6 -0
  57. data/spec/integration/dataset_test.rb +84 -2
  58. data/spec/integration/schema_test.rb +24 -5
  59. data/spec/integration/spec_helper.rb +8 -6
  60. data/spec/model/eager_loading_spec.rb +9 -0
  61. data/spec/model/record_spec.rb +35 -8
  62. metadata +8 -7
  63. data/lib/sequel/adapters/utils/date_format.rb +0 -21
  64. data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
  65. 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/core'
4
+ require 'sequel'
5
5
  Sequel.quote_identifiers = false
6
6
  end
7
7
  begin
8
- require File.join(File.dirname(__FILE__), '../spec_config.rb')
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
@@ -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
@@ -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 qualify_to the first source" do
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(:a)).having(false).qualify_to_first_source.sql.should == \
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