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.
Files changed (43) hide show
  1. data/CHANGELOG +16 -0
  2. data/README +4 -1
  3. data/Rakefile +17 -19
  4. data/doc/prepared_statements.rdoc +104 -0
  5. data/doc/sharding.rdoc +113 -0
  6. data/lib/sequel_core/adapters/ado.rb +24 -17
  7. data/lib/sequel_core/adapters/db2.rb +30 -33
  8. data/lib/sequel_core/adapters/dbi.rb +15 -13
  9. data/lib/sequel_core/adapters/informix.rb +13 -14
  10. data/lib/sequel_core/adapters/jdbc.rb +243 -60
  11. data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
  12. data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
  13. data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
  14. data/lib/sequel_core/adapters/mysql.rb +164 -76
  15. data/lib/sequel_core/adapters/odbc.rb +21 -34
  16. data/lib/sequel_core/adapters/openbase.rb +10 -7
  17. data/lib/sequel_core/adapters/oracle.rb +17 -23
  18. data/lib/sequel_core/adapters/postgres.rb +246 -35
  19. data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
  21. data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
  22. data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
  23. data/lib/sequel_core/adapters/sqlite.rb +141 -44
  24. data/lib/sequel_core/connection_pool.rb +85 -63
  25. data/lib/sequel_core/database.rb +46 -17
  26. data/lib/sequel_core/dataset.rb +21 -40
  27. data/lib/sequel_core/dataset/convenience.rb +3 -3
  28. data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
  29. data/lib/sequel_core/exceptions.rb +0 -12
  30. data/lib/sequel_model/base.rb +1 -2
  31. data/lib/sequel_model/plugins.rb +1 -1
  32. data/spec/adapters/ado_spec.rb +32 -3
  33. data/spec/adapters/mysql_spec.rb +7 -8
  34. data/spec/integration/prepared_statement_test.rb +106 -0
  35. data/spec/sequel_core/connection_pool_spec.rb +105 -3
  36. data/spec/sequel_core/database_spec.rb +41 -3
  37. data/spec/sequel_core/dataset_spec.rb +117 -7
  38. data/spec/sequel_core/spec_helper.rb +2 -2
  39. data/spec/sequel_model/model_spec.rb +0 -6
  40. data/spec/sequel_model/spec_helper.rb +1 -1
  41. metadata +11 -6
  42. data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
  43. 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
@@ -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]
@@ -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.instance_methods)
38
+ def_dataset_method(*m::DatasetMethods.public_instance_methods)
39
39
  end
40
40
  end
41
41
  metaalias :is_a, :is
@@ -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.connect(:adapter => 'ado', :driver => "{Microsoft Access Driver (*.mdb)}; DBQ=c:\\Nwind.mdb")
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
- setup do
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
@@ -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::InvalidJoinType)
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