sequel 2.7.1 → 2.8.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 (50) hide show
  1. data/CHANGELOG +56 -0
  2. data/README +1 -0
  3. data/Rakefile +1 -1
  4. data/lib/sequel_core.rb +9 -16
  5. data/lib/sequel_core/adapters/ado.rb +6 -15
  6. data/lib/sequel_core/adapters/db2.rb +8 -10
  7. data/lib/sequel_core/adapters/dbi.rb +6 -4
  8. data/lib/sequel_core/adapters/informix.rb +21 -22
  9. data/lib/sequel_core/adapters/jdbc.rb +69 -10
  10. data/lib/sequel_core/adapters/jdbc/postgresql.rb +1 -0
  11. data/lib/sequel_core/adapters/mysql.rb +81 -13
  12. data/lib/sequel_core/adapters/odbc.rb +32 -4
  13. data/lib/sequel_core/adapters/openbase.rb +6 -5
  14. data/lib/sequel_core/adapters/oracle.rb +23 -7
  15. data/lib/sequel_core/adapters/postgres.rb +42 -32
  16. data/lib/sequel_core/adapters/shared/mssql.rb +37 -62
  17. data/lib/sequel_core/adapters/shared/mysql.rb +22 -7
  18. data/lib/sequel_core/adapters/shared/oracle.rb +27 -48
  19. data/lib/sequel_core/adapters/shared/postgres.rb +64 -43
  20. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  21. data/lib/sequel_core/adapters/shared/sqlite.rb +15 -4
  22. data/lib/sequel_core/adapters/sqlite.rb +6 -14
  23. data/lib/sequel_core/connection_pool.rb +47 -13
  24. data/lib/sequel_core/database.rb +60 -35
  25. data/lib/sequel_core/database/schema.rb +4 -4
  26. data/lib/sequel_core/dataset.rb +12 -3
  27. data/lib/sequel_core/dataset/convenience.rb +4 -13
  28. data/lib/sequel_core/dataset/prepared_statements.rb +30 -28
  29. data/lib/sequel_core/dataset/sql.rb +144 -85
  30. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  31. data/lib/sequel_core/dataset/unsupported.rb +31 -0
  32. data/lib/sequel_core/exceptions.rb +6 -0
  33. data/lib/sequel_core/schema/generator.rb +4 -3
  34. data/lib/sequel_core/schema/sql.rb +41 -23
  35. data/lib/sequel_core/sql.rb +29 -1
  36. data/lib/sequel_model/associations.rb +1 -1
  37. data/lib/sequel_model/record.rb +31 -28
  38. data/spec/adapters/mysql_spec.rb +37 -4
  39. data/spec/adapters/oracle_spec.rb +26 -4
  40. data/spec/adapters/sqlite_spec.rb +7 -0
  41. data/spec/integration/prepared_statement_test.rb +24 -0
  42. data/spec/integration/schema_test.rb +1 -1
  43. data/spec/sequel_core/connection_pool_spec.rb +49 -2
  44. data/spec/sequel_core/core_sql_spec.rb +9 -2
  45. data/spec/sequel_core/database_spec.rb +64 -14
  46. data/spec/sequel_core/dataset_spec.rb +105 -7
  47. data/spec/sequel_core/schema_spec.rb +40 -12
  48. data/spec/sequel_core/spec_helper.rb +1 -0
  49. data/spec/sequel_model/spec_helper.rb +1 -0
  50. metadata +6 -3
@@ -12,7 +12,6 @@ unless defined?(MYSQL_SOCKET_FILE)
12
12
  end
13
13
 
14
14
  MYSQL_URI = URI.parse(MYSQL_DB.uri)
15
- MYSQL_DB_NAME = (m = /\/(.*)/.match(MYSQL_URI.path)) && m[1]
16
15
 
17
16
  MYSQL_DB.create_table! :items do
18
17
  text :name
@@ -482,23 +481,32 @@ context "A MySQL database" do
482
481
  @db << 'DELETE FROM items'
483
482
  @db[:items].first.should == nil
