sequel 3.31.0 → 3.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGELOG +54 -0
  2. data/MIT-LICENSE +1 -1
  3. data/doc/advanced_associations.rdoc +17 -0
  4. data/doc/association_basics.rdoc +74 -30
  5. data/doc/release_notes/3.32.0.txt +202 -0
  6. data/doc/schema_modification.rdoc +1 -1
  7. data/lib/sequel/adapters/jdbc/db2.rb +7 -0
  8. data/lib/sequel/adapters/jdbc/derby.rb +13 -0
  9. data/lib/sequel/adapters/jdbc/h2.rb +10 -1
  10. data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
  11. data/lib/sequel/adapters/jdbc/oracle.rb +7 -0
  12. data/lib/sequel/adapters/mock.rb +4 -0
  13. data/lib/sequel/adapters/mysql.rb +3 -0
  14. data/lib/sequel/adapters/oracle.rb +7 -3
  15. data/lib/sequel/adapters/shared/db2.rb +9 -2
  16. data/lib/sequel/adapters/shared/mssql.rb +48 -2
  17. data/lib/sequel/adapters/shared/mysql.rb +24 -4
  18. data/lib/sequel/adapters/shared/oracle.rb +7 -6
  19. data/lib/sequel/adapters/shared/progress.rb +1 -1
  20. data/lib/sequel/adapters/shared/sqlite.rb +16 -10
  21. data/lib/sequel/core.rb +22 -0
  22. data/lib/sequel/database/query.rb +13 -4
  23. data/lib/sequel/dataset/actions.rb +20 -11
  24. data/lib/sequel/dataset/mutation.rb +7 -1
  25. data/lib/sequel/dataset/prepared_statements.rb +11 -0
  26. data/lib/sequel/dataset/sql.rb +21 -24
  27. data/lib/sequel/extensions/query.rb +1 -1
  28. data/lib/sequel/model.rb +5 -2
  29. data/lib/sequel/model/associations.rb +70 -16
  30. data/lib/sequel/model/base.rb +11 -6
  31. data/lib/sequel/plugins/active_model.rb +13 -1
  32. data/lib/sequel/plugins/composition.rb +43 -10
  33. data/lib/sequel/plugins/many_through_many.rb +4 -1
  34. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  35. data/lib/sequel/plugins/serialization.rb +13 -8
  36. data/lib/sequel/plugins/serialization_modification_detection.rb +22 -10
  37. data/lib/sequel/version.rb +1 -1
  38. data/spec/adapters/mssql_spec.rb +33 -10
  39. data/spec/adapters/mysql_spec.rb +111 -91
  40. data/spec/adapters/oracle_spec.rb +18 -0
  41. data/spec/core/database_spec.rb +1 -0
  42. data/spec/core/dataset_spec.rb +110 -15
  43. data/spec/extensions/active_model_spec.rb +13 -0
  44. data/spec/extensions/many_through_many_spec.rb +14 -14
  45. data/spec/extensions/query_spec.rb +6 -0
  46. data/spec/extensions/serialization_modification_detection_spec.rb +36 -1
  47. data/spec/extensions/serialization_spec.rb +9 -0
  48. data/spec/integration/associations_test.rb +278 -154
  49. data/spec/integration/dataset_test.rb +39 -2
  50. data/spec/integration/plugin_test.rb +63 -3
  51. data/spec/integration/prepared_statement_test.rb +10 -3
  52. data/spec/integration/schema_test.rb +61 -14
  53. data/spec/integration/transaction_test.rb +10 -0
  54. data/spec/model/associations_spec.rb +170 -80
  55. data/spec/model/hooks_spec.rb +40 -0
  56. metadata +4 -2
@@ -86,6 +86,11 @@ module Sequel
86
86
  # database to typecast the value correctly.
87
87
  attr_accessor :typecast_on_assignment
88
88
 
89
+ # Whether to enable the after_commit and after_rollback hooks when saving/destroying
90
+ # instances. On by default, can be turned off for performance reasons or when using
91
+ # prepared transactions (which aren't compatible with after commit/rollback).
92
+ attr_accessor :use_after_commit_rollback
93
+
89
94
  # Whether to use a transaction by default when saving/deleting records (default: true).
90
95
  # If you are sending database queries in before_* or after_* hooks, you shouldn't change
91
96
  # the default setting without a good reason.
@@ -793,7 +798,7 @@ module Sequel
793
798
  #
794
799
  # * All of the methods in +HOOKS+ and +AROUND_HOOKS+ create instance methods that are called
795
800
  # by Sequel when the appropriate action occurs. For example, when destroying
796
- # a model object, Sequel will call +around_destory+, which will call +before_destroy+, do
801
+ # a model object, Sequel will call +around_destroy+, which will call +before_destroy+, do
797
802
  # the destroy, and then call +after_destroy+.
798
803
  # * The following instance_methods all call the class method of the same
799
804
  # name: columns, db, primary_key, db_schema.
@@ -1080,7 +1085,7 @@ module Sequel
1080
1085
  # a = Artist[1]
1081
1086
  # a.modified? # => false
1082
1087
  # a.set(:name=>'Jim')
1083
- # a.modified # => true
1088
+ # a.modified? # => true
1084
1089
  def modified?
1085
1090
  @modified || !changed_columns.empty?
1086
1091
  end
@@ -1360,7 +1365,7 @@ module Sequel
1360
1365
  # allow running inside a transaction
1361
1366
  def _destroy(opts)
1362
1367
  sh = {:server=>this_server}
1363
- db.after_rollback(sh){after_destroy_rollback}
1368
+ db.after_rollback(sh){after_destroy_rollback} if use_after_commit_rollback
1364
1369
  called = false
1365
1370
  around_destroy do
1366
1371
  called = true
@@ -1370,7 +1375,7 @@ module Sequel
1370
1375
  true
1371
1376
  end
1372
1377
  raise_hook_failure(:destroy) unless called
1373
- db.after_commit(sh){after_destroy_commit}
1378
+ db.after_commit(sh){after_destroy_commit} if use_after_commit_rollback
1374
1379
  self
1375
1380
  end
1376
1381
 
@@ -1432,7 +1437,7 @@ module Sequel
1432
1437
  # it's own transaction.
1433
1438
  def _save(columns, opts)
1434
1439
  sh = {:server=>this_server}
1435
- db.after_rollback(sh){after_rollback}
1440
+ db.after_rollback(sh){after_rollback} if use_after_commit_rollback
1436
1441
  was_new = false
1437
1442
  pk = nil
1438
1443
  called_save = false
@@ -1486,7 +1491,7 @@ module Sequel
1486
1491
  @columns_updated = nil
1487
1492
  end
1488
1493
  @modified = false
1489
- db.after_commit(sh){after_commit}
1494
+ db.after_commit(sh){after_commit} if use_after_commit_rollback
1490
1495
  self
1491
1496
  end
1492
1497
 
@@ -16,7 +16,14 @@ module Sequel
16
16
  # # Make the Album class active_model compliant
