Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add Dataset#provides_accurate_rows_matched?, for seeing if update and…

… delete are likely to return correct numbers

Some adapters don't have support for returning the correct number of
matching rows (such as ADO).  Others, such as MySQL using the native
and do adapters, may return a number that isn't the same as the
number of matching rows.  This flag allows you to check if the
dataset supports this and act accordingly.

The Sequel::Model code uses this flag to determine whether to
set require_modification for the model class if you haven't set a
global default.
  • Loading branch information...
commit ddccd464714df454b90151ce0e0ac28cc6b3dd82 1 parent 21ef8d4
@jeremyevans authored
View
2  CHANGELOG
@@ -1,5 +1,7 @@
=== HEAD
+* Add Dataset#provides_accurate_rows_matched?, for seeing if update and delete are likely to return correct numbers (jeremyevans)
+
* Add require_modification to Sequel::Model, for checking that model instance updating and deleting affects a single row (jeremyevans)
* Fix leak of ResultSets when getting metadata in the jdbc adapter (jrun)
View
5 lib/sequel/adapters/ado.rb
@@ -80,6 +80,11 @@ def fetch_rows(sql)
s.getRows.transpose.each{|r| yield cols.inject({}){|m,c| m[c] = r.shift; m}} unless s.eof
end
end
+
+ # ADO returns nil for all for delete and update statements.
+ def provides_accurate_rows_matched?
+ false
+ end
end
end
end
View
5 lib/sequel/adapters/jdbc/mysql.rb
@@ -63,6 +63,11 @@ def insert(*values)
def replace(*args)
execute_insert(replace_sql(*args))
end
+
+ # MySQL on JDBC does provides an accurate number of rows matched.
+ def provides_accurate_rows_matched?
+ true
+ end
end
end
end
View
6 lib/sequel/adapters/shared/mysql.rb
@@ -342,6 +342,12 @@ def multi_insert_sql(columns, values)
[insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
end
+ # MySQL uses the number of rows actually modified in the update,
+ # instead of the number of matched by the filter.
+ def provides_accurate_rows_matched?
+ false
+ end
+
# MySQL uses the nonstandard ` (backtick) for quoting identifiers.
def quoted_identifier(c)
"`#{c}`"
View
7 lib/sequel/dataset/features.rb
@@ -5,6 +5,13 @@ def quote_identifiers?
@quote_identifiers
end
+ # Whether this dataset will provide accurate number of rows matched for
+ # delete and update statements. Accurate in this case is the number of
+ # rows matched by the dataset's filter.
+ def provides_accurate_rows_matched?
+ true
+ end
+
# Whether the dataset requires SQL standard datetimes (false by default,
# as most allow strings with ISO 8601 format.
def requires_sql_standard_datetimes?
View
2  lib/sequel/model.rb
@@ -101,7 +101,7 @@ class Model
@primary_key = :id
@raise_on_save_failure = true
@raise_on_typecast_failure = true
- @require_modification = true
+ @require_modification = nil
@restrict_primary_key = true
@restricted_columns = nil
@simple_pk = nil
View
1  lib/sequel/model/base.rb
@@ -287,6 +287,7 @@ def set_dataset(ds, opts={})
raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
end
@dataset.row_proc = Proc.new{|r| load(r)}
+ @require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
if inherited
@simple_table = superclass.simple_table
@columns = @dataset.columns rescue nil
View
10 lib/sequel/plugins/instance_filters.rb
@@ -33,12 +33,18 @@ module Plugins
# # delete now works.
# i1.delete
#
- # You must have require_modification on the class and instances
- # that use this plugin.
+ # This plugin sets the require_modification flag on the model,
+ # so if the model's dataset doesn't provide an accurate number
+ # of matched rows, this could result in invalid exceptions being raised.
module InstanceFilters
# Exception class raised when updating or deleting an object does
# not affect exactly one row.
Error = Sequel::NoExistingObject
+
+ # Set the require_modification flag to true for the model.
+ def self.configure(model)
+ model.require_modification = true
+ end
module InstanceMethods
# Clear the instance filters after successfully destroying the object.
View
2  lib/sequel/plugins/optimistic_locking.rb
@@ -19,8 +19,6 @@ module Plugins
# class level accessor) that defaults to 0.
#
# This plugin relies on the instance_filters plugin.
- # You must have require_modification on the class and instances
- # that use this plugin.
module OptimisticLocking
# Exception class raised when trying to update or destroy a stale object.
Error = InstanceFilters::Error
View
9 spec/integration/dataset_test.rb
@@ -38,11 +38,18 @@
@ds.filter(1=>1).delete.should == 1
@ds.count.should == 0
end
-
+
specify "should update correctly" do
@ds.update(:number=>:number+1).should == 1
@ds.all.should == [{:id=>1, :number=>11}]
end
+
+ cspecify "should have update return the number of matched rows", [:mysql, :mysql], [:do, :mysql], [:ado] do
+ @ds.update(:number=>:number).should == 1
+ @ds.filter(:id=>1).update(:number=>:number).should == 1
+ @ds.filter(:id=>2).update(:number=>:number).should == 0
+ @ds.all.should == [{:id=>1, :number=>10}]
+ end
specify "should fetch all results correctly" do
@ds.all.should == [{:id=>1, :number=>10}]
View
1  spec/integration/model_test.rb
@@ -78,6 +78,7 @@ class ::Item::Thing < Sequel::Model(@db[:items].select(:name))
specify "#save should check that the only a single row is modified, unless require_modification is false" do
i = Item.create(:name=>'a')
+ i.require_modification = true
i.delete
proc{i.save}.should raise_error(Sequel::NoExistingObject)
proc{i.delete}.should raise_error(Sequel::NoExistingObject)
View
27 spec/model/base_spec.rb
@@ -327,6 +327,33 @@ def refresh
end
end
+describe Sequel::Model, ".require_modification" do
+ before do
+ @ds1 = MODEL_DB[:items]
+ @ds1.meta_def(:provides_accurate_rows_matched?){false}
+ @ds2 = MODEL_DB[:items]
+ @ds2.meta_def(:provides_accurate_rows_matched?){true}
+ end
+ after do
+ Sequel::Model.require_modification = nil
+ end
+
+ it "should depend on whether the dataset provides an accurate number of rows matched by default" do
+ Class.new(Sequel::Model(@ds1)).require_modification.should == false
+ Class.new(Sequel::Model(@ds2)).require_modification.should == true
+ end
+
+ it "should obey global setting regardless of dataset support if set" do
+ Sequel::Model.require_modification = true
+ Class.new(Sequel::Model(@ds1)).require_modification.should == true
+ Class.new(Sequel::Model(@ds2)).require_modification.should == true
+
+ Sequel::Model.require_modification = false
+ Class.new(Sequel::Model(@ds1)).require_modification.should == false
+ Class.new(Sequel::Model(@ds2)).require_modification.should == false
+ end
+end
+
describe Sequel::Model, ".[] optimization" do
before do
@c = Class.new(Sequel::Model(:a))
Please sign in to comment.
Something went wrong with that request. Please try again.