Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent command out of sync errors with Prepared Statements #958

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions ext/mysql2/statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,38 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
}
}

// Duplicate the options hash, merge! extra opts, put the copy into the Result object
current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
(void)RB_GC_GUARD(current);
Check_Type(current, T_HASH);

// Merge in hash opts/keyword arguments
if (!NIL_P(opts)) {
rb_funcall(current, intern_merge_bang, 1, opts);
}

is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));

// From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
// Ruby API calls are allowed so that GC is not invoked. If the connection is
// in results-streaming-mode for Statement A, and in the middle Statement B
// gets garbage collected, a message will be sent to the server notifying it
// to release Statement B, resulting in the following error:
// Commands out of sync; you can't run this command now
//
// In streaming mode, statement execute must return a cursor because we
// cannot prevent other Statement objects from being garbage collected
// between fetches of each row of the result set. The following error
// occurs if cursor mode is not set:
// Row retrieval was canceled by mysql_stmt_close

if (is_streaming) {
unsigned long type = CURSOR_TYPE_READ_ONLY;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
}
}

if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
FREE_BINDS;
rb_raise_mysql2_stmt_error(stmt_wrapper);
Expand All @@ -421,17 +453,6 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
return Qnil;
}

// Duplicate the options hash, merge! extra opts, put the copy into the Result object
current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
(void)RB_GC_GUARD(current);
Check_Type(current, T_HASH);

// Merge in hash opts/keyword arguments
if (!NIL_P(opts)) {
rb_funcall(current, intern_merge_bang, 1, opts);
}

is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
if (!is_streaming) {
// recieve the whole result set from the server
if (mysql_stmt_store_result(stmt)) {
Expand Down