Skip to content

Commit

Permalink
Handle usage of on_duplicate_key_update in MySQL prepared statements (F…
Browse files Browse the repository at this point in the history
…ixes jeremyevans#404)

This fixes a general class of bug where columns was called by
the dataset literalization code when using prepared statements.
The columns call would call the prepared statement literalization
to recurse, usually leading to a SystemStackError or a
NoMemoryError.

The fix is fairly simple.  Prepared statements now have a link to
the dataset that created them, and calling columns on a prepared
statement is now delegated to the dataset that prepared it.
  • Loading branch information
jeremyevans committed Dec 1, 2011
1 parent ffbb638 commit e6ae6a2
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 0 deletions.
10 changes: 10 additions & 0 deletions lib/sequel/dataset/prepared_statements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ module PreparedStatementMethods
# The array/hash of bound variable placeholder names.
attr_accessor :prepared_args

# The dataset that created this prepared statement.
attr_accessor :orig_dataset

# The argument to supply to insert and update, which may use
# placeholders specified by prepared_args
attr_accessor :prepared_modify_values
Expand All @@ -72,6 +75,12 @@ module PreparedStatementMethods
def call(bind_vars={}, &block)
bind(bind_vars).run(&block)
end

# Send the columns to the original dataset, as calling it
# on the prepared statement can cause problems.
def columns
orig_dataset.columns
end

# Returns the SQL for the prepared statement, depending on
# the type of the statement and the prepared_modify_values.
Expand Down Expand Up @@ -244,6 +253,7 @@ def prepare(type, name=nil, *values)
def to_prepared_statement(type, values=nil)
ps = bind
ps.extend(PreparedStatementMethods)
ps.orig_dataset = self
ps.prepared_type = type
ps.prepared_modify_values = values
ps
Expand Down
10 changes: 10 additions & 0 deletions spec/adapters/mysql_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,16 @@ def logger.method_missing(m, msg)

@d.first[:name].should == ':\\'
end

specify "should handle prepared statements with on_duplicate_key_update" do
@d.db.add_index :items, :value, :unique=>true
ds = @d.on_duplicate_key_update
ps = ds.prepare(:insert, :insert_user_id_feature_name, :value => :$v, :name => :$n)
ps.call(:v => 1, :n => 'a')
ds.all.should == [{:value=>1, :name=>'a'}]
ps.call(:v => 1, :n => 'b')
ds.all.should == [{:value=>1, :name=>'b'}]
end
end

describe "MySQL datasets" do
Expand Down
7 changes: 7 additions & 0 deletions spec/core/dataset_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3066,6 +3066,13 @@ class ::InspectDataset < Sequel::Dataset; end
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
end

specify "should handle columns on prepared statements correctly" do
@db.columns = [:num]
@ds.meta_def(:select_where_sql){|sql| super(sql); sql << " OR #{columns.first} = 1" if opts[:where]}
@ds.filter(:num=>:$n).prepare(:select, :sn).sql.should == 'SELECT * FROM items WHERE (num = $n) OR num = 1'
@db.sqls.should == ['SELECT * FROM items LIMIT 1']
end

specify "should handle datasets using static sql and placeholders" do
@db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
Expand Down

0 comments on commit e6ae6a2

Please sign in to comment.