484
483
  end
484
+
485
+ specify "should handle multiple select statements at once" do
486
+ @db << 'DELETE FROM items; '
487
+
488
+ @db[:items].delete
489
+ @db[:items].insert(:name => 'tutu', :value => 1234)
490
+ @db["SELECT * FROM items; SELECT * FROM items"].all.should == \
491
+ [{:name => 'tutu', :value => 1234}, {:name => 'tutu', :value => 1234}]
492
+ end
485
493
  end
486
494
 
487
495
  # Socket tests should only be run if the MySQL server is on localhost
488
496
  if %w'localhost 127.0.0.1 ::1'.include? MYSQL_URI.host
489
497
  context "A MySQL database" do
490
498
  specify "should accept a socket option" do
491
- db = Sequel.mysql(MYSQL_DB_NAME, :host => 'localhost', :user => MYSQL_USER, :socket => MYSQL_SOCKET_FILE)
499
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :host => 'localhost', :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
492
500
  proc {db.test_connection}.should_not raise_error
493
501
  end
494
502
 
495
503
  specify "should accept a socket option without host option" do
496
- db = Sequel.mysql(MYSQL_DB_NAME, :user => MYSQL_USER, :socket => MYSQL_SOCKET_FILE)
504
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
497
505
  proc {db.test_connection}.should_not raise_error
498
506
  end
499
507
 
500
508
  specify "should fail to connect with invalid socket" do
501
- db = Sequel.mysql(MYSQL_DB_NAME, :host => 'localhost', :user => MYSQL_USER, :socket => 'blah')
509
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket =>'blah')
502
510
  proc {db.test_connection}.should raise_error
503
511
  end
504
512
  end
@@ -761,3 +769,28 @@ context "MySQL::Dataset#complex_expression_sql" do
761
769
  @d.literal([:x].sql_string_join(' ')).should == "x"
762
770
  end
763
771
  end
772
+
773
+ context "MySQL Stored Procedures" do
774
+ teardown do
775
+ MYSQL_DB.execute('DROP PROCEDURE test_sproc')
776
+ end
777
+
778
+ specify "should be callable on the database object" do
779
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc() BEGIN DELETE FROM items; END')
780
+ MYSQL_DB[:items].delete
781
+ MYSQL_DB[:items].insert(:value=>1)
782
+ MYSQL_DB[:items].count.should == 1
783
+ MYSQL_DB.call_sproc(:test_sproc)
784
+ MYSQL_DB[:items].count.should == 0
785
+ end
786
+
787
+ specify "should be callable on the dataset object" do
788
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc(a INTEGER) BEGIN SELECT *, a AS b FROM items; END')
789
+ @d = MYSQL_DB[:items]
790
+ @d.call_sproc(:select, :test_sproc, 3).should == []
791
+ @d.insert(:value=>1)
792
+ @d.call_sproc(:select, :test_sproc, 4).should == [{:id=>nil, :value=>1, :b=>4}]
793
+ @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
794
+ @d.call_sproc(:select, :test_sproc, 3).should == [{:id=>nil, :value=>2, :b=>6}]
795
+ end
796
+ end
@@ -71,7 +71,7 @@ context "An Oracle dataset" do
71
71
  {:name => 'def'}
72
72
  ]
73
73
 
74
- @d.order(:value.DESC).limit(1).to_a.should == [
74
+ @d.order(:value.desc).limit(1).to_a.should == [
75
75
  {:name => 'def', :value => 789}
76
76
  ]
77
77
 
@@ -80,7 +80,7 @@ context "An Oracle dataset" do
80
80
  {:name => 'abc', :value => 456}
81
81
  ]
82
82
 
83
- @d.order(:value.DESC).filter(:name => 'abc').to_a.should == [
83
+ @d.order(:value.desc).filter(:name => 'abc').to_a.should == [
84
84
  {:name => 'abc', :value => 456},
85
85
  {:name => 'abc', :value => 123}
86
86
  ]
@@ -89,7 +89,7 @@ context "An Oracle dataset" do
89
89
  {:name => 'abc', :value => 123}
90
90
  ]
