123456789_123456789_123456789_123456789_123456789_

Class: Mysql2::Statement

Relationships & Source Files
Inherits: Object
Defined in: lib/mysql2/statement.rb,
ext/mysql2/statement.c

Instance Attribute Summary

Instance Method Summary

Instance Attribute Details

#closed?Boolean (readonly)

Returns wheter or not the statement have been closed.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 764

static VALUE rb_mysql_stmt_closed_p(VALUE self) {
  RAW_GET_STATEMENT(self);

  return stmt_wrapper->closed ? Qtrue : Qfalse;
}

Instance Method Details

#execute

Executes the current prepared statement, returns result.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 338

static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
  MYSQL_BIND *bind_buffers = NULL;
  unsigned long *length_buffers = NULL;
  unsigned long bind_count;
  unsigned long i;
  MYSQL_STMT *stmt;
  MYSQL_RES *metadata;
  VALUE opts;
  VALUE current;
  VALUE resultObj;
  VALUE *params_enc = NULL;
  int is_streaming;
  rb_encoding *conn_enc;

  GET_STATEMENT(self);
  GET_CLIENT(stmt_wrapper->client);

  /* We're about to issue a new command on this connection: a safe point to
   * close out any statements that were GC'd while it was last busy, and to
   * free any abandoned result sets left over from a stream that was
   * dropped mid-iteration -- including on this very statement handle, if
   * it was previously executed as a stream and abandoned before being
   * fully drained. That must happen before we touch stmt below again.
   * mysql2_abandon_active_stream handles the still-live case (not yet
   * collected by GC), which the reap below alone wouldn't catch. */
  mysql2_abandon_active_stream(wrapper);
  mysql2_reap_pending_result_frees(wrapper);
  mysql2_reap_pending_stmt_closes(wrapper);

  conn_enc = rb_to_encoding(wrapper->encoding);

  stmt = stmt_wrapper->stmt;
  bind_count = mysql_stmt_param_count(stmt);

  // Get count of ordinary arguments, and extract hash opts/keyword arguments
  // Use a local scope to avoid leaking the temporary count variable
  {
    int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
    if (c != (long)bind_count) {
      rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
    }
  }

  // Duplicate the options hash, merge! extra opts, put the copy into the
  // Result object. Deliberately ahead of the bind buffer setup below:
  // canonicalizing :force_encoding raises for invalid values, and raising
  // here -- before any bind buffers are xmalloc'd and before anything is
  // written to the wire -- leaks nothing and leaves the connection
  // untouched and reusable. Nothing downstream of the execute may raise
  // for this option; rb_mysql_result_to_obj in particular must not.
  current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_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);
  }

  mysql2_canonicalize_force_encoding(current);

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

  // setup any bind variables in the query
  if (bind_count > 0) {
    // Scratch space for string encoding exports, allocate on the stack
    params_enc = alloca(sizeof(VALUE) * bind_count);
    bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
    length_buffers = xcalloc(bind_count, sizeof(unsigned long));

    for (i = 0; i < bind_count; i++) {
      bind_buffers[i].buffer = NULL;
      params_enc[i] = Qnil;

      switch (TYPE(argv[i])) {
        case T_NIL:
          bind_buffers[i].buffer_type = MYSQL_TYPE_NULL;
          break;
        case T_FIXNUM:
#if SIZEOF_INT < SIZEOF_LONG
          bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
          bind_buffers[i].buffer = xmalloc(sizeof(long long int));
          *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]);
#else
          bind_buffers[i].buffer_type = MYSQL_TYPE_LONG;
          bind_buffers[i].buffer = xmalloc(sizeof(int));
          *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]);