17
17
  # Album.plugin :active_model
18
18
  module ActiveModel
19
- ClassMethods = ::ActiveModel::Naming
19
+ module ClassMethods
20
+ include ::ActiveModel::Naming
21
+
22
+ # Class level cache for to_partial_path.
23
+ def _to_partial_path
24
+ @_to_partial_path ||= "#{underscore(pluralize(to_s))}/#{underscore(demodulize(to_s))}".freeze
25
+ end
26
+ end
20
27
 
21
28
  module InstanceMethods
22
29
  # The default string to join composite primary keys with in to_param.
@@ -56,6 +63,11 @@ module Sequel
56
63
  k.join(to_param_joiner)
57
64
  end
58
65
  end
66
+
67
+ # Returns a string identifying the path associated with the object.
68
+ def to_partial_path
69
+ model._to_partial_path
70
+ end
59
71
 
60
72
  private
61
73
 
@@ -1,20 +1,53 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The composition plugin allows you to easily define getter and
4
- # setter instance methods for a class where the backing data
5
- # is composed of other getters and decomposed to other setters.
3
+ # The composition plugin allows you to easily define a virtual
4
+ # attribute where the backing data is composed of other columns.
6
5
  #
7
- # A simple example of this is when you have a database table with
8
- # separate columns for year, month, and day, but where you want
9
- # to deal with Date objects in your ruby code. This can be handled
10
- # with:
6
+ # There are two ways to use the plugin. One way is with the
7
+ # :mapping option. A simple example of this is when you have a
8
+ # database table with separate columns for year, month, and day,
9
+ # but where you want to deal with Date objects in your ruby code.
10
+ # This can be handled with:
11
11
  #
12
12
  # Album.plugin :composition
13
13
  # Album.composition :date, :mapping=>[:year, :month, :day]
14
14
  #
15
- # The :mapping option is optional, but you can define custom
16
- # composition and decomposition procs via the :composer and
17
- # :decomposer options.
15
+ # With the :mapping option, you can provide a :class option
16
+ # that gives the class to use, but if that is not provided, it
17
+ # is inferred from the name of the composition (e.g. :date -> Date).
18
+ # When the <tt>date</tt> method is called, it will return a
19
+ # Date object by calling:
20
+ #
21
+ # Date.new(year, month, day)
22
+ #
23
+ # When saving the object, if the date composition has been used
24
+ # (by calling either the getter or setter method), it will
25
+ # populate the related columns of the object before saving:
26
+ #
27
+ # self.year = date.year
28
+ # self.month = date.month
29
+ # self.day = date.day
30
+ #
31
+ # The :mapping option is just a shortcut that works in particular
32
+ # cases. To handle any case, you can define a custom :composer
33
+ # and :decomposer procs. The :composer proc will be instance_evaled
34
+ # the first time the getter is called, and the :decomposer proc
35
+ # will be instance_evaled before saving. The above example could
36
+ # also be implemented as:
37
+ #
38
+ # Album.composition, :date,
39
+ # :composer=>proc{Date.new(year, month, day) if year || month || day},
40
+ # :decomposer=>(proc do
41
+ # if d = compositions[:date]
42
+ # self.year = d.year
43
+ # self.month = d.month
44
+ # self.day = d.day
45
+ # else
46
+ # self.year = nil
47
+ # self.month = nil
48
+ # self.day = nil
49
+ # end
50
+ # end)
18
51
  #
19
52
  # Note that when using the composition object, you should not
20
53
  # modify the underlying columns if you are also instantiating
@@ -255,6 +255,7 @@ module Sequel
255
255
  def many_through_many_association_filter_expression(op, ref, obj)
256
256
  lpks = ref[:left_primary_keys]
257
257
  lpks = lpks.first if lpks.length == 1
258
+ lpks = ref.qualify(model.table_name, lpks)
258
259
  edges = ref.edges
259
260
  first, rest = edges.first, edges[1..-1]
260
261
  last = edges.last
@@ -266,7 +267,9 @@ module Sequel
266
267
  last_join = ds.opts[:join].last
267
268
  last_join.table_alias || last_join.table
268
269
  end
269
- exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref[:final_edge][:left])), ref.right_primary_keys, obj)
270
+ meths = ref.right_primary_keys
271
+ meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
272
+ exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref[:final_edge][:left])), meths, obj)
270
273
  if exp == SQL::Constants::FALSE
271
274
  association_filter_handle_inversion(op, exp, Array(lpks))
272
275
  else
@@ -1,20 +1,75 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The nested_attributes plugin allows you to update attributes for associated
4
- # objects directly through the parent object, similar to ActiveRecord's
5
- # Nested Attributes feature.
6
- #
7
- # Nested attributes are created using the nested_attributes method:
3
+ # The nested_attributes plugin allows you to create, update, and delete
4
+ # associated objects directly by calling a method on the current object.
5
+ # Nested attributes are defined using the nested_attributes class method:
8
6
  #
9
7
  # Artist.one_to_many :albums
10
8
  # Artist.plugin :nested_attributes
11
9
  # Artist.nested_attributes :albums
12
- # a = Artist.new(:name=>'YJM',
13
- # :albums_attributes=>[{:name=>'RF'}, {:name=>'MO'}])
14
- # # No database activity yet
15
10
  #
16
- # a.save # Saves artist and both albums
17
- # a.albums.map{|x| x.name} # ['RF', 'MO']
11
+ # The nested_attributes call defines a single method, <tt><i>association</i>_attributes=</tt>,
12
+ # (e.g. <tt>albums_attributes=</tt>). So if you have an Artist instance:
13
+ #
14
+ # a = Artist.new(:name=>'YJM')
15
+ #
16
+ # You can create new album instances related to this artist:
17
+ #
18
+ # a.albums_attributes = [{:name=>'RF'}, {:name=>'MO'}]
19
+ #
20
+ # Note that this doesn't send any queries to the database yet. That doesn't happen till
21
+ # you save the object:
22
+ #
23
+ # a.save
24
+ #
25
+ # That will save the artist first, and then save both albums. If either the artist
26
+ # is invalid or one of the albums is invalid, none of the objects will be saved to the
27
+ # database, and all related validation errors will be available in the artist's validation
28
+ # errors.
29
+ #
30
+ # In addition to creating new associated objects, you can also update existing associated
31
+ # objects. You just need to make sure that the primary key field is filled in for the
32
+ # associated object:
33
+ #
34
+ # a.update(:albums_attributes => [{:id=>1, :name=>'T'}])
35
+ #
36
+ # Since the primary key field is filled in, the plugin will update the album with id 1 instead
37
+ # of creating a new album.
38
+ #
39
+ # If you would like to delete the associated object instead of updating it, you add a _delete
40
+ # entry to the hash:
41
+ #
42
+ # a.update(:albums_attributes => [{:id=>1, :_delete=>true}])
43
+ #
44
+ # This will delete the related associated object from the database. If you want to leave the
45
+ # associated object in the database, but just remove it from the association, add a _remove
46
+ # entry in the hash:
47
+ #
48
+ # a.update(:albums_attributes => [{:id=>1, :_remove=>true}])
49
+ #
50
+ # The above example was for a one_to_many association, but the plugin also works similarly
51
+ # for other association types. For one_to_one and many_to_one associations, you need to
52
+ # pass a single hash instead of an array of hashes.
53
+ #
54
+ # This plugin is mainly designed to make it easy to use on html forms, where a single form
55
+ # submission can contained nested attributes (and even nested attributes of those attributes).
56
+ # You just need to name your form inputs correctly:
57
+ #
58
+ # artist[name]
59
+ # artist[albums_attributes][0][:name]
60
+ # artist[albums_attributes][1][:id]
61
+ # artist[albums_attributes][1][:name]
62
+ #
63
+ # Your web stack will probably parse that into a nested hash similar to:
64
+ #
65
+ # {:artist=>{:name=>?, :albums_attributes=>{0=>{:name=>?}, 1=>{:id=>?, :name=>?}}}}
66
+ #
67
+ # Then you can do:
68
+ #
69
+ # artist.update(params[:artist])
70
+ #
71
+ # To save changes to the artist, create the first album and associate it to the artist,
72
+ # and update the other existing associated album.
18
73
  module NestedAttributes