91
91
 
92
- @d.filter(:name => 'abc').order(:value.DESC).limit(1).to_a.should == [
92
+ @d.filter(:name => 'abc').order(:value.desc).limit(1).to_a.should == [
93
93
  {:name => 'abc', :value => 456}
94
94
  ]
95
95
 
@@ -214,9 +214,31 @@ context "Joined Oracle dataset" do
214
214
  {:id => 4, :title => 'ddd', :cat_name => nil}
215
215
  ]
216
216
 
217
- @d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id.DESC).limit(2, 0).to_a.should == [
217
+ @d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id.desc).limit(2, 0).to_a.should == [
218
218
  {:id => 4, :title => 'ddd', :cat_name => nil},
219
219
  {:id => 3, :title => 'ccc', :cat_name => 'rails'}
220
220
  ]
221
221
  end
222
222
  end
223
+
224
+ context "Oracle aliasing" do
225
+ setup do
226
+ @d1 = ORACLE_DB[:books]
227
+ @d1.delete # remove all records
228
+ @d1 << {:id => 1, :title => 'aaa', :category_id => 100}
229
+ @d1 << {:id => 2, :title => 'bbb', :category_id => 100}
230
+ @d1 << {:id => 3, :title => 'bbb', :category_id => 100}
231
+ end
232
+
233
+ specify "should allow columns to be renamed" do
234
+ @d1.select(:title.as(:name)).order_by(:id).to_a.should == [
235
+ { :name => 'aaa' },
236
+ { :name => 'bbb' },
237
+ { :name => 'bbb' },
238
+ ]
239
+ end
240
+
241
+ specify "nested queries should work" do
242
+ @d1.select(:title).group_by(:title).count.should == 2
243
+ end
244
+ end
@@ -415,6 +415,13 @@ context "A SQLite database" do
415
415
  @db[:test2].first.should == {:name => 'mmm'}
416
416
  end
417
417
 
418
+ specify "should support drop_column operations in a transaction" do
419
+ @db.transaction{@db.drop_column :test2, :value}
420
+ @db[:test2].columns.should == [:name]
421
+ @db[:test2] << {:name => 'mmm'}
422
+ @db[:test2].first.should == {:name => 'mmm'}
423
+ end
424
+
418
425
  specify "should not support rename_column operations" do
419
426
  proc {@db.rename_column :test2, :value, :zyx}.should raise_error(Sequel::Error)
420
427
  end
@@ -24,6 +24,30 @@ describe "Prepared Statements and Bound Arguments" do
24
24
  @ds.filter(:number=>@ds.ba(:$n)).call(:first, :n=>10).should == {:id=>1, :number=>10}
25
25
  end
26
26
 
27
+ specify "should support placeholder literal strings" do
28
+ @ds.filter("number = ?", @ds.ba(:$n)).call(:select, :n=>10).should == [{:id=>1, :number=>10}]
29
+ end
30
+
31
+ specify "should support datasets with static sql and placeholders" do
32
+ INTEGRATION_DB["SELECT * FROM items WHERE number = ?", @ds.ba(:$n)].call(:select, :n=>10).should == [{:id=>1, :number=>10}]
33
+ end
34
+
35
+ specify "should support subselects" do
36
+ @ds.filter(:id=>:$i).filter(:number=>@ds.select(:number).filter(:number=>@ds.ba(:$n))).filter(:id=>:$j).call(:select, :n=>10, :i=>1, :j=>1).should == [{:id=>1, :number=>10}]
37
+ end
38
+
39
+ specify "should support subselects with literal strings" do
40
+ @ds.filter(:id=>:$i, :number=>@ds.select(:number).filter("number = ?", @ds.ba(:$n))).call(:select, :n=>10, :i=>1).should == [{:id=>1, :number=>10}]
41
+ end
42
+
43
+ specify "should support subselects with static sql and placeholders" do
44
+ @ds.filter(:id=>:$i, :number=>INTEGRATION_DB["SELECT number FROM items WHERE number = ?", @ds.ba(:$n)]).call(:select, :n=>10, :i=>1).should == [{:id=>1, :number=>10}]
45
+ end
46
+
47
+ specify "should support subselects of subselects" do
48
+ @ds.filter(:id=>:$i).filter(:number=>@ds.select(:number).filter(:number=>@ds.select(:number).filter(:number=>@ds.ba(:$n)))).filter(:id=>:$j).call(:select, :n=>10, :i=>1, :j=>1).should == [{:id=>1, :number=>10}]
49
+ end
50
+
27
51
  specify "should support bound variables with insert" do
