sequel 3.31.0 → 3.32.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 (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