19
74
  # Depend on the instance_hooks plugin.
20
75
  def self.apply(model)
@@ -165,18 +165,12 @@ module Sequel
165
165
  super
166
166
  end
167
167
 
168
- # Serialize all deserialized values
168
+ # Serialize deserialized values before saving
169
169
  def before_save
170
- deserialized_values.each{|k,v| @values[k] = serialize_value(k, v)}
170
+ serialize_deserialized_values
171
171
  super
172
172
  end
173
173
 
174
- # Empty the deserialized values when refreshing.
175
- def refresh
176
- @deserialized_values = {}
177
- super
178
- end
179
-
180
174
  # Initialization the deserialized values for objects retrieved from the database.
181
175
  def set_values(*)
182
176
  @deserialized_values ||= {}
@@ -185,6 +179,12 @@ module Sequel
185
179
 
186
180
  private
187
181
 
182
+ # Empty the deserialized values when refreshing.
183
+ def _refresh(*)
184
+ @deserialized_values = {}
185
+ super
186
+ end
187
+
188
188
  # Deserialize the column value. Called when the model column accessor is called to
189
189
  # return a deserialized value.
190
190
  def deserialize_value(column, v)
@@ -194,6 +194,11 @@ module Sequel
194
194
  end
195
195
  end
196
196
 
197
+ # Serialize all deserialized values
198
+ def serialize_deserialized_values
199
+ deserialized_values.each{|k,v| @values[k] = serialize_value(k, v)}
200
+ end
201
+
197
202
  # Serialize the column value. Called before saving to ensure the serialized value
198
203
  # is saved in the database.
199
204
  def serialize_value(column, v)
@@ -1,11 +1,16 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # Sequel's built in Serialization plugin doesn't check for modification
4
- # of the serialized objects, because it requires an extra deserialization of a potentially
5
- # very large object. This plugin can detect changes in serialized values by
6
- # checking whether the current deserialized value is the same as the original
7
- # deserialized value. This does require deserializing the value twice, but the
8
- # original deserialized value is cached.
3
+ # This plugin extends the serialization plugin and enables it to detect
4
+ # changes in serialized values by checking whether the current
5
+ # deserialized value is the same as the original deserialized value.
6
+ # The serialization plugin does not do such checks by default, as they
7
+ # often aren't needed and can hurt performance.
8
+ #
9
+ # Note that for this plugin to work correctly, the values you are
10
+ # serializing must roundtrip correctly (i.e. deserialize(serialize(value))
11
+ # should equal value). This is true in most cases, but not in all. For
12
+ # example, ruby symbols round trip through yaml, but not json (as they get
13
+ # turned into strings in json).
9
14
  #
10
15
  # == Example
11
16
  #
@@ -25,6 +30,13 @@ module Sequel
25
30
  end
26
31
 
27
32
  module InstanceMethods
33
+ # Clear the cache of original deserialized values after saving so that it doesn't
34
+ # show the column is modified after saving.
35
+ def after_save
36
+ super
37
+ @original_deserialized_values = {}
38
+ end
39
+
28
40
  # Detect which serialized columns have changed.
29
41
  def changed_columns
30
42
  cc = super
@@ -34,11 +46,11 @@ module Sequel
34
46
 
35
47
  private
36
48
 
37
- # Clear the cache of original deserialized values after saving so that it doesn't
38
- # show the column is modified after saving.
39
- def after_save
49
+ # For new objects, serialize any existing deserialized values so that changes can
50
+ # be detected.
51
+ def initialize_set(values)
40
52
  super
41
- @original_deserialized_values.clear if @original_deserialized_values
53
+ serialize_deserialized_values
42
54
  end
43
55
 
44
56
  # Return the original deserialized value of the column, caching it to improve performance.
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 31
6
+ MINOR = 32
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -75,16 +75,16 @@ describe "MSSQL full_text_search" do
75
75
 
76
76
  specify "should support fulltext indexes and full_text_search" do
77
77
  log do
78
- @db.create_table(:posts){Integer :id, :null=>false; String :title; String :body; index :id, :name=>:fts_id_idx, :unique=>true; full_text_index :title, :key_index=>:fts_id_idx; full_text_index [:title, :body], :key_index=>:fts_id_idx}
79
- @db[:posts].insert(:title=>'ruby rails', :body=>'y')
80
- @db[:posts].insert(:title=>'sequel', :body=>'ruby')
81
- @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
78
+ @db.create_table(:posts){Integer :id, :null=>false; String :title; String :body; index :id, :name=>:fts_id_idx, :unique=>true; full_text_index :title, :key_index=>:fts_id_idx; full_text_index [:title, :body], :key_index=>:fts_id_idx}
79
+ @db[:posts].insert(:title=>'ruby rails', :body=>'y')
80
+ @db[:posts].insert(:title=>'sequel', :body=>'ruby')
81
+ @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
82
82
 
83
- @db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'y'}]
84
- @db[:posts].full_text_search([:title, :body], ['sequel', 'ruby']).all.should == [{:title=>'sequel', :body=>'ruby'}]
83
+ @db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'y'}]
84
+ @db[:posts].full_text_search([:title, :body], ['sequel', 'ruby']).all.should == [{:title=>'sequel', :body=>'ruby'}]
85
85
 
86
- @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
87
- @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
86
+ @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
87
+ @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
88
88
  end
89
89
  end
90
90
  end if false
@@ -94,8 +94,8 @@ describe "MSSQL Dataset#join_table" do
94
94
  MSSQL_DB[:items].join(:categories, [:id]).sql.should ==
95
95
  'SELECT * FROM [ITEMS] INNER JOIN [CATEGORIES] ON ([CATEGORIES].[ID] = [ITEMS].[ID])'