28
52
  @ds.call(:insert, {:n=>20, :i=>100}, :id=>@ds.ba(:$i), :number=>@ds.ba(:$n))
29
53
  @ds.count.should == 2
@@ -9,7 +9,7 @@ describe "Database schema parser" do
9
9
  INTEGRATION_DB.create_table!(:items){integer :number}
10
10
  schema = INTEGRATION_DB.schema(nil, :reload=>true)
11
11
  schema.should be_a_kind_of(Hash)
12
- schema.should include(:items)
12
+ schema[:items].should_not == nil
13
13
  end
14
14
 
15
15
  specify "should not issue an sql query if the schema has been loaded unless :reload is true" do
@@ -23,7 +23,7 @@ end
23
23
  context "A connection pool handling connections" do
24
24
  setup do
25
25
  @max_size = 2
26
- @cpool = Sequel::ConnectionPool.new(CONNECTION_POOL_DEFAULTS.merge(:max_connections=>@max_size)) {:got_connection}
26
+ @cpool = Sequel::ConnectionPool.new(CONNECTION_POOL_DEFAULTS.merge(:disconnection_proc=>proc{|c| @max_size=3}, :max_connections=>@max_size)) {:got_connection}
27
27
  end
28
28
 
29
29
  specify "#hold should increment #created_count" do
@@ -61,6 +61,31 @@ context "A connection pool handling connections" do
61
61
  @cpool.send(:make_new, :default).should == nil
62
62
  @cpool.created_count.should == 2
63
63
  end
