sequel 2.3.0 → 2.4.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.
- 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
|