#endif
          break;
        case T_BIGNUM:
          {
            LONG_LONG num;
            if (my_big2ll(argv[i], &num) == 0) {
              bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
              bind_buffers[i].buffer = xmalloc(sizeof(long long int));
              *(LONG_LONG*)(bind_buffers[i].buffer) = num;
            } else {
              /* The bignum was larger than we can fit in LONG_LONG, send it as a string */
              bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
              params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
              set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
            }
          }
          break;
        case T_FLOAT:
          bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
          bind_buffers[i].buffer = xmalloc(sizeof(double));
          *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
          break;
        case T_STRING:
          bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;

          params_enc[i] = argv[i];
          params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
          set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
          break;
        case T_TRUE:
          bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
          bind_buffers[i].buffer = xmalloc(sizeof(signed char));
          *(signed char*)(bind_buffers[i].buffer) = 1;
          break;
        case T_FALSE:
          bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
          bind_buffers[i].buffer = xmalloc(sizeof(signed char));
          *(signed char*)(bind_buffers[i].buffer) = 0;
          break;
        default:
          // TODO: what Ruby type should support MYSQL_TYPE_TIME
          if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
            MYSQL_TIME t;
            VALUE rb_time = argv[i];

            bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME;
            bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));

            memset(&t, 0, sizeof(MYSQL_TIME));
            t.neg = 0;

            if (CLASS_OF(argv[i]) == rb_cTime) {
              t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
            } else if (CLASS_OF(argv[i]) == cDateTime) {
              // sec_fraction is an exact Rational; keep the multiply in
              // Rational-space and only truncate to an integer at the end,
              // so no floating-point error can creep into the microseconds.
              VALUE usec = rb_funcall(rb_funcall(rb_time, intern_sec_fraction, 0), intern_mul, 1, INT2FIX(1000000));
              t.second_part = NUM2ULONG(rb_funcall(usec, intern_truncate, 0));
            }

            t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
            t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
            t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
            t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
            t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
            t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));

            *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
          } else if (CLASS_OF(argv[i]) == cDate) {
            MYSQL_TIME t;
            VALUE rb_time = argv[i];

            bind_buffers[i].buffer_type = MYSQL_TYPE_DATE;
            bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));

            memset(&t, 0, sizeof(MYSQL_TIME));
            t.second_part = 0;
            t.neg = 0;
            t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
            t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
            t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));

            *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
          } else if (CLASS_OF(argv[i]) == cBigDecimal) {
            bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;

            // DECIMAL are represented with the "string representation of the
            // original server-side value", see
            // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
            // This should be independent of the locale used both on the server
            // and the client side.
            VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);

            params_enc[i] = rb_val_as_string;
            params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
            set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
          } else {
            int state = 0;
            VALUE inspect = rb_protect(rb_inspect, argv[i], &state);
            if (!state && rb_str_strlen(inspect) > 40) {
              inspect = rb_str_substr(inspect, 0, 40);
              rb_str_cat2(inspect, "...");
            }
            /* An inspect that raised, or inspect output holding a NUL byte
             * (which rb_raise's PRIsVALUE formatting rejects), still gets the
             * TypeError -- just without the value. */
            if (state || memchr(RSTRING_PTR(inspect), '\0', RSTRING_LEN(inspect))) {
              rb_raise(rb_eTypeError, "can't bind parameter %lu: no conversion for %s",
                       i + 1, rb_obj_classname(argv[i]));
            }
            rb_raise(rb_eTypeError, "can't bind parameter %lu: no conversion for %s (%" PRIsVALUE ")",
                     i + 1, rb_obj_classname(argv[i]), inspect);
          }
          break;
      }
    }

    // copies bind_buffers into internal storage
    if (mysql_stmt_bind_param(stmt, bind_buffers)) {
      FREE_BINDS;
      rb_raise_mysql2_stmt_error(stmt_wrapper);
    }
  }

  // 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)) {
      FREE_BINDS;
      rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
    }
  }

  // Abandon/reap once more, right before the actual network write below: the
  // bind setup above (rb_str_export_to_enc, rb_funcall for Time/DateTime/
  // BigDecimal conversions, rb_hash_dup) can itself allocate, and under
  // GC.stress (or just unlucky timing) that can be what triggers the GC
  // sweep that abandons a *different* stream on this connection -- the
  // earlier checks up top can't see that yet, and leaving it undrained here
  // would desync the protocol once this command's bytes hit the wire ahead
  // of the old stream's rows.
  mysql2_abandon_active_stream(wrapper);
  mysql2_reap_pending_result_frees(wrapper);
  mysql2_reap_pending_stmt_closes(wrapper);

  // From here through mysql_stmt_result_metadata/mysql_stmt_store_result,
  // the connection is BUSY: a Statement freed elsewhere during this window
  // must not be allowed to write COM_STMT_CLOSE to this socket. See
  // mysql2_enqueue_pending_stmt_close / mysql2_reap_pending_stmt_closes.
  wrapper->state = MYSQL2_CLIENT_QUERYING;

  if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
    FREE_BINDS;
    wrapper->state = MYSQL2_CLIENT_IDLE;
    rb_raise_mysql2_stmt_error(stmt_wrapper);
  }

  FREE_BINDS;

  metadata = mysql_stmt_result_metadata(stmt);
  if (metadata == NULL) {
    wrapper->state = MYSQL2_CLIENT_IDLE;
    if (mysql_stmt_errno(stmt) != 0) {
      // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
      wrapper->active_fiber = Qnil;
      rb_raise_mysql2_stmt_error(stmt_wrapper);
    }
    // no data and no error, so query was not a SELECT
    mysql2_reap_pending_result_frees(wrapper);
    mysql2_reap_pending_stmt_closes(wrapper);
    return Qnil;
  }

  if (!is_streaming) {
    // receive the whole result set from the server
    if (mysql_stmt_store_result(stmt)) {
      mysql_free_result(metadata);
      wrapper->state = MYSQL2_CLIENT_IDLE;
      rb_raise_mysql2_stmt_error(stmt_wrapper);
    }
    wrapper->active_fiber = Qnil;
    // The whole result set is buffered locally; free to reap and to run
    // another command right away.
    wrapper->state = MYSQL2_CLIENT_IDLE;
    mysql2_reap_pending_result_frees(wrapper);
    mysql2_reap_pending_stmt_closes(wrapper);
  } else {
    // A cursor is now open on the server; leave the connection BUSY until
    // the Result finishes (or abandons) streaming rows -- see result.c.
    wrapper->state = MYSQL2_CLIENT_STREAMING;
  }

  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);

  /* Track the open cursor so a later command can force-drain it if it's
   * abandoned instead of exhausted -- see mysql2_abandon_active_stream. */
  if (is_streaming) {
    wrapper->active_streaming_result = resultObj;
  }

  if (!is_streaming) {
    // cache all result
    rb_funcall(resultObj, intern_each, 0);
  }

  return resultObj;
}

