sequel 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +16 -0
- data/README +4 -1
- data/Rakefile +17 -19
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/sharding.rdoc +113 -0
- data/lib/sequel_core/adapters/ado.rb +24 -17
- data/lib/sequel_core/adapters/db2.rb +30 -33
- data/lib/sequel_core/adapters/dbi.rb +15 -13
- data/lib/sequel_core/adapters/informix.rb +13 -14
- data/lib/sequel_core/adapters/jdbc.rb +243 -60
- data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
- data/lib/sequel_core/adapters/mysql.rb +164 -76
- data/lib/sequel_core/adapters/odbc.rb +21 -34
- data/lib/sequel_core/adapters/openbase.rb +10 -7
- data/lib/sequel_core/adapters/oracle.rb +17 -23
- data/lib/sequel_core/adapters/postgres.rb +246 -35
- data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
- data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
- data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
- data/lib/sequel_core/adapters/sqlite.rb +141 -44
- data/lib/sequel_core/connection_pool.rb +85 -63
- data/lib/sequel_core/database.rb +46 -17
- data/lib/sequel_core/dataset.rb +21 -40
- data/lib/sequel_core/dataset/convenience.rb +3 -3
- data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
- data/lib/sequel_core/exceptions.rb +0 -12
- data/lib/sequel_model/base.rb +1 -2
- data/lib/sequel_model/plugins.rb +1 -1
- data/spec/adapters/ado_spec.rb +32 -3
- data/spec/adapters/mysql_spec.rb +7 -8
- data/spec/integration/prepared_statement_test.rb +106 -0
- data/spec/sequel_core/connection_pool_spec.rb +105 -3
- data/spec/sequel_core/database_spec.rb +41 -3
- data/spec/sequel_core/dataset_spec.rb +117 -7
- data/spec/sequel_core/spec_helper.rb +2 -2
- data/spec/sequel_model/model_spec.rb +0 -6
- data/spec/sequel_model/spec_helper.rb +1 -1
- metadata +11 -6
- data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
- data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
@@ -4,15 +4,6 @@ module Sequel
|
|
4
4
|
|
5
5
|
# Raised when Sequel is unable to load a specified adapter.
|
6
6
|
class AdapterNotFound < Error ; end
|
7
|
-
|
8
|
-
# Raise when an invalid expression is encountered inside a block filter.
|
9
|
-
class InvalidExpression < Error; end
|
10
|
-
|
11
|
-
# Represents an Invalid filter.
|
12
|
-
class InvalidFilter < Error ; end
|
13
|
-
|
14
|
-
# Represents an invalid join type.
|
15
|
-
class InvalidJoinType < Error ; end
|
16
7
|
|
17
8
|
# Raised on an invalid operation.
|
18
9
|
class InvalidOperation < Error; end
|
@@ -35,8 +26,5 @@ module Sequel
|
|
35
26
|
# Rollback is a special error used to rollback a transactions.
|
36
27
|
# A transaction block will catch this error and won't pass further up the stack.
|
37
28
|
class Rollback < Error ; end
|
38
|
-
|
39
|
-
# Should be raised inside a worker loop to tell it to stop working.
|
40
|
-
class WorkerStop < RuntimeError ; end
|
41
29
|
end
|
42
30
|
end
|
data/lib/sequel_model/base.rb
CHANGED
@@ -62,7 +62,7 @@ module Sequel
|
|
62
62
|
insert_multiple intersect interval invert_order join join_table last
|
63
63
|
left_outer_join limit map multi_insert naked order order_by order_more
|
64
64
|
paginate print query range reverse_order right_outer_join select
|
65
|
-
select_all select_more set set_graph_aliases single_value size to_csv to_hash
|
65
|
+
select_all select_more server set set_graph_aliases single_value size to_csv to_hash
|
66
66
|
transform union uniq unfiltered unordered update where'.map{|x| x.to_sym}
|
67
67
|
|
68
68
|
# Instance variables that are inherited in subclasses
|
@@ -79,7 +79,6 @@ module Sequel
|
|
79
79
|
# first before a dataset lookup is attempted unless a hash is supplied.
|
80
80
|
def self.[](*args)
|
81
81
|
args = args.first if (args.size == 1)
|
82
|
-
raise(Error::InvalidFilter, "Did you mean to supply a hash?") if args === true || args === false
|
83
82
|
|
84
83
|
if Hash === args
|
85
84
|
dataset[args]
|
data/lib/sequel_model/plugins.rb
CHANGED
@@ -35,7 +35,7 @@ module Sequel
|
|
35
35
|
if m.const_defined?("DatasetMethods")
|
36
36
|
dataset.meta_def(:"#{plugin}_opts") {args.first}
|
37
37
|
dataset.extend(m::DatasetMethods)
|
38
|
-
def_dataset_method(*m::DatasetMethods.
|
38
|
+
def_dataset_method(*m::DatasetMethods.public_instance_methods)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
metaalias :is_a, :is
|
data/spec/adapters/ado_spec.rb
CHANGED
@@ -1,17 +1,46 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
2
|
|
3
3
|
unless defined?(ADO_DB)
|
4
|
-
ADO_DB = Sequel.
|
4
|
+
ADO_DB = Sequel.ado(:host => 'MY_SQL_SERVER', :database => 'MyDB', :user => 'my_pwd', :password => 'my_usr')
|
5
5
|
end
|
6
6
|
|
7
7
|
context "An ADO dataset" do
|
8
|
-
|
8
|
+
before(:each) do
|
9
9
|
ADO_DB.create_table!(:items) { text :name }
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
specify "should not raise exceptions when working with empty datasets" do
|
13
13
|
lambda {
|
14
14
|
ADO_DB[:items].all
|
15
15
|
}.should_not raise_error
|
16
16
|
end
|
17
17
|
end
|
18
|
+
|
19
|
+
context "An MSSQL dataset" do
|
20
|
+
before(:each) do
|
21
|
+
ADO_DB.create_table!(:items) { text :name }
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "should assign a default name to anonymous columns" do
|
25
|
+
col = ADO_DB.fetch('SELECT COUNT(*) FROM items').columns[0]
|
26
|
+
col.to_s.should == '(no column name)'
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "should support counting" do
|
30
|
+
ADO_DB[:items] << {:name => 'my name' }
|
31
|
+
ADO_DB[:items].count.should == 1
|
32
|
+
end
|
33
|
+
|
34
|
+
specify "should support first" do
|
35
|
+
ADO_DB[:items] << {:name => 'x' }
|
36
|
+
ADO_DB[:items] << {:name => 'y' }
|
37
|
+
ADO_DB[:items].first[:name].should == 'x'
|
38
|
+
end
|
39
|
+
|
40
|
+
specify "should support limit" do
|
41
|
+
3.times do
|
42
|
+
ADO_DB[:items] << {:name => 'my name' }
|
43
|
+
end
|
44
|
+
ADO_DB[:items].limit(2).all.size.should == 2
|
45
|
+
end
|
46
|
+
end
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -41,13 +41,6 @@ context "A MySQL database" do
|
|
41
41
|
teardown do
|
42
42
|
Sequel.convert_tinyint_to_bool = true
|
43
43
|
end
|
44
|
-
|
45
|
-
specify "should provide disconnect functionality" do
|
46
|
-
@db.tables
|
47
|
-
@db.pool.size.should == 1
|
48
|
-
@db.disconnect
|
49
|
-
@db.pool.size.should == 0
|
50
|
-
end
|
51
44
|
|
52
45
|
specify "should provide the server version" do
|
53
46
|
@db.server_version.should >= 40000
|
@@ -64,6 +57,12 @@ context "A MySQL database" do
|
|
64
57
|
{:id => 3, :name => 'ghi'}
|
65
58
|
]
|
66
59
|
end
|
60
|
+
|
61
|
+
specify "should provide disconnect functionality" do
|
62
|
+
@db.pool.size.should == 1
|
63
|
+
@db.disconnect
|
64
|
+
@db.pool.size.should == 0
|
65
|
+
end
|
67
66
|
|
68
67
|
specify "should convert Mysql::Errors to Sequel::Errors" do
|
69
68
|
proc{@db << "SELECT 1 + blah;"}.should raise_error(Sequel::Error)
|
@@ -319,7 +318,7 @@ context "MySQL join expressions" do
|
|
319
318
|
end
|
320
319
|
|
321
320
|
specify "should raise error for :full_outer join requests." do
|
322
|
-
lambda{@ds.join_table(:full_outer, :nodes)}.should raise_error(Sequel::Error
|
321
|
+
lambda{@ds.join_table(:full_outer, :nodes)}.should raise_error(Sequel::Error)
|
323
322
|
end
|
324
323
|
specify "should support natural left joins" do
|
325
324
|
@ds.join_table(:natural_left, :nodes).sql.should == \
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
describe "Prepared Statements and Bound Arguments" do
|
4
|
+
before do
|
5
|
+
INTEGRATION_DB.create_table!(:items) do
|
6
|
+
primary_key :id
|
7
|
+
integer :number
|
8
|
+
end
|
9
|
+
@c = Class.new(Sequel::Model(:items))
|
10
|
+
@ds = INTEGRATION_DB[:items]
|
11
|
+
@ds.insert(:number=>10)
|
12
|
+
@ds.meta_def(:ba) do |sym|
|
13
|
+
prepared_arg_placeholder == '$' ? :"#{sym}__int" : sym
|
14
|
+
end
|
15
|
+
clear_sqls
|
16
|
+
end
|
17
|
+
after do
|
18
|
+
INTEGRATION_DB.drop_table(:items)
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "should support bound variables with select, all, and first" do
|
22
|
+
@ds.filter(:number=>@ds.ba(:$n)).call(:select, :n=>10).should == [{:id=>1, :number=>10}]
|
23
|
+
@ds.filter(:number=>@ds.ba(:$n)).call(:all, :n=>10).should == [{:id=>1, :number=>10}]
|
24
|
+
@ds.filter(:number=>@ds.ba(:$n)).call(:first, :n=>10).should == {:id=>1, :number=>10}
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "should support bound variables with insert" do
|
28
|
+
@ds.call(:insert, {:n=>20, :i=>100}, :id=>@ds.ba(:$i), :number=>@ds.ba(:$n))
|
29
|
+
@ds.count.should == 2
|
30
|
+
@ds.order(:id).all.should == [{:id=>1, :number=>10}, {:id=>100, :number=>20}]
|
31
|
+
end
|
32
|
+
|
33
|
+
specify "should have insert return primary key value when using bound arguments" do
|
34
|
+
@ds.call(:insert, {:n=>20}, :number=>@ds.ba(:$n)).should == 2
|
35
|
+
@ds.filter(:id=>2).first[:number].should == 20
|
36
|
+
end
|
37
|
+
|
38
|
+
specify "should support bound variables with delete" do
|
39
|
+
@ds.filter(:number=>@ds.ba(:$n)).call(:delete, :n=>10).should == 1
|
40
|
+
@ds.count.should == 0
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "should support bound variables with update" do
|
44
|
+
@ds.filter(:number=>@ds.ba(:$n)).call(:update, {:n=>10, :nn=>20}, :number=>:number+@ds.ba(:$nn)).should == 1
|
45
|
+
@ds.all.should == [{:id=>1, :number=>30}]
|
46
|
+
end
|
47
|
+
|
48
|
+
specify "should support prepared statements with select, first, and all" do
|
49
|
+
@ds.filter(:number=>@ds.ba(:$n)).prepare(:select, :select_n)
|
50
|
+
INTEGRATION_DB.call(:select_n, :n=>10).should == [{:id=>1, :number=>10}]
|
51
|
+
@ds.filter(:number=>@ds.ba(:$n)).prepare(:all, :select_n)
|
52
|
+
INTEGRATION_DB.call(:select_n, :n=>10).should == [{:id=>1, :number=>10}]
|
53
|
+
@ds.filter(:number=>@ds.ba(:$n)).prepare(:first, :select_n)
|
54
|
+
INTEGRATION_DB.call(:select_n, :n=>10).should == {:id=>1, :number=>10}
|
55
|
+
if INTEGRATION_DB.uri =~ /jdbc:sqlite:/
|
56
|
+
# Work around for open prepared statements on a table not allowing the
|
57
|
+
# dropping of a table when using SQLite over JDBC
|
58
|
+
INTEGRATION_DB.synchronize{|c| c.prepared_statements[:select_n][1].close}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "should support prepared statements with insert" do
|
63
|
+
@ds.prepare(:insert, :insert_n, :id=>@ds.ba(:$i), :number=>@ds.ba(:$n))
|
64
|
+
INTEGRATION_DB.call(:insert_n, :n=>20, :i=>100)
|
65
|
+
@ds.count.should == 2
|
66
|
+
@ds.order(:id).all.should == [{:id=>1, :number=>10}, {:id=>100, :number=>20}]
|
67
|
+
end
|
68
|
+
|
69
|
+
specify "should have insert return primary key value when using prepared statements" do
|
70
|
+
@ds.prepare(:insert, :insert_n, :number=>@ds.ba(:$n))
|
71
|
+
INTEGRATION_DB.call(:insert_n, :n=>20).should == 2
|
72
|
+
@ds.filter(:id=>2).first[:number].should == 20
|
73
|
+
end
|
74
|
+
|
75
|
+
specify "should support prepared statements with delete" do
|
76
|
+
@ds.filter(:number=>@ds.ba(:$n)).prepare(:delete, :delete_n)
|
77
|
+
INTEGRATION_DB.call(:delete_n, :n=>10).should == 1
|
78
|
+
@ds.count.should == 0
|
79
|
+
end
|
80
|
+
|
81
|
+
specify "should support prepared statements with update" do
|
82
|
+
@ds.filter(:number=>@ds.ba(:$n)).prepare(:update, :update_n, :number=>:number+@ds.ba(:$nn))
|
83
|
+
INTEGRATION_DB.call(:update_n, :n=>10, :nn=>20).should == 1
|
84
|
+
@ds.all.should == [{:id=>1, :number=>30}]
|
85
|
+
end
|
86
|
+
|
87
|
+
specify "model datasets should return model instances when using select, all, and first with bound variables" do
|
88
|
+
@c.filter(:number=>@ds.ba(:$n)).call(:select, :n=>10).should == [@c.load(:id=>1, :number=>10)]
|
89
|
+
@c.filter(:number=>@ds.ba(:$n)).call(:all, :n=>10).should == [@c.load(:id=>1, :number=>10)]
|
90
|
+
@c.filter(:number=>@ds.ba(:$n)).call(:first, :n=>10).should == @c.load(:id=>1, :number=>10)
|
91
|
+
end
|
92
|
+
|
93
|
+
specify "model datasets should return model instances when using select, all, and first with prepared statements" do
|
94
|
+
@c.filter(:number=>@ds.ba(:$n)).prepare(:select, :select_n)
|
95
|
+
INTEGRATION_DB.call(:select_n, :n=>10).should == [@c.load(:id=>1, :number=>10)]
|
96
|
+
@c.filter(:number=>@ds.ba(:$n)).prepare(:all, :select_n)
|
97
|
+
INTEGRATION_DB.call(:select_n, :n=>10).should == [@c.load(:id=>1, :number=>10)]
|
98
|
+
@c.filter(:number=>@ds.ba(:$n)).prepare(:first, :select_n)
|
99
|
+
INTEGRATION_DB.call(:select_n, :n=>10).should == @c.load(:id=>1, :number=>10)
|
100
|
+
if INTEGRATION_DB.uri =~ /jdbc:sqlite:/
|
101
|
+
# Work around for open prepared statements on a table not allowing the
|
102
|
+
# dropping of a table when using SQLite over JDBC
|
103
|
+
INTEGRATION_DB.synchronize{|c| c.prepared_statements[:select_n][1].close}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -56,9 +56,9 @@ context "A connection pool handling connections" do
|
|
56
56
|
end
|
57
57
|
|
58
58
|
specify "#make_new should not make more than max_size connections" do
|
59
|
-
@cpool.send(:make_new).should == :got_connection
|
60
|
-
@cpool.send(:make_new).should == :got_connection
|
61
|
-
@cpool.send(:make_new).should == nil
|
59
|
+
@cpool.send(:make_new, :default).should == :got_connection
|
60
|
+
@cpool.send(:make_new, :default).should == :got_connection
|
61
|
+
@cpool.send(:make_new, :default).should == nil
|
62
62
|
@cpool.created_count.should == 2
|
63
63
|
end
|
64
64
|
end
|
@@ -341,6 +341,65 @@ context "ConnectionPool#disconnect" do
|
|
341
341
|
end
|
342
342
|
end
|
343
343
|
|
344
|
+
context "A connection pool with multiple servers" do
|
345
|
+
setup do
|
346
|
+
@invoked_counts = Hash.new(0)
|
347
|
+
@pool = Sequel::ConnectionPool.new(CONNECTION_POOL_DEFAULTS.merge(:servers=>{:read_only=>{}})){|server| "#{server}#{@invoked_counts[server] += 1}"}
|
348
|
+
end
|
349
|
+
|
350
|
+
specify "should use the :default server by default" do
|
351
|
+
@pool.size.should == 0
|
352
|
+
@pool.hold do |c|
|
353
|
+
c.should == "default1"
|
354
|
+
@pool.allocated.should == {Thread.current=>"default1"}
|
355
|
+
end
|
356
|
+
@pool.available_connections.should == ["default1"]
|
357
|
+
@pool.size.should == 1
|
358
|
+
@invoked_counts.should == {:default=>1}
|
359
|
+
end
|
360
|
+
|
361
|
+
specify "should use the requested server if server is given" do
|
362
|
+
@pool.size(:read_only).should == 0
|
363
|
+
@pool.hold(:read_only) do |c|
|
364
|
+
c.should == "read_only1"
|
365
|
+
@pool.allocated(:read_only).should == {Thread.current=>"read_only1"}
|
366
|
+
end
|
367
|
+
@pool.available_connections(:read_only).should == ["read_only1"]
|
368
|
+
@pool.size(:read_only).should == 1
|
369
|
+
@invoked_counts.should == {:read_only=>1}
|
370
|
+
end
|
371
|
+
|
372
|
+
specify "#hold should only yield connections for the server requested" do
|
373
|
+
@pool.hold(:read_only) do |c|
|
374
|
+
c.should == "read_only1"
|
375
|
+
@pool.allocated(:read_only).should == {Thread.current=>"read_only1"}
|
376
|
+
@pool.hold do |d|
|
377
|
+
d.should == "default1"
|
378
|
+
@pool.hold do |e|
|
379
|
+
e.should == d
|
380
|
+
@pool.hold(:read_only){|b| b.should == c}
|
381
|
+
end
|
382
|
+
@pool.allocated.should == {Thread.current=>"default1"}
|
383
|
+
end
|
384
|
+
end
|
385
|
+
@invoked_counts.should == {:read_only=>1, :default=>1}
|
386
|
+
end
|
387
|
+
|
388
|
+
specify "#disconnect should disconnect from all servers" do
|
389
|
+
@pool.hold(:read_only){}
|
390
|
+
@pool.hold{}
|
391
|
+
conns = []
|
392
|
+
@pool.size.should == 1
|
393
|
+
@pool.size(:read_only).should == 1
|
394
|
+
@pool.disconnect{|c| conns << c}
|
395
|
+
conns.sort.should == %w'default1 read_only1'
|
396
|
+
@pool.size.should == 0
|
397
|
+
@pool.size(:read_only).should == 0
|
398
|
+
@pool.hold(:read_only){|c| c.should == 'read_only2'}
|
399
|
+
@pool.hold{|c| c.should == 'default2'}
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
344
403
|
context "SingleThreadedPool" do
|
345
404
|
setup do
|
346
405
|
@pool = Sequel::SingleThreadedPool.new(CONNECTION_POOL_DEFAULTS){1234}
|
@@ -361,3 +420,46 @@ context "SingleThreadedPool" do
|
|
361
420
|
@pool.conn.should be_nil
|
362
421
|
end
|
363
422
|
end
|
423
|
+
|
424
|
+
context "A single threaded pool with multiple servers" do
|
425
|
+
setup do
|
426
|
+
@pool = Sequel::SingleThreadedPool.new(CONNECTION_POOL_DEFAULTS.merge(:servers=>{:read_only=>{}})){|server| server}
|
427
|
+
end
|
428
|
+
|
429
|
+
specify "should use the :default server by default" do
|
430
|
+
@pool.hold{|c| c.should == :default}
|
431
|
+
@pool.conn.should == :default
|
432
|
+
end
|
433
|
+
|
434
|
+
specify "should use the requested server if server is given" do
|
435
|
+
@pool.hold(:read_only){|c| c.should == :read_only}
|
436
|
+
@pool.conn(:read_only).should == :read_only
|
437
|
+
end
|
438
|
+
|
439
|
+
specify "#hold should only yield connections for the server requested" do
|
440
|
+
@pool.hold(:read_only) do |c|
|
441
|
+
c.should == :read_only
|
442
|
+
@pool.hold do |d|
|
443
|
+
d.should == :default
|
444
|
+
@pool.hold do |e|
|
445
|
+
e.should == d
|
446
|
+
@pool.hold(:read_only){|b| b.should == c}
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
@pool.conn.should == :default
|
451
|
+
@pool.conn(:read_only).should == :read_only
|
452
|
+
end
|
453
|
+
|
454
|
+
specify "#disconnect should disconnect from all servers" do
|
455
|
+
@pool.hold(:read_only){}
|
456
|
+
@pool.hold{}
|
457
|
+
conns = []
|
458
|
+
@pool.conn.should == :default
|
459
|
+
@pool.conn(:read_only).should == :read_only
|
460
|
+
@pool.disconnect{|c| conns << c}
|
461
|
+
conns.sort_by{|x| x.to_s}.should == [:default, :read_only]
|
462
|
+
@pool.conn.should == nil
|
463
|
+
@pool.conn(:read_only).should == nil
|
464
|
+
end
|
465
|
+
end
|
@@ -150,7 +150,7 @@ end
|
|
150
150
|
context "Database#<<" do
|
151
151
|
setup do
|
152
152
|
@c = Class.new(Sequel::Database) do
|
153
|
-
define_method(:execute) {|sql| sql}
|
153
|
+
define_method(:execute) {|sql, opts| sql}
|
154
154
|
end
|
155
155
|
@db = @c.new({})
|
156
156
|
end
|
@@ -238,7 +238,7 @@ end
|
|
238
238
|
class DummyDatabase < Sequel::Database
|
239
239
|
attr_reader :sqls
|
240
240
|
|
241
|
-
def execute(sql)
|
241
|
+
def execute(sql, opts={})
|
242
242
|
@sqls ||= []
|
243
243
|
@sqls << sql
|
244
244
|
end
|
@@ -455,7 +455,7 @@ end
|
|
455
455
|
|
456
456
|
class Dummy3Database < Sequel::Database
|
457
457
|
attr_reader :sql, :transactions
|
458
|
-
def execute(sql); @sql ||= []; @sql << sql; end
|
458
|
+
def execute(sql, opts={}); @sql ||= []; @sql << sql; end
|
459
459
|
|
460
460
|
class DummyConnection
|
461
461
|
def initialize(db); @db = db; end
|
@@ -962,3 +962,41 @@ context "Database#get" do
|
|
962
962
|
@db.sqls.last.should == 'SELECT version()'
|
963
963
|
end
|
964
964
|
end
|
965
|
+
|
966
|
+
context "Database#call" do
|
967
|
+
specify "should call the prepared statement with the given name" do
|
968
|
+
db = MockDatabase.new
|
969
|
+
db[:items].prepare(:select, :select_all)
|
970
|
+
db.call(:select_all).should == [{:id => 1, :x => 1}]
|
971
|
+
db[:items].filter(:n=>:$n).prepare(:select, :select_n)
|
972
|
+
db.call(:select_n, :n=>1).should == [{:id => 1, :x => 1}]
|
973
|
+
db.sqls.should == ['SELECT * FROM items', 'SELECT * FROM items WHERE (n = 1)']
|
974
|
+
end
|
975
|
+
end
|
976
|
+
|
977
|
+
context "Database#server_opts" do
|
978
|
+
specify "should return the general opts if no :servers option is used" do
|
979
|
+
opts = {:host=>1, :database=>2}
|
980
|
+
MockDatabase.new(opts).send(:server_opts, :server1).should == {:host=>1, :database=>2}
|
981
|
+
end
|
982
|
+
|
983
|
+
specify "should return the general opts if entry for the server is present in the :servers option" do
|
984
|
+
opts = {:host=>1, :database=>2, :servers=>{}}
|
985
|
+
MockDatabase.new(opts).send(:server_opts, :server1).should == {:host=>1, :database=>2}
|
986
|
+
end
|
987
|
+
|
988
|
+
specify "should return the general opts merged with the specific opts if given as a hash" do
|
989
|
+
opts = {:host=>1, :database=>2, :servers=>{:server1=>{:host=>3}}}
|
990
|
+
MockDatabase.new(opts).send(:server_opts, :server1).should == {:host=>3, :database=>2}
|
991
|
+
end
|
992
|
+
|
993
|
+
specify "should return the sgeneral opts merged with the specific opts if given as a proc" do
|
994
|
+
opts = {:host=>1, :database=>2, :servers=>{:server1=>proc{|db| {:host=>4}}}}
|
995
|
+
MockDatabase.new(opts).send(:server_opts, :server1).should == {:host=>4, :database=>2}
|
996
|
+
end
|
997
|
+
|
998
|
+
specify "should raise an error if the specific opts is not a proc or hash" do
|
999
|
+
opts = {:host=>1, :database=>2, :servers=>{:server1=>2}}
|
1000
|
+
proc{MockDatabase.new(opts).send(:server_opts, :server1)}.should raise_error(Sequel::Error)
|
1001
|
+
end
|
1002
|
+
end
|
@@ -2244,7 +2244,7 @@ context "Dataset#multi_insert" do
|
|
2244
2244
|
@dbc = Class.new do
|
2245
2245
|
attr_reader :sqls
|
2246
2246
|
|
2247
|
-
def execute(sql)
|
2247
|
+
def execute(sql, opts={})
|
2248
2248
|
@sqls ||= []
|
2249
2249
|
@sqls << sql
|
2250
2250
|
end
|
@@ -2717,7 +2717,7 @@ context "Dataset#create_view" do
|
|
2717
2717
|
@dbc = Class.new(Sequel::Database) do
|
2718
2718
|
attr_reader :sqls
|
2719
2719
|
|
2720
|
-
def execute(sql)
|
2720
|
+
def execute(sql, opts={})
|
2721
2721
|
@sqls ||= []
|
2722
2722
|
@sqls << sql
|
2723
2723
|
end
|
@@ -2738,7 +2738,7 @@ context "Dataset#create_or_replace_view" do
|
|
2738
2738
|
@dbc = Class.new(Sequel::Database) do
|
2739
2739
|
attr_reader :sqls
|
2740
2740
|
|
2741
|
-
def execute(sql)
|
2741
|
+
def execute(sql, opts={})
|
2742
2742
|
@sqls ||= []
|
2743
2743
|
@sqls << sql
|
2744
2744
|
end
|
@@ -2950,7 +2950,7 @@ context "Dataset.dataset_classes" do
|
|
2950
2950
|
end
|
2951
2951
|
end
|
2952
2952
|
|
2953
|
-
context "Dataset default #fetch_rows, #insert, #update, and #delete" do
|
2953
|
+
context "Dataset default #fetch_rows, #insert, #update, and #delete, #execute" do
|
2954
2954
|
setup do
|
2955
2955
|
@db = Sequel::Database.new
|
2956
2956
|
@ds = @db[:items]
|
@@ -2961,17 +2961,127 @@ context "Dataset default #fetch_rows, #insert, #update, and #delete" do
|
|
2961
2961
|
end
|
2962
2962
|
|
2963
2963
|
specify "#delete should execute delete SQL" do
|
2964
|
-
@db.should_receive(:execute).once.with('DELETE FROM items')
|
2964
|
+
@db.should_receive(:execute).once.with('DELETE FROM items', :server=>:default)
|
2965
2965
|
@ds.delete
|
2966
2966
|
end
|
2967
2967
|
|
2968
2968
|
specify "#insert should execute insert SQL" do
|
2969
|
-
@db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES')
|
2969
|
+
@db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
|
2970
2970
|
@ds.insert([])
|
2971
2971
|
end
|
2972
2972
|
|
2973
2973
|
specify "#update should execute update SQL" do
|
2974
|
-
@db.should_receive(:execute).once.with('UPDATE items SET number = 1')
|
2974
|
+
@db.should_receive(:execute).once.with('UPDATE items SET number = 1', :server=>:default)
|
2975
2975
|
@ds.update(:number=>1)
|
2976
2976
|
end
|
2977
|
+
|
2978
|
+
specify "#execute should execute the SQL on the database" do
|
2979
|
+
@db.should_receive(:execute).once.with('SELECT 1', :server=>:read_only)
|
2980
|
+
@ds.send(:execute, 'SELECT 1')
|
2981
|
+
end
|
2982
|
+
end
|
2983
|
+
|
2984
|
+
context "Dataset prepared statements and bound variables " do
|
2985
|
+
setup do
|
2986
|
+
@db = Sequel::Database.new
|
2987
|
+
@db.meta_eval{attr_accessor :sqls}
|
2988
|
+
@db.sqls = []
|
2989
|
+
def @db.execute(sql, opts={})
|
2990
|
+
@sqls << sql
|
2991
|
+
end
|
2992
|
+
@ds = @db[:items]
|
2993
|
+
def @ds.fetch_rows(sql, &block)
|
2994
|
+
execute(sql)
|
2995
|
+
end
|
2996
|
+
end
|
2997
|
+
|
2998
|
+
specify "#call should take a type and bind hash and interpolate it" do
|
2999
|
+
@ds.filter(:num=>:$n).call(:select, :n=>1)
|
3000
|
+
@ds.filter(:num=>:$n).call(:first, :n=>1)
|
3001
|
+
@ds.filter(:num=>:$n).call(:delete, :n=>1)
|
3002
|
+
@ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
|
3003
|
+
@ds.call(:insert, {:n=>1}, :num=>:$n)
|
3004
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
|
3005
|
+
'SELECT * FROM items WHERE (num = 1) LIMIT 1',
|
3006
|
+
'DELETE FROM items WHERE (num = 1)',
|
3007
|
+
'UPDATE items SET num = 2 WHERE (num = 1)',
|
3008
|
+
'INSERT INTO items (num) VALUES (1)']
|
3009
|
+
end
|
3010
|
+
|
3011
|
+
specify "#prepare should take a type and name and store it in the database for later use with call" do
|
3012
|
+
pss = []
|
3013
|
+
pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
|
3014
|
+
pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
|
3015
|
+
pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
|
3016
|
+
pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
|
3017
|
+
pss << @ds.prepare(:insert, :in, :num=>:$n)
|
3018
|
+
@db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
|
3019
|
+
[:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
|
3020
|
+
@db.call(:sn, :n=>1)
|
3021
|
+
@db.call(:fn, :n=>1)
|
3022
|
+
@db.call(:dn, :n=>1)
|
3023
|
+
@db.call(:un, :n=>1, :n2=>2)
|
3024
|
+
@db.call(:in, :n=>1)
|
3025
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
|
3026
|
+
'SELECT * FROM items WHERE (num = 1) LIMIT 1',
|
3027
|
+
'DELETE FROM items WHERE (num = 1)',
|
3028
|
+
'UPDATE items SET num = 2 WHERE (num = 1)',
|
3029
|
+
'INSERT INTO items (num) VALUES (1)']
|
3030
|
+
end
|
3031
|
+
|
3032
|
+
specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
|
3033
|
+
@ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
|
3034
|
+
'<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
|
3035
|
+
end
|
2977
3036
|
end
|
3037
|
+
|
3038
|
+
context Sequel::Dataset::UnnumberedArgumentMapper do
|
3039
|
+
setup do
|
3040
|
+
@db = Sequel::Database.new
|
3041
|
+
@db.meta_eval{attr_accessor :sqls}
|
3042
|
+
@db.sqls = []
|
3043
|
+
def @db.execute(sql, opts={})
|
3044
|
+
@sqls << [sql, *opts[:arguments]]
|
3045
|
+
end
|
3046
|
+
@ds = @db[:items].filter(:num=>:$n)
|
3047
|
+
def @ds.fetch_rows(sql, &block)
|
3048
|
+
execute(sql)
|
3049
|
+
end
|
3050
|
+
def @ds.execute(sql, opts={}, &block)
|
3051
|
+
@db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
|
3052
|
+
end
|
3053
|
+
@ps = @ds.prepare(:select, :sn)
|
3054
|
+
@ps.extend(Sequel::Dataset::UnnumberedArgumentMapper)
|
3055
|
+
end
|
3056
|
+
|
3057
|
+
specify "#inspect should show the actual SQL submitted to the database" do
|
3058
|
+
@ps.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
|
3059
|
+
end
|
3060
|
+
|
3061
|
+
specify "should submitted the SQL to the database with placeholders and bind variables" do
|
3062
|
+
@ps.call(:n=>1)
|
3063
|
+
@db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1]]
|
3064
|
+
end
|
3065
|
+
end
|
3066
|
+
|
3067
|
+
context "Sequel::Dataset#server" do
|
3068
|
+
specify "should set the server to use for the dataset" do
|
3069
|
+
@db = Sequel::Database.new
|
3070
|
+
@ds = @db[:items].server(:s)
|
3071
|
+
sqls = []
|
3072
|
+
@db.meta_def(:execute) do |sql, opts|
|
3073
|
+
sqls << [sql, opts[:server]]
|
3074
|
+
end
|
3075
|
+
def @ds.fetch_rows(sql, &block)
|
3076
|
+
execute(sql)
|
3077
|
+
end
|
3078
|
+
@ds.all
|
3079
|
+
@ds.server(:i).insert(:a=>1)
|
3080
|
+
@ds.server(:d).delete
|
3081
|
+
@ds.server(:u).update(:a=>:a+1)
|
3082
|
+
sqls.should == [['SELECT * FROM items', :s],
|
3083
|
+
['INSERT INTO items (a) VALUES (1)', :i],
|
3084
|
+
['DELETE FROM items', :d],
|
3085
|
+
['UPDATE items SET a = (a + 1)', :u]]
|
3086
|
+
end
|
3087
|
+
end
|