sequel 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
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