64
+
65
+ specify ":disconnection_proc option should set the disconnection proc to use" do
66
+ @max_size.should == 2
67
+ proc{@cpool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
68
+ @max_size.should == 3
69
+ end
70
+
71
+ specify "#disconnection_proc= should set the disconnection proc to use" do
72
+ a = 1
73
+ @cpool.disconnection_proc = proc{|c| a += 1}
74
+ proc{@cpool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
75
+ a.should == 2
76
+ end
77
+
78
+ specify "#hold should remove the connection if a DatabaseDisconnectError is raised" do
79
+ @cpool.created_count.should == 0
80
+ @cpool.hold{Thread.new{@cpool.hold{}}; sleep 0.01}
81
+ @cpool.created_count.should == 2
82
+ proc{@cpool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
83
+ @cpool.created_count.should == 1
84
+ proc{@cpool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
85
+ @cpool.created_count.should == 0
86
+ proc{@cpool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
87
+ @cpool.created_count.should == 0
88
+ end
64
89
  end
65
90
 
66
91
  class DummyConnection
@@ -423,7 +448,8 @@ end
423
448
 
424
449
  context "A single threaded pool with multiple servers" do
425
450
  setup do
426
- @pool = Sequel::SingleThreadedPool.new(CONNECTION_POOL_DEFAULTS.merge(:servers=>{:read_only=>{}})){|server| server}
451
+ @max_size=2
452
+ @pool = Sequel::SingleThreadedPool.new(CONNECTION_POOL_DEFAULTS.merge(:disconnection_proc=>proc{|c| @max_size=3}, :servers=>{:read_only=>{}})){|server| server}
427
453
  end
428
454
 
429
455
  specify "should use the :default server by default" do
@@ -462,4 +488,25 @@ context "A single threaded pool with multiple servers" do
462
488
  @pool.conn.should == nil
463
489
  @pool.conn(:read_only).should == nil
464
490
  end
491
+
492
+ specify ":disconnection_proc option should set the disconnection proc to use" do
493
+ @max_size.should == 2
494
+ proc{@pool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
495
+ @max_size.should == 3
496
+ end
497
+
498
+ specify "#disconnection_proc= should set the disconnection proc to use" do
499
+ a = 1
500
+ @pool.disconnection_proc = proc{|c| a += 1}
501
+ proc{@pool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
502
+ a.should == 2
503
+ end
504
+
505
+ specify "#hold should remove the connection if a DatabaseDisconnectError is raised" do
506
+ @pool.instance_variable_get(:@conns).length.should == 0
507
+ @pool.hold{}
508
+ @pool.instance_variable_get(:@conns).length.should == 1
509
+ proc{@pool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::DatabaseDisconnectError)
510
+ @pool.instance_variable_get(:@conns).length.should == 0
511
+ end
465
512
  end
@@ -259,12 +259,18 @@ context "Symbol#*" do
259
259
  :xyz.*(3).to_s(@ds).should == '(xyz * 3)'
260
260
  :abc.*(5).to_s(@ds).should == '(abc * 5)'
261
261
  end
262
+
263
+ specify "should support qualified symbols if no argument" do
264
+ :xyz__abc.*.to_s(@ds).should == 'xyz.abc.*'
265
+ end
266
+
262
267
  end
263
268
 
264
269
  context "Symbol" do
265
270
  before do
266
271
  @ds = Sequel::Dataset.new(nil)
267
272
  @ds.quote_identifiers = true
273
+ @ds.upcase_identifiers = true
268
274
  end
269
275
 
270
276
  specify "#identifier should format an identifier" do
@@ -276,11 +282,12 @@ context "Symbol" do
276
282
  end
277
283
 
278
284
  specify "should be able to qualify an identifier" do
279
- @ds.literal(:xyz.identifier.qualify(:xyz__abc)).should == '"XYZ__ABC"."XYZ"'
285
+ @ds.literal(:xyz.identifier.qualify(:xyz__abc)).should == '"XYZ"."ABC"."XYZ"'
280
286
  end
281
287
 
282
288
  specify "should be able to specify a schema.table.column" do
283
- @ds.literal(:column.qualify(:table__name.qualify(:schema))).should == '"SCHEMA"."TABLE__NAME"."COLUMN"'
289
+ @ds.literal(:column.qualify(:table.qualify(:schema))).should == '"SCHEMA"."TABLE"."COLUMN"'
290
+ @ds.literal(:column.qualify(:table__name.identifier.qualify(:schema))).should == '"SCHEMA"."TABLE__NAME"."COLUMN"'
284
291
  end
285
292
  end
286
293
 
@@ -6,6 +6,7 @@ context "A new Database" do
6
6
  end
7
7
  teardown do
8
8
  Sequel.quote_identifiers = false
9
+ Sequel.upcase_identifiers = false
9
10
  end
10
11
 
11
12
  specify "should receive options" do
@@ -39,15 +40,41 @@ context "A new Database" do
39
40
  cc.should == 1234
40
41
  end
41
42
 
42
- specify "should respect the :quote_identifiers and :single_threaded options" do
43
- db = Sequel::Database.new(:quote_identifiers=>false, :single_threaded=>true)
44
- db.quote_identifiers?.should == false
43
+ specify "should respect the :single_threaded option" do
44
+ db = Sequel::Database.new(:single_threaded=>true)
45
45
  db.pool.should be_a_kind_of(Sequel::SingleThreadedPool)
46
- db = Sequel::Database.new(:quote_identifiers=>true, :single_threaded=>false)
47
- db.quote_identifiers?.should == true
46
+ db = Sequel::Database.new(:single_threaded=>false)
48
47
  db.pool.should be_a_kind_of(Sequel::ConnectionPool)
49
48
  end
50
49
 
50
+ specify "should respect the :quote_identifiers option" do
51
+ db = Sequel::Database.new(:quote_identifiers=>false)
52
+ db.quote_identifiers?.should == false
53
+ db = Sequel::Database.new(:quote_identifiers=>true)
54
+ db.quote_identifiers?.should == true
55
+ end
56
+
57
+ specify "should respect the :upcase_identifiers option" do
58
+ Sequel.upcase_identifiers = false
59
+ db = Sequel::Database.new(:upcase_identifiers=>false)
60
+ db.upcase_identifiers?.should == false
61
+ db.upcase_identifiers = true
62
+ db.upcase_identifiers?.should == true
63
+ db = Sequel::Database.new(:upcase_identifiers=>true)
64
+ db.upcase_identifiers?.should == true
65
+ db.upcase_identifiers = false
66
+ db.upcase_identifiers?.should == false
67
+ Sequel.upcase_identifiers = true
68
+ db = Sequel::Database.new(:upcase_identifiers=>false)
69
+ db.upcase_identifiers?.should == false
70
+ db.upcase_identifiers = true
71
+ db.upcase_identifiers?.should == true
72
+ db = Sequel::Database.new(:upcase_identifiers=>true)
73
+ db.upcase_identifiers?.should == true
74
+ db.upcase_identifiers = false
75
+ db.upcase_identifiers?.should == false
76
+ end
77
+
51
78
  specify "should use the default Sequel.quote_identifiers value" do
52
79
  Sequel.quote_identifiers = true
53
80
  Sequel::Database.new({}).quote_identifiers?.should == true
@@ -59,6 +86,26 @@ context "A new Database" do
59
86
  Sequel::Database.new({}).quote_identifiers?.should == false
60
87
  end
61
88
 
89
+ specify "should use the default Sequel.upcase_identifiers value" do
90
+ Sequel.upcase_identifiers = true
91
+ Sequel::Database.new({}).upcase_identifiers?.should == true
92
+ Sequel.upcase_identifiers = false
93
+ Sequel::Database.new({}).upcase_identifiers?.should == false
94
+ Sequel::Database.upcase_identifiers = true
95
+ Sequel::Database.new({}).upcase_identifiers?.should == true
96
+ Sequel::Database.upcase_identifiers = false
97
+ Sequel::Database.new({}).upcase_identifiers?.should == false
98
+ end
99
+
100
+ specify "should respect the upcase_indentifiers_default method if Sequel.upcase_identifiers = nil" do
101
+ Sequel.upcase_identifiers = nil
102
+ Sequel::Database.new({}).upcase_identifiers?.should == true
103
+ x = Class.new(Sequel::Database){def upcase_identifiers_default; false end}
104
+ x.new({}).upcase_identifiers?.should == false
105
+ y = Class.new(Sequel::Database){def upcase_identifiers_default; true end}
106
+ y.new({}).upcase_identifiers?.should == true
107
+ end
108
+
62
109
  specify "should just use a :uri option for jdbc with the full connection string" do
63
110
  Sequel::Database.should_receive(:adapter_class).once.with(:jdbc).and_return(Sequel::Database)
64
111
  db = Sequel.connect('jdbc:test://host/db_name')
@@ -67,15 +114,20 @@ context "A new Database" do
67
114
  end
68
115
  end
69
116
 
70
- context "Database#connect" do
71
- specify "should raise Sequel::Error::NotImplemented" do
72
- proc {Sequel::Database.new.connect}.should raise_error(NotImplementedError)
117
+ context "Database#disconnect" do
118
+ specify "should call pool.disconnect" do
119
+ d = Sequel::Database.new
120
+ p = d.pool
121
+ a = 1
122
+ p.meta_def(:disconnect){a += 1}
123
+ d.disconnect.should == 2
124
+ a.should == 2
73
125
  end
74
126
  end
75
127
 
76
- context "Database#disconnect" do
128
+ context "Database#connect" do
77
129
  specify "should raise Sequel::Error::NotImplemented" do
78
- proc {Sequel::Database.new.disconnect}.should raise_error(NotImplementedError)
130
+ proc {Sequel::Database.new.connect}.should raise_error(NotImplementedError)
79
131
  end
80
132
  end
81
133
 
@@ -437,14 +489,12 @@ end
437
489
  context "Database#table_exists?" do
438
490
  setup do
439
491
  @db = DummyDatabase.new
440
- @db.stub!(:tables).and_return([:a, :b])
492
+ @db.instance_variable_set(:@schemas, {:a=>[]})
441
493
  @db2 = DummyDatabase.new
442
494
  end
443
495
 
444
- specify "should use Database#tables if available" do
496
+ specify "should use schema information if available" do
445
497
  @db.table_exists?(:a).should be_true
446
- @db.table_exists?(:b).should be_true
447
- @db.table_exists?(:c).should be_false
448
498
  end
449
499
 
450
500
  specify "should otherwise try to select the first record from the table's dataset" do
@@ -191,6 +191,26 @@ context "A dataset with multiple tables in its FROM clause" do
191
191
  end
192
192
  end
193
193
 
194
+ context "Dataset#exists" do
195
+ setup do
196
+ @ds1 = Sequel::Dataset.new(nil).from(:test)
197
+ @ds2 = @ds1.filter(:price < 100)
198
+ @ds3 = @ds1.filter(:price > 50)
199
+ end
200
+
201
+ specify "should work in filters" do
202
+ @ds1.filter(@ds2.exists).sql.should ==
203
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
204
+ @ds1.filter(@ds2.exists & @ds3.exists).sql.should ==
205
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)) AND EXISTS (SELECT * FROM test WHERE (price > 50)))'
206
+ end
207
+
208
+ specify "should work in select" do
209
+ @ds1.select(@ds2.exists.as(:a), @ds3.exists.as(:b)).sql.should ==
210
+ 'SELECT EXISTS (SELECT * FROM test WHERE (price < 100)) AS a, EXISTS (SELECT * FROM test WHERE (price > 50)) AS b FROM test'
211
+ end
212
+ end
213
+
194
214
  context "Dataset#where" do
195
215
  setup do
196
216
  @dataset = Sequel::Dataset.new(nil).from(:test)
@@ -1309,6 +1329,23 @@ context "Dataset#join_table" do
1309
1329
  'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1310
1330
  end
1311
1331
 
1332
+ specify "should support implicit schemas in from table symbols" do
1333
+ @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should ==
1334
+ 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
1335
+ end
1336
+
1337
+ specify "should support implicit aliases in from table symbols" do
1338
+ @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should ==
1339
+ 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
1340
+ @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should ==
1341
+ 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
1342
+ end
1343
+
1344
+ specify "should support AliasedExpressions" do
1345
+ @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should ==
1346
+ 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
1347
+ end
1348
+
1312
1349
  specify "should support the :implicit_qualifier option" do
1313
1350
  @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should ==
1314
1351
  'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
@@ -2874,14 +2911,12 @@ end
2874
2911
  context "Dataset#table_exists?" do
2875
2912
  setup do
2876
2913
  @db = DummyMummyDatabase.new
2877
- @db.stub!(:tables).and_return([:a, :b])
2914
+ @db.instance_variable_set(:@schemas, {:a=>[]})
2878
2915
  @db2 = DummyMummyDatabase.new
2879
2916
  end
2880
2917
 
2881
- specify "should use Database#tables if available" do
2918
+ specify "should use the database schema if available" do
2882
2919
  @db[:a].table_exists?.should be_true
2883
- @db[:b].table_exists?.should be_true
2884
- @db[:c].table_exists?.should be_false
2885
2920
  end
2886
2921
 
2887
2922
  specify "should otherwise try to select the first record from the table's dataset" do
@@ -3053,10 +3088,14 @@ context "Dataset prepared statements and bound variables " do
3053
3088
  def @db.execute(sql, opts={})
3054
3089
  @sqls << sql
3055
3090
  end
3056
- @ds = @db[:items]
3057
- def @ds.fetch_rows(sql, &block)
3058
- execute(sql)
3091
+ def @db.dataset
3092
+ ds = super()
3093
+ def ds.fetch_rows(sql, &block)
3094
+ execute(sql)
3095
+ end
3096
+ ds
3059
3097
  end
3098
+ @ds = @db[:items]
3060
3099
  end
3061
3100
 
3062
3101
  specify "#call should take a type and bind hash and interpolate it" do
@@ -3097,6 +3136,36 @@ context "Dataset prepared statements and bound variables " do
3097
3136
  @ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
3098
3137
  '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3099
3138
  end
3139
+
3140
+ specify "should handle literal strings" do
3141
+ @ds.filter("num = ?", :$n).call(:select, :n=>1)
3142
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3143
+ end
3144
+
3145
+ specify "should handle datasets using static sql and placeholders" do
3146
+ @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3147
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3148
+ end
3149
+
3150
+ specify "should handle subselects" do
3151
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>:$n)).filter(:$c).call(:select, :n=>1, :b=>0, :c=>2)
3152
+ @db.sqls.should == ['SELECT * FROM items WHERE ((0 AND (num IN (SELECT num FROM items WHERE (num = 1)))) AND 2)']
3153
+ end
3154
+
3155
+ specify "should handle subselects in subselects" do
3156
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>@ds.select(:num).filter(:num=>:$n))).call(:select, :n=>1, :b=>0)
3157
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num IN (SELECT num FROM items WHERE (num = 1))))))']
3158
+ end
3159
+
3160
+ specify "should handle subselects with literal strings" do
3161
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter("num = ?", :$n)).call(:select, :n=>1, :b=>0)
3162
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3163
+ end
3164
+
3165
+ specify "should handle subselects with static sql and placeholders" do
3166
+ @ds.filter(:$b).filter(:num=>@db["SELECT num FROM items WHERE (num = ?)", :$n]).call(:select, :n=>1, :b=>0)
3167
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3168
+ end
3100
3169
  end
