sequel 3.1.0 → 3.2.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.
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