#affected_rows

Returns the number of rows changed, deleted, or inserted.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 714

static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
  my_ulonglong affected;
  GET_STATEMENT(self);

  affected = mysql_stmt_affected_rows(stmt_wrapper->stmt);
  if (affected == (my_ulonglong)-1) {
    rb_raise_mysql2_stmt_error(stmt_wrapper);
  }

  return ULL2NUM(affected);
}

#close

Explicitly closing this will free up server resources immediately rather than waiting for the garbage collector. Useful if you're managing your own prepared statement cache.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 733

static VALUE rb_mysql_stmt_close(VALUE self) {
  RAW_GET_STATEMENT(self);

  if (!stmt_wrapper->closed) {
      GET_CLIENT(stmt_wrapper->client);

      /* Ordinary Ruby-level call, not GC/dfree: a safe point to also close
       * out any other statements that were GC'd while the connection was
       * last busy. Abandon/reap pending result frees first: this statement
       * itself may have an abandoned streaming result on it -- still live
       * (mysql2_abandon_active_stream) or already queued
       * (mysql2_reap_pending_result_frees) -- and that must be drained
       * before nogvl_stmt_close below frees the same MYSQL_STMT* out from
       * under it. */
      mysql2_abandon_active_stream(wrapper);
      mysql2_reap_pending_result_frees(wrapper);
      mysql2_reap_pending_stmt_closes(wrapper);

      stmt_wrapper->closed = 1;
      rb_hash_delete(wrapper->prepared_statements, ULL2NUM((unsigned long long)stmt_wrapper));
      rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
  }

  return Qnil;
}

#execute(*args, **kwargs)

[ GitHub ]

  
# File 'lib/mysql2/statement.rb', line 3

def execute(*args, **kwargs)
  Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do
    _execute(*args, **kwargs)
  end
end

#field_count(#) ⇒ Numeric

Returns the number of fields the prepared statement returns.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 247

static VALUE rb_mysql_stmt_field_count(VALUE self) {
  GET_STATEMENT(self);

  return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
}

#fields(#) ⇒ Array

Returns a list of fields that will be returned by this statement.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 650

static VALUE rb_mysql_stmt_fields(VALUE self) {
  MYSQL_FIELD *fields;
  MYSQL_RES *metadata;
  unsigned int field_count;
  unsigned int i;
  VALUE field_list;
  MYSQL_STMT* stmt;
  rb_encoding *default_internal_enc, *conn_enc;
  GET_STATEMENT(self);
  GET_CLIENT(stmt_wrapper->client);
  stmt = stmt_wrapper->stmt;

  default_internal_enc = rb_default_internal_encoding();
  {
    GET_CLIENT(stmt_wrapper->client);
    conn_enc = rb_to_encoding(wrapper->encoding);
  }

  metadata = mysql_stmt_result_metadata(stmt);
  if (metadata == NULL) {
    if (mysql_stmt_errno(stmt) != 0) {
      // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
      wrapper->active_fiber = Qnil;
      rb_raise_mysql2_stmt_error(stmt_wrapper);
    }
    // no data and no error, so query was not a SELECT
    return Qnil;
  }

  fields      = mysql_fetch_fields(metadata);
  field_count = mysql_stmt_field_count(stmt);
  field_list  = rb_ary_new2((long)field_count);

  for (i = 0; i < field_count; i++) {
    VALUE rb_field;

    rb_field = rb_str_new(fields[i].name, fields[i].name_length);
    rb_enc_associate(rb_field, conn_enc);
    if (default_internal_enc) {
     rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
   }

    rb_ary_store(field_list, (long)i, rb_field);
  }

  mysql_free_result(metadata);
  return field_list;
}

#last_id

Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 704

static VALUE rb_mysql_stmt_last_id(VALUE self) {
  GET_STATEMENT(self);
  return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt));
}

#param_count(#) ⇒ Numeric

Returns the number of parameters the prepared statement expects.

[ GitHub ]

  
# File 'ext/mysql2/statement.c', line 237

static VALUE rb_mysql_stmt_param_count(VALUE self) {
  GET_STATEMENT(self);

  return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
}