3101
3170
 
3102
3171
  context Sequel::Dataset::UnnumberedArgumentMapper do
@@ -3233,3 +3302,32 @@ context "Sequel::Dataset#each" do
3233
3302
  end
3234
3303
  end
3235
3304
  end
3305
+
3306
+ context Sequel::Dataset::UnsupportedIntersectExcept do
3307
+ before do
3308
+ @ds = Sequel::Dataset.new(nil).from(:items)
3309
+ @ds2 = Sequel::Dataset.new(nil).from(:i)
3310
+ @ds.extend(Sequel::Dataset::UnsupportedIntersectExcept)
3311
+ end
3312
+
3313
+ specify "should raise an error if INTERSECT or EXCEPT is USED" do
3314
+ @ds.union(@ds2).sql.should == 'SELECT * FROM items UNION SELECT * FROM i'
3315
+ proc{@ds.intersect(@ds2)}.should raise_error(Sequel::Error)
3316
+ proc{@ds.except(@ds2)}.should raise_error(Sequel::Error)
3317
+ end
3318
+ end
3319
+
3320
+ context Sequel::Dataset::UnsupportedIntersectExceptAll do
3321
+ before do
3322
+ @ds = Sequel::Dataset.new(nil).from(:items)
3323
+ @ds2 = Sequel::Dataset.new(nil).from(:i)
3324
+ @ds.extend(Sequel::Dataset::UnsupportedIntersectExceptAll)
3325
+ end
3326
+
3327
+ specify "should raise an error if INTERSECT or EXCEPT is USED" do
3328
+ @ds.intersect(@ds2).sql.should == 'SELECT * FROM items INTERSECT SELECT * FROM i'
3329
+ @ds.except(@ds2).sql.should == 'SELECT * FROM items EXCEPT SELECT * FROM i'
3330
+ proc{@ds.intersect(@ds2, true)}.should raise_error(Sequel::Error)
3331
+ proc{@ds.except(@ds2, true)}.should raise_error(Sequel::Error)
3332
+ end
3333
+ end