96
96
  ['SELECT * FROM [ITEMS] INNER JOIN [CATEGORIES] ON (([CATEGORIES].[ID1] = [ITEMS].[ID1]) AND ([CATEGORIES].[ID2] = [ITEMS].[ID2]))',
97
- 'SELECT * FROM [ITEMS] INNER JOIN [CATEGORIES] ON (([CATEGORIES].[ID2] = [ITEMS].[ID2]) AND ([CATEGORIES].[ID1] = [ITEMS].[ID1]))'].
98
- should include(MSSQL_DB[:items].join(:categories, [:id1, :id2]).sql)
97
+ 'SELECT * FROM [ITEMS] INNER JOIN [CATEGORIES] ON (([CATEGORIES].[ID2] = [ITEMS].[ID2]) AND ([CATEGORIES].[ID1] = [ITEMS].[ID1]))'].
98
+ should include(MSSQL_DB[:items].join(:categories, [:id1, :id2]).sql)
99
99
  MSSQL_DB[:items___i].join(:categories___c, [:id]).sql.should ==
100
100
  'SELECT * FROM [ITEMS] AS [I] INNER JOIN [CATEGORIES] AS [C] ON ([C].[ID] = [I].[ID])'
101
101
  end
@@ -522,3 +522,26 @@ describe "MSSQL::Database#mssql_unicode_strings = false" do
522
522
  ds.select_map(:name).should == ['foo']
523
523
  end
524
524
  end
525
+
526
+ describe "A MSSQL database adds index with include" do
527
+ before :all do
528
+ @table_name = :test_index_include
529
+ @db = MSSQL_DB
530
+ @db.create_table! @table_name do
531
+ integer :col1
532
+ integer :col2
533
+ integer :col3
534
+ end
535
+ end
536
+
537
+ after :all do
538
+ @db.drop_table @table_name
539
+ end
540
+
541
+ cspecify "should be able add index with include" do
542
+ @db.alter_table @table_name do
543
+ add_index [:col1], :include => [:col2,:col3]
544
+ end
545
+ @db.indexes(@table_name).should have_key("#{@table_name}_col1_index".to_sym)
546
+ end
547
+ end
@@ -42,27 +42,27 @@ describe "MySQL", '#create_table' do
42
42
  after do
43
43
  @db.drop_table(:dolls) rescue nil
44
44
  end
45
-
45
+
46
46
  specify "should allow to specify options for MySQL" do
47
47
  @db.create_table(:dolls, :engine => 'MyISAM', :charset => 'latin2'){text :name}
48
48
  @db.sqls.should == ["CREATE TABLE `dolls` (`name` text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
49
49
  end
50
-
50
+
51
51
  specify "should create a temporary table" do
52
52
  @db.create_table(:tmp_dolls, :temp => true, :engine => 'MyISAM', :charset => 'latin2'){text :name}
53
53
  @db.sqls.should == ["CREATE TEMPORARY TABLE `tmp_dolls` (`name` text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
54
54
  end
55
-
55
+
56
56
  specify "should not use a default for a String :text=>true type" do
57
57
  @db.create_table(:dolls){String :name, :text=>true, :default=>'blah'}
58
58
  @db.sqls.should == ["CREATE TABLE `dolls` (`name` text)"]
59
59
  end
60
-
60
+
61
61
  specify "should not use a default for a File type" do
62
62
  @db.create_table(:dolls){File :name, :default=>'blah'}
63
63
  @db.sqls.should == ["CREATE TABLE `dolls` (`name` blob)"]
64
64
  end
65
-
65
+
66
66
  specify "should respect the size option for File type" do
67
67
  @db.create_table(:dolls) do
68
68
  File :n1
@@ -94,11 +94,11 @@ describe "A MySQL database" do
94
94
  specify "should provide the server version" do
95
95
  MYSQL_DB.server_version.should >= 40000
96
96
  end
97
-
97
+
98
98
  specify "should handle the creation and dropping of an InnoDB table with foreign keys" do
99
99
  proc{MYSQL_DB.create_table!(:test_innodb, :engine=>:InnoDB){primary_key :id; foreign_key :fk, :test_innodb, :key=>:id}}.should_not raise_error
100
100
  end
101
-
101
+
102
102
  specify "should support for_share" do
103
103
  MYSQL_DB.transaction{MYSQL_DB[:test2].for_share.all.should == []}
104
104
  end
@@ -115,13 +115,13 @@ if MYSQL_DB.adapter_scheme == :mysql
115
115
  @db.convert_tinyint_to_bool = true
116
116
  @db.drop_table(:booltest)
117
117
  end
118
-
118
+
119
119
  specify "should consider tinyint(1) datatypes as boolean if set, but not larger tinyints" do
120
120
  @db.schema(:booltest, :reload=>true).should == [[:b, {:type=>:boolean, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(1)"}, ], [:i, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}, ]]
121
121
  @db.convert_tinyint_to_bool = false
122
122
  @db.schema(:booltest, :reload=>true).should == [[:b, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(1)"}, ], [:i, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}, ]]
123
123
  end
124
-
124
+
125
125
  specify "should return tinyint(1)s as bools and tinyint(4)s as integers when set" do
126
126
  @db.convert_tinyint_to_bool = true
127
127
  @ds.delete
@@ -143,7 +143,7 @@ if MYSQL_DB.adapter_scheme == :mysql
143
143
  @ds.delete
144
144
  @ds << {:b=>false, :i=>0}
145
145
  @ds.all.should == [{:b=>0, :i=>0}]
146
-
146
+
147
147
  @ds.delete
148
148
  @ds << {:b=>1, :i=>10}
149
149
  @ds.all.should == [{:b=>1, :i=>10}]
@@ -163,18 +163,18 @@ describe "A MySQL dataset" do
163
163
  after do
164
164
  MYSQL_DB.drop_table(:items)
165
165
  end
166
-
166
+
167
167
  specify "should quote columns and tables using back-ticks if quoting identifiers" do
168
168
  @d.quote_identifiers = true
169
169
  @d.select(:name).sql.should == \
170
170
  'SELECT `name` FROM `items`'
171
-
171
+
172
172
  @d.select('COUNT(*)'.lit).sql.should == \
173
173
  'SELECT COUNT(*) FROM `items`'
174
174
 
175
175
  @d.select(:max.sql_function(:value)).sql.should == \
176
176
  'SELECT max(`value`) FROM `items`'
177
-
177
+
178
178
  @d.select(:NOW.sql_function).sql.should == \
179
179
  'SELECT NOW() FROM `items`'
180
180
 
@@ -186,13 +186,13 @@ describe "A MySQL dataset" do
186
186
 
187
187
  @d.select('items.name AS item_name'.lit).sql.should == \
188
188
  'SELECT items.name AS item_name FROM `items`'
189
-
189
+
190
190
  @d.select('`name`'.lit).sql.should == \
191
191
  'SELECT `name` FROM `items`'
192
192
 
193
193
  @d.select('max(items.`name`) AS `max_name`'.lit).sql.should == \
194
194
  'SELECT max(items.`name`) AS `max_name` FROM `items`'
195
-
195
+
196
196
  @d.select(:test.sql_function(:abc, 'hello')).sql.should == \
197
197
  "SELECT test(`abc`, 'hello') FROM `items`"
198
198
 
@@ -208,7 +208,7 @@ describe "A MySQL dataset" do
208
208
  @d.insert_sql(:x => :y).should == \
209
209
  'INSERT INTO `items` (`x`) VALUES (`y`)'
210
210
  end
211
-
211
+
212
212
  specify "should quote fields correctly when reversing the order" do
213
213
  @d.quote_identifiers = true
214
214
  @d.reverse_order(:name).sql.should == \
@@ -223,31 +223,31 @@ describe "A MySQL dataset" do
223
223
  @d.reverse_order(:name.desc, :test).sql.should == \
224
224
  'SELECT * FROM `items` ORDER BY `name` ASC, `test` DESC'
225
225
  end
226
-
226
+
227
227
  specify "should support ORDER clause in UPDATE statements" do
228
228
  @d.order(:name).update_sql(:value => 1).should == \
229
229
  'UPDATE `items` SET `value` = 1 ORDER BY `name`'
230
230
  end
231
-
231
+
232
232
  specify "should support LIMIT clause in UPDATE statements" do
233
233
  @d.limit(10).update_sql(:value => 1).should == \
234
234
  'UPDATE `items` SET `value` = 1 LIMIT 10'
235
235
  end
236
-
236
+
237
237
  specify "should support regexps" do
238
238
  @d << {:name => 'abc', :value => 1}
239
239
  @d << {:name => 'bcd', :value => 2}
240
240
  @d.filter(:name => /bc/).count.should == 2
241
241
  @d.filter(:name => /^bc/).count.should == 1
242
242
  end
243
-
243
+
244
244
  specify "should correctly literalize strings with comment backslashes in them" do
245
245
  @d.delete
246
246
  proc {@d << {:name => ':\\'}}.should_not raise_error
247
-
247
+
248
248
  @d.first[:name].should == ':\\'
249
249
  end
250
-
250
+
251
251
  specify "should handle prepared statements with on_duplicate_key_update" do
252
252
  @d.db.add_index :items, :value, :unique=>true
253
253
  ds = @d.on_duplicate_key_update
@@ -263,7 +263,7 @@ describe "MySQL datasets" do
263
263
  before do
264
264
  @d = MYSQL_DB[:orders]
265
265
  end
266
-
266
+
267
267
  specify "should correctly quote column references" do
268
268
  @d.quote_identifiers = true
269
269
  market = 'ICE'
@@ -287,7 +287,7 @@ describe "Dataset#distinct" do
287
287
  after do
288
288
  @db.drop_table(:a)
289
289
  end
290
-
290
+
291
291
  it "#distinct with arguments should return results distinct on those arguments" do
292
292
  @ds.insert(20, 10)
293
293
  @ds.insert(30, 10)
@@ -353,20 +353,20 @@ describe "Joined MySQL dataset" do
353
353
  before do
354
354
  @ds = MYSQL_DB[:nodes]
355
355
  end
356
-
356
+
357
357
  specify "should quote fields correctly" do
358
358
  @ds.quote_identifiers = true
359
359
  @ds.join(:attributes, :node_id => :id).sql.should == \
360
360
  "SELECT * FROM `nodes` INNER JOIN `attributes` ON (`attributes`.`node_id` = `nodes`.`id`)"
361
361
  end
362
-
362
+
363
363
  specify "should allow a having clause on ungrouped datasets" do
364
364
  proc {@ds.having('blah')}.should_not raise_error
365
365
 
366
366
  @ds.having('blah').sql.should == \
367
367
  "SELECT * FROM `nodes` HAVING (blah)"
368
368
  end
369
-
369
+
370
370
  specify "should put a having clause before an order by clause" do
371
371
  @ds.order(:aaa).having(:bbb => :ccc).sql.should == \
372
372
  "SELECT * FROM `nodes` HAVING (`bbb` = `ccc`) ORDER BY `aaa`"
@@ -380,19 +380,19 @@ describe "A MySQL database" do
380
380
 
381
381
  specify "should support add_column operations" do
382
382
  @db.add_column :test2, :xyz, :text
383
-
383
+
384
384
  @db[:test2].columns.should == [:name, :value, :xyz]
385
385
  @db[:test2] << {:name => 'mmm', :value => 111, :xyz => '000'}
386
386
  @db[:test2].first[:xyz].should == '000'
387
387
  end
388
-
388
+
389
389
  specify "should support drop_column operations" do
390
390
  @db[:test2].columns.should == [:name, :value, :xyz]
391
391
  @db.drop_column :test2, :xyz
392
-
392
+
393
393
  @db[:test2].columns.should == [:name, :value]
394
394
  end
395
-
395
+
396
396
  specify "should support rename_column operations" do
397
397
  @db[:test2].delete
398
398
  @db.add_column :test2, :xyz, :text
@@ -403,7 +403,7 @@ describe "A MySQL database" do
403
403
  @db[:test2].columns.should == [:name, :value, :zyx]
404
404
  @db[:test2].first[:zyx].should == 'qqqq'
405
405
  end
406
-
406
+
407
407
  specify "should support rename_column operations with types like varchar(255)" do
408
408
  @db[:test2].delete
409
409
  @db.add_column :test2, :tre, :text
@@ -414,24 +414,24 @@ describe "A MySQL database" do
414
414
  @db[:test2].columns.should == [:name, :value, :zyx, :ert]
415
415
  @db[:test2].first[:ert].should == 'qqqq'
416
416
  end
417
-
417
+
418
418
  specify "should support set_column_type operations" do
419
419
  @db.add_column :test2, :xyz, :float
420
420
  @db[:test2].delete
421
421
  @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
422
422
  @db.set_column_type :test2, :xyz, :integer
423
-
423
+
424
424
  @db[:test2].first[:xyz].should == 57
425
425
  end
426
-
426
+
427
427
  specify "should support add_index" do
428
428
  @db.add_index :test2, :value
429
429
  end
430
-
430
+
431
431
  specify "should support drop_index" do
432
432
  @db.drop_index :test2, :value
433
433
  end
434
-
434
+
435
435
  specify "should support add_foreign_key" do
436
436
  @db.alter_table :test2 do
437
437
  add_foreign_key :value2, :test2, :key=>:value
@@ -443,14 +443,14 @@ end
443
443
  describe "A MySQL database with table options" do
444
444
  before do
445
445
  @options = {:engine=>'MyISAM', :charset=>'latin1', :collate => 'latin1_swedish_ci'}
446
-
446
+
447
447
  Sequel::MySQL.default_engine = 'InnoDB'
448
448
  Sequel::MySQL.default_charset = 'utf8'
449
449
  Sequel::MySQL.default_collate = 'utf8_general_ci'
450
-
450
+
451
451
  @db = MYSQL_DB
452
452
  @db.drop_table(:items) rescue nil
453
-
453
+
454
454
  MYSQL_DB.sqls.clear
455
455
  end
456
456
  after do
@@ -460,17 +460,17 @@ describe "A MySQL database with table options" do
460
460
  Sequel::MySQL.default_charset = nil
461
461
  Sequel::MySQL.default_collate = nil
462
462
  end
463
-
463
+
464
464
  specify "should allow to pass custom options (engine, charset, collate) for table creation" do
465
465
  @db.create_table(:items, @options){Integer :size; text :name}
466
466
  @db.sqls.should == ["CREATE TABLE `items` (`size` integer, `name` text) ENGINE=MyISAM DEFAULT CHARSET=latin1 DEFAULT COLLATE=latin1_swedish_ci"]
467
467
  end
468
-
468
+
469
469
  specify "should use default options if specified (engine, charset, collate) for table creation" do
470
470
  @db.create_table(:items){Integer :size; text :name}
471
471
  @db.sqls.should == ["CREATE TABLE `items` (`size` integer, `name` text) ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci"]
472
472
  end
473
-
473
+
474
474
  specify "should not use default if option has a nil value" do
475
475
  @db.create_table(:items, :engine=>nil, :charset=>nil, :collate=>nil){Integer :size; text :name}
476
476
  @db.sqls.should == ["CREATE TABLE `items` (`size` integer, `name` text)"]
@@ -486,23 +486,23 @@ describe "A MySQL database" do
486
486
  after do
487
487
  @db.drop_table(:items) rescue nil
488
488
  end
489
-
489
+
490
490
  specify "should support defaults for boolean columns" do
491
491
  @db.create_table(:items){TrueClass :active1, :default=>true; FalseClass :active2, :default => false}
492
492
  @db.sqls.should == ["CREATE TABLE `items` (`active1` tinyint(1) DEFAULT 1, `active2` tinyint(1) DEFAULT 0)"]
493
493
  end
494
-
494
+
495
495
  specify "should correctly format CREATE TABLE statements with foreign keys" do
496
496
  @db.create_table(:items){Integer :id; foreign_key :p_id, :items, :key => :id, :null => false, :on_delete => :cascade}
497
497
  @db.sqls.should == ["CREATE TABLE `items` (`id` integer, `p_id` integer NOT NULL, FOREIGN KEY (`p_id`) REFERENCES `items`(`id`) ON DELETE CASCADE)"]
498
498
  end
499
-
499
+
500
500
  specify "should correctly format ALTER TABLE statements with foreign keys" do
501
501
  @db.create_table(:items){Integer :id}
502
502
  @db.alter_table(:items){add_foreign_key :p_id, :users, :key => :id, :null => false, :on_delete => :cascade}
503
503
  @db.sqls.should == ["CREATE TABLE `items` (`id` integer)", "ALTER TABLE `items` ADD COLUMN `p_id` integer NOT NULL", "ALTER TABLE `items` ADD FOREIGN KEY (`p_id`) REFERENCES `users`(`id`) ON DELETE CASCADE"]
504
504
  end
505
-
505
+
506
506
  specify "should have rename_column support keep existing options" do
507
507
  @db.create_table(:items){String :id, :null=>false, :default=>'blah'}
508
508
  @db.alter_table(:items){rename_column :id, :nid}
@@ -511,7 +511,7 @@ describe "A MySQL database" do
511
511
  @db[:items].all.should == [{:nid=>'blah'}]
512
512
  proc{@db[:items].insert(:nid=>nil)}.should raise_error(Sequel::DatabaseError)
513
513
  end
514
-
514
+
515
515
  specify "should have set_column_type support keep existing options" do
516
516
  @db.create_table(:items){Integer :id, :null=>false, :default=>5}
517
517
  @db.alter_table(:items){set_column_type :id, Bignum}
@@ -529,7 +529,7 @@ describe "A MySQL database" do
529
529
  @db.alter_table(:items){set_column_type :id, :int, :unsigned=>true, :size=>8; set_column_type :list, :enum, :elements=>%w[two]}
530
530
  @db.sqls.should == ["CREATE TABLE `items` (`id` integer, `list` enum('one'))", "DESCRIBE `items`", "ALTER TABLE `items` CHANGE COLUMN `id` `id` int(8) UNSIGNED NULL", "ALTER TABLE `items` CHANGE COLUMN `list` `list` enum('two') NULL"]
531
531
  end
532
-
532
+
533
533
  specify "should have set_column_default support keep existing options" do
534
534
  @db.create_table(:items){Integer :id, :null=>false, :default=>5}
535
535
  @db.alter_table(:items){set_column_default :id, 6}
@@ -538,7 +538,7 @@ describe "A MySQL database" do
538
538
  @db[:items].all.should == [{:id=>6}]
539
539
  proc{@db[:items].insert(:id=>nil)}.should raise_error(Sequel::DatabaseError)
540
540
  end
541
-
541
+
542
542
  specify "should have set_column_allow_null support keep existing options" do
543
543
  @db.create_table(:items){Integer :id, :null=>false, :default=>5}
544
544
  @db.alter_table(:items){set_column_allow_null :id, true}
@@ -547,15 +547,15 @@ describe "A MySQL database" do
547
547
  @db[:items].all.should == [{:id=>5}]
548
548
  proc{@db[:items].insert(:id=>nil)}.should_not
549
549
  end
550
-
550
+
551
551
  specify "should accept repeated raw sql statements using Database#<<" do
552
552
  @db.create_table(:items){String :name; Integer :value}
553
553
  @db << 'DELETE FROM items'
554
554
  @db[:items].count.should == 0
555
-
555
+
556
556
  @db << "INSERT INTO items (name, value) VALUES ('tutu', 1234)"
557
557
  @db[:items].first.should == {:name => 'tutu', :value => 1234}
558
-
558
+
559
559
  @db << 'DELETE FROM items'
560
560
  @db[:items].first.should == nil
561
561
  end
@@ -568,12 +568,12 @@ if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.adapter_sch
568
568
  db = Sequel.mysql(MYSQL_DB.opts[:database], :host => 'localhost', :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
569
569
  proc {db.test_connection}.should_not raise_error
570
570
  end
571
-
571
+
572
572
  specify "should accept a socket option without host option" do
573
573
  db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
574
574
  proc {db.test_connection}.should_not raise_error
575
575
  end
576
-
576
+
577
577
  specify "should fail to connect with invalid socket" do
578
578
  db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket =>'blah')
579
579
  proc {db.test_connection}.should raise_error
@@ -603,12 +603,12 @@ describe "A grouped MySQL dataset" do
603
603
  MYSQL_DB[:test2] << {:name => '12', :value => 20}
604
604
  MYSQL_DB[:test2] << {:name => '13', :value => 10}
605
605
  end
606
-
606
+
607
607
  specify "should return the correct count for raw sql query" do
608
608
  ds = MYSQL_DB["select name FROM test2 WHERE name = '11' GROUP BY name"]
609
609
  ds.count.should == 1
610
610
  end
611
-
611
+
612
612
  specify "should return the correct count for a normal dataset" do
613
613
  ds = MYSQL_DB[:test2].select(:name).where(:name => '11').group(:name)
614
614
  ds.count.should == 1
@@ -624,7 +624,7 @@ describe "A MySQL database" do
624
624
  after do
625
625
  @db.drop_table(:posts) rescue nil
626
626
  end
627
-
627
+
628
628
  specify "should support fulltext indexes and full_text_search" do
629
629
  @db.create_table(:posts){text :title; text :body; full_text_index :title; full_text_index [:title, :body]}
630
630
  @db.sqls.should == [
@@ -632,7 +632,7 @@ describe "A MySQL database" do
632
632
  "CREATE FULLTEXT INDEX `posts_title_index` ON `posts` (`title`)",
633
633
  "CREATE FULLTEXT INDEX `posts_title_body_index` ON `posts` (`title`, `body`)"
634
634
  ]
635
-
635
+
636
636
  @db[:posts].insert(:title=>'ruby rails', :body=>'y')
637
637
  @db[:posts].insert(:title=>'sequel', :body=>'ruby')
638
638
  @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
@@ -732,7 +732,7 @@ describe "MySQL::Dataset#insert and related methods" do
732
732
 
733
733
  specify "#multi_insert should insert multiple records in a single statement" do
734
734
  @d.multi_insert([{:name => 'abc'}, {:name => 'def'}])
735
-
735
+
736
736
  MYSQL_DB.sqls.should == [
737
737
  SQL_BEGIN,
738
738
  "INSERT INTO `items` (`name`) VALUES ('abc'), ('def')",
@@ -756,7 +756,7 @@ describe "MySQL::Dataset#insert and related methods" do
756
756
  "INSERT INTO `items` (`value`) VALUES (3), (4)",
757
757
  SQL_COMMIT
758
758
  ]
759
-
759
+
760
760
  @d.all.should == [
761
761
  {:name => nil, :value => 1},
762
762
  {:name => nil, :value => 2},
@@ -777,7 +777,7 @@ describe "MySQL::Dataset#insert and related methods" do
777
777
  "INSERT INTO `items` (`value`) VALUES (3), (4)",
778
778
  SQL_COMMIT
779
779
  ]
780
-
780
+
781
781
  @d.all.should == [
782
782
  {:name => nil, :value => 1},
783
783
  {:name => nil, :value => 2},
@@ -785,7 +785,7 @@ describe "MySQL::Dataset#insert and related methods" do
785
785
  {:name => nil, :value => 4}
786
786
  ]
787
787
  end
788
-
788
+
789
789
  specify "#import should support inserting using columns and values arrays" do
790
790
  @d.import([:name, :value], [['abc', 1], ['def', 2]])
791
791
 
@@ -794,16 +794,16 @@ describe "MySQL::Dataset#insert and related methods" do
794
794
  "INSERT INTO `items` (`name`, `value`) VALUES ('abc', 1), ('def', 2)",
795
795
  SQL_COMMIT
796
796
  ]
797
-
797
+
798
798
  @d.all.should == [
799
799
  {:name => 'abc', :value => 1},
800
800
  {:name => 'def', :value => 2}
801
801
  ]
802
802
  end
803
-
803
+
804
804
  specify "#insert_ignore should add the IGNORE keyword when inserting" do
805
805
  @d.insert_ignore.multi_insert([{:name => 'abc'}, {:name => 'def'}])
806
-
806
+
807
807
  MYSQL_DB.sqls.should == [
808
808
  SQL_BEGIN,
809
809
  "INSERT IGNORE INTO `items` (`name`) VALUES ('abc'), ('def')",
@@ -814,16 +814,16 @@ describe "MySQL::Dataset#insert and related methods" do
814
814
  {:name => 'abc', :value => nil}, {:name => 'def', :value => nil}
815
815
  ]
816
816
  end
817
-
817
+
818
818
  specify "#insert_ignore should add the IGNORE keyword for single inserts" do
819
819
  @d.insert_ignore.insert(:name => 'ghi')
820
820
  MYSQL_DB.sqls.should == ["INSERT IGNORE INTO `items` (`name`) VALUES ('ghi')"]
821
821
  @d.all.should == [{:name => 'ghi', :value => nil}]
822
822
  end
823
-
823
+
824
824
  specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and ALL columns when no args given" do
825
825
  @d.on_duplicate_key_update.import([:name,:value], [['abc', 1], ['def',2]])
826
-
826
+
827
827
  MYSQL_DB.sqls.should == [
828
828
  "SELECT * FROM `items` LIMIT 1",
829
829
  SQL_BEGIN,
@@ -835,12 +835,12 @@ describe "MySQL::Dataset#insert and related methods" do
835
835
  {:name => 'abc', :value => 1}, {:name => 'def', :value => 2}
836
836
  ]
837
837
  end
838
-
838
+
839
839
  specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and columns specified when args are given" do
840
840
  @d.on_duplicate_key_update(:value).import([:name,:value],
841
841
  [['abc', 1], ['def',2]]
842
842
  )
843
-
843
+
844
844
  MYSQL_DB.sqls.should == [
845
845
  SQL_BEGIN,
846
846
  "INSERT INTO `items` (`name`, `value`) VALUES ('abc', 1), ('def', 2) ON DUPLICATE KEY UPDATE `value`=VALUES(`value`)",
@@ -851,7 +851,27 @@ describe "MySQL::Dataset#insert and related methods" do
851
851
  {:name => 'abc', :value => 1}, {:name => 'def', :value => 2}
852
852
  ]
853
853
  end
854
-
854
+
855
+ end
856
+
857
+ describe "MySQL::Dataset#update and related methods" do
858
+ before do
859
+ MYSQL_DB.create_table(:items){String :name; Integer :value; index :name, :unique=>true}
860
+ @d = MYSQL_DB[:items]
861
+ end
862
+ after do
863
+ MYSQL_DB.drop_table(:items)
864
+ end
865
+
866
+ specify "#update_ignore should not raise error where normal update would fail" do
867
+ @d.insert(:name => 'cow', :value => 0)
868
+ @d.insert(:name => 'cat', :value => 1)
869
+ proc{@d.where(:value => 1).update(:name => 'cow')}.should raise_error(Sequel::DatabaseError)
870
+ MYSQL_DB.sqls.clear
871
+ @d.update_ignore.where(:value => 1).update(:name => 'cow')
872
+ MYSQL_DB.sqls.should == ["UPDATE IGNORE `items` SET `name` = 'cow' WHERE (`value` = 1)"]
873
+ @d.order(:name).all.should == [{:name => 'cat', :value => 1}, {:name => 'cow', :value => 0}]
874
+ end
855
875
  end
856
876
 
857
877
  describe "MySQL::Dataset#replace" do
@@ -863,7 +883,7 @@ describe "MySQL::Dataset#replace" do
863
883
  after do
864
884
  MYSQL_DB.drop_table(:items)
865
885
  end
866
-
886
+
867
887
  specify "should use default values if they exist" do
868
888
  MYSQL_DB.alter_table(:items){set_column_default :id, 1; set_column_default :value, 2}
869
889
  @d.replace
@@ -873,7 +893,7 @@ describe "MySQL::Dataset#replace" do
873
893
  @d.replace({})
874
894
  @d.all.should == [{:id=>1, :value=>2}]
875
895
  end
876
-
896
+
877
897
  specify "should use support arrays, datasets, and multiple values" do
878
898
  @d.replace([1, 2])
879
899
  @d.all.should == [{:id=>1, :value=>2}]
@@ -882,12 +902,12 @@ describe "MySQL::Dataset#replace" do
882
902
  @d.replace(@d)
883
903
  @d.all.should == [{:id=>1, :value=>2}]
884
904
  end
885
-
905
+
886
906
  specify "should create a record if the condition is not met" do
887
907
  @d.replace(:id => 111, :value => 333)
888
908
  @d.all.should == [{:id => 111, :value => 333}]
889
909
  end
890
-
910
+
891
911
  specify "should update a record if the condition is met" do
892
912
  @d << {:id => 111}
893
913
  @d.all.should == [{:id => 111, :value => nil}]
@@ -962,7 +982,7 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc or MYSQ
962
982
  MYSQL_DB.drop_table(:items)
963
983
  MYSQL_DB.execute('DROP PROCEDURE test_sproc')
964
984
  end
965
-
985
+
966
986
  specify "should be callable on the database object" do
967
987
  MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc() BEGIN DELETE FROM items; END')
968
988
  MYSQL_DB[:items].delete
@@ -971,7 +991,7 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc or MYSQ
971
991
  MYSQL_DB.call_sproc(:test_sproc)
972
992
  MYSQL_DB[:items].count.should == 0
973
993
  end
974
-
994
+
975
995
  # Mysql2 doesn't support stored procedures that return result sets, probably because
976
996
  # CLIENT_MULTI_RESULTS is not set.
977
997
  unless MYSQL_DB.adapter_scheme == :mysql2
@@ -985,7 +1005,7 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc or MYSQ
985
1005
  @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
986
1006
  @d.call_sproc(:select, :test_sproc, 3).should == [{:id=>nil, :value=>2, :b=>6}]
987
1007
  end
988
-
1008
+
989
1009
  specify "should be callable on the dataset object with multiple arguments" do
990
1010
  MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc(a INTEGER, c INTEGER) BEGIN SELECT *, a AS b, c AS d FROM items; END')
991
1011
  MYSQL_DB[:items].delete
@@ -1012,14 +1032,14 @@ if MYSQL_DB.adapter_scheme == :mysql
1012
1032
  after do
1013
1033
  MYSQL_DB.convert_invalid_date_time = false
1014
1034
  end
1015
-
1035
+
1016
1036
  specify "should raise an exception when a bad date/time is used and convert_invalid_date_time is false" do
1017
1037
  MYSQL_DB.convert_invalid_date_time = false
1018
1038
  proc{MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value}.should raise_error(Sequel::InvalidValue)
1019
1039
  proc{MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value}.should raise_error(Sequel::InvalidValue)
1020
1040
  proc{MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value}.should raise_error(Sequel::InvalidValue)
1021
1041
  end
1022
-
1042
+
1023
1043
  specify "should not use a nil value bad date/time is used and convert_invalid_date_time is nil or :nil" do
1024
1044
  MYSQL_DB.convert_invalid_date_time = nil
1025
1045
  MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value.should == nil
@@ -1030,7 +1050,7 @@ if MYSQL_DB.adapter_scheme == :mysql
1030
1050
  MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value.should == nil
1031
1051
  MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == nil
1032
1052
  end
1033
-
1053
+
1034
1054
  specify "should not use a nil value bad date/time is used and convert_invalid_date_time is :string" do
1035
1055
  MYSQL_DB.convert_invalid_date_time = :string
1036
1056
  MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value.should == '0000-00-00'
@@ -1038,7 +1058,7 @@ if MYSQL_DB.adapter_scheme == :mysql
1038
1058
  MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == '25:00:00'
1039
1059
  end
1040
1060
  end
1041
-
1061
+
1042
1062
  describe "MySQL multiple result sets" do
1043
1063
  before do
1044
1064
  MYSQL_DB.create_table!(:a){Integer :a}
@@ -1052,39 +1072,39 @@ if MYSQL_DB.adapter_scheme == :mysql
1052
1072
  after do
1053
1073
  MYSQL_DB.drop_table(:a, :b)
1054
1074
  end
1055
-
1075
+
1056
1076
  specify "should combine all results by default" do
1057
1077
  @ds.all.should == [{:a=>10}, {:a=>15}, {:b=>20}, {:b=>25}]
1058
1078
  end
1059
-
1079
+
1060
1080
  specify "should work with Database#run" do
1061
1081
  proc{MYSQL_DB.run('SELECT * FROM a; SELECT * FROM b')}.should_not raise_error
1062
1082
  proc{MYSQL_DB.run('SELECT * FROM a; SELECT * FROM b')}.should_not raise_error
1063
1083
  end
1064
-
1084
+
1065
1085
  specify "should work with Database#run and other statements" do
1066
1086
  proc{MYSQL_DB.run('UPDATE a SET a = 1; SELECT * FROM a; DELETE FROM b')}.should_not raise_error
1067
1087
  MYSQL_DB[:a].select_order_map(:a).should == [1, 1]
1068
1088
  MYSQL_DB[:b].all.should == []
1069
1089
  end
1070
-
1090
+
1071
1091
  specify "should split results returned into arrays if split_multiple_result_sets is used" do
1072
1092
  @ds.split_multiple_result_sets.all.should == [[{:a=>10}, {:a=>15}], [{:b=>20}, {:b=>25}]]
1073
1093
  end
1074
-
1094
+
1075
1095
  specify "should have regular row_procs work when splitting multiple result sets" do
1076
1096
  @ds.row_proc = proc{|x| x[x.keys.first] *= 2; x}
1077
1097
  @ds.split_multiple_result_sets.all.should == [[{:a=>20}, {:a=>30}], [{:b=>40}, {:b=>50}]]
1078
1098
  end
1079
-
1099
+
1080
1100
  specify "should use the columns from the first result set when splitting result sets" do
1081
1101
  @ds.split_multiple_result_sets.columns.should == [:a]
1082
1102
  end
1083
-
1103
+
1084
1104
  specify "should not allow graphing a dataset that splits multiple statements" do
1085
1105
  proc{@ds.split_multiple_result_sets.graph(:b, :b=>:a)}.should raise_error(Sequel::Error)
1086
1106
  end
1087
-
1107
+
1088
1108
  specify "should not allow splitting a graphed dataset" do
1089
1109
  proc{MYSQL_DB[:a].graph(:b, :b=>:a).split_multiple_result_sets}.should raise_error(Sequel::Error)
1090
1110
  end