123456789_123456789_123456789_123456789_123456789_

Class: Mysql2::Result

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
self, Enumerable
Inherits: Object
Defined in: lib/mysql2/result.rb,
ext/mysql2/result.c

Instance Method Summary

Instance Method Details

#count

Alias for #size.

#dbs

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 1683

static VALUE rb_mysql_result_fetch_dbs(VALUE self) {
  unsigned int i = 0;

  GET_RESULT(self);

  if (wrapper->dbs == Qnil) {
    wrapper->numberOfFields = mysql_num_fields(wrapper->result);
    wrapper->dbs = rb_ary_new2(wrapper->numberOfFields);
  }

  if ((my_ulonglong)RARRAY_LEN(wrapper->dbs) != wrapper->numberOfFields) {
    for (i=0; i<wrapper->numberOfFields; i++) {
      rb_mysql_result_fetch_db(self, i);
    }
  }

  return wrapper->dbs;
}

#each(*args)

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 1874

static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
  result_each_args args;
  VALUE scratch_holder = 0;
  VALUE rows;
  VALUE opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
  ID db_timezone, app_timezone;
  int symbolizeKeys, asArray, castBool, cacheRows;
  mysql2_cast_mode cast;
  int warnDbTimezone, perEachOpts;
  unsigned long rowsPerGvlYield;

  GET_RESULT(self);

  if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) {
    rb_raise(cMysql2Error, "Statement handle already closed");
  }

  // A block can be passed to this method, but since we don't call the block directly from C,
  // we don't need to capture it into a variable here with the "&" scan arg.
  perEachOpts = rb_scan_args(argc, argv, "01", &opts) == 1;

  /* :force_encoding is resolved when the query/execute command is issued
   * and is fixed for the life of the Result: non-streaming
   * Statement#execute materializes every row by calling #each itself, so
   * a value passed here could never be honored consistently. Reject it
   * outright rather than silently ignoring it. (The merged defaults
   * below legitimately carry the query-time value; only the per-#each
   * hash is checked.) */
  if (perEachOpts && RB_TYPE_P(opts, T_HASH) && rb_hash_lookup2(opts, sym_force_encoding, Qundef) != Qundef) {
    rb_raise(cMysql2Error, ":force_encoding is a query option and cannot be set on Result#each");
  }

  if (!perEachOpts && wrapper->each_opts.parsed) {
    /* Argument-less #each over the already-parsed @query_options: reuse the
     * parse. Warnings are deliberately not part of the cache -- their
     * conditions are recomputed from the cached (pre-forcing) values below,
     * so they fire on every call exactly as an uncached parse would. */
    symbolizeKeys   = wrapper->each_opts.symbolizeKeys;
    asArray         = wrapper->each_opts.asArray;
    castBool        = wrapper->each_opts.castBool;
    cacheRows       = wrapper->each_opts.cacheRows;
    cast            = wrapper->each_opts.cast;
    warnDbTimezone  = wrapper->each_opts.warnDbTimezone;
    rowsPerGvlYield = wrapper->each_opts.rowsPerGvlYield;
    db_timezone     = wrapper->each_opts.db_timezone;
    app_timezone    = wrapper->each_opts.app_timezone;
  } else {
    VALUE dbTz, appTz, rowsPerGvlYieldOpt, castOpt;
    VALUE defaults = rb_ivar_get(self, intern_query_options);
    Check_Type(defaults, T_HASH);

    opts = perEachOpts ? rb_funcall(defaults, intern_merge, 1, opts) : defaults;

    symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
    asArray       = rb_hash_aref(opts, sym_as) == sym_array;
    castBool      = RTEST(rb_hash_aref(opts, sym_cast_booleans));
    cacheRows     = RTEST(rb_hash_aref(opts, sym_cache_rows));

    /* See mysql2_cast_mode: only the exact symbol :fast selects partial
     * casting; any other truthy value stays full casting. */
    castOpt = rb_hash_aref(opts, sym_cast);
    if (castOpt == sym_fast) {
      cast = MYSQL2_CAST_FAST;
    } else if (RTEST(castOpt)) {
      cast = MYSQL2_CAST_ALL;
    } else {
      cast = MYSQL2_CAST_NONE;
    }

    /* :rows_per_gvl_yield -- 0 disables yielding; nil uses the default. */
    rowsPerGvlYield = MYSQL2_ROWS_PER_GVL_YIELD_DEFAULT;
    rowsPerGvlYieldOpt = rb_hash_aref(opts, sym_rows_per_gvl_yield);
    if (!NIL_P(rowsPerGvlYieldOpt)) {
      long requested = NUM2LONG(rowsPerGvlYieldOpt);
      if (requested < 0) {
        rb_raise(cMysql2Error, ":rows_per_gvl_yield must not be negative");
      }
      rowsPerGvlYield = (unsigned long)requested;
    }

    /* The timezone lookups are hoisted from their historical spot below the
     * freed-result guard so a complete parse exists to cache; the lookups
     * themselves are side-effect free, and the invalid-:database_timezone
     * warning is deferred (warnDbTimezone) to its historical point, after
     * that guard. */
    dbTz = rb_hash_aref(opts, sym_database_timezone);
    warnDbTimezone = 0;
    if (dbTz == sym_local) {
      db_timezone = intern_local;
    } else if (dbTz == sym_utc) {
      db_timezone = intern_utc;
    } else {
      warnDbTimezone = !NIL_P(dbTz);
      db_timezone = intern_local;
    }

    appTz = rb_hash_aref(opts, sym_application_timezone);
    if (appTz == sym_local) {
      app_timezone = intern_local;
    } else if (appTz == sym_utc) {
      app_timezone = intern_utc;
    } else {
      app_timezone = Qnil;
    }

    if (!perEachOpts) {
      /* Nothing above raised, so this parse of @query_options is complete
       * and can serve every later argument-less call. An invalid
       * :rows_per_gvl_yield raises before this point, leaving the cache
       * unset so the next call re-parses and re-raises just as an uncached
       * one would. */
      wrapper->each_opts.symbolizeKeys   = symbolizeKeys;
      wrapper->each_opts.asArray         = asArray;
      wrapper->each_opts.castBool        = castBool;
      wrapper->each_opts.cacheRows       = cacheRows;
      wrapper->each_opts.cast            = cast;
      wrapper->each_opts.warnDbTimezone  = warnDbTimezone;
      wrapper->each_opts.rowsPerGvlYield = rowsPerGvlYield;
      wrapper->each_opts.db_timezone     = db_timezone;
      wrapper->each_opts.app_timezone    = app_timezone;
      wrapper->each_opts.parsed          = 1;
    }
  }

  if (wrapper->is_streaming && cacheRows) {
    rb_warn(":cache_rows is ignored if :stream is true");
  }

  if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) {
    rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
    cacheRows = 1;
  }

  /* The binary-protocol row fetch (rb_mysql_result_fetch_row_stmt) always
   * fully casts; the existing warning already reads as "any non-true :cast
   * is overridden here", so cast: :fast reuses it verbatim. */
  if (wrapper->stmt_wrapper && cast != MYSQL2_CAST_ALL) {
    rb_warn(":cast is forced for prepared statements");
  }

  /* A freed result can only be re-iterated from the fully cached rows array
   * (or raise the streaming-specific error below when a completed stream is
   * re-iterated); anything else would dereference the freed MYSQL_RES. The
   * rows-length check matters: with cache_rows: false the rows array stays
   * empty even after a full iteration, and replaying it would yield nil
   * rows. */
  if (wrapper->resultFreed) {
    int replayable = cacheRows && wrapper->rows != Qnil &&
                     wrapper->lastRowProcessed == wrapper->numberOfRows &&
                     (my_ulonglong)RARRAY_LEN(wrapper->rows) == wrapper->numberOfRows;
    if (wrapper->is_streaming ? !wrapper->streamingComplete : !replayable) {
      rb_raise(cMysql2Error, "Result set has already been freed");
    }
  }

  if (warnDbTimezone) {
    rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
  }

  if (wrapper->rows == Qnil && !wrapper->is_streaming) {
    wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
    /* Only reserve room for every row when the rows will actually be kept.
     * With cache_rows: false nothing is ever stored in this array, so the
     * reservation is dead weight proportional to the result size. */
    wrapper->rows = cacheRows ? rb_ary_new2(wrapper->numberOfRows) : rb_ary_new();
  } else if (wrapper->rows && !cacheRows) {
    if (wrapper->resultFreed) {
      rb_raise(cMysql2Error, "Result set has already been freed");
    }
    mysql_data_seek(wrapper->result, 0);
    wrapper->lastRowProcessed = 0;
    wrapper->rows = rb_ary_new();
  }

  // Backward compat
  args.symbolizeKeys = symbolizeKeys;
  args.asArray = asArray;
  args.castBool = castBool;
  args.cacheRows = cacheRows;
  args.rowsPerGvlYield = rowsPerGvlYield;
  args.cast = cast;
  args.db_timezone = db_timezone;
  args.app_timezone = app_timezone;
  args.block_given = rb_block_given_p();
  /* Captured once per #each call; see the field's comment in
   * result_each_args. */
  args.default_internal_enc = rb_default_internal_encoding();

  /* See the field's comment in result_each_args. A freed result only
   * replays cached rows (or raises), never fetches, so wrapper->result is
   * valid whenever the scratch is allocated -- and the fetch functions only
   * touch the scratch after their own freed-result guards pass. A raise or
   * break during iteration skips the ALLOCV_END below and leaks the
   * heap-allocated form of the buffer until GC reclaims scratch_holder;
   * that is the standard ALLOCV trade, bounded to one buffer per raised
   * iteration because the allocation is per-#each, not per-row. */
  args.rowScratch = NULL;
  if (asArray && !wrapper->resultFreed) {
    args.rowScratch = ALLOCV_N(VALUE, scratch_holder, mysql_num_fields(wrapper->result));
  }

  if (wrapper->stmt_wrapper) {
    fetch_row_func = rb_mysql_result_fetch_row_stmt;
  } else {
    fetch_row_func = rb_mysql_result_fetch_row;
  }

  rows = rb_mysql_result_each_(self, fetch_row_func, &args);
  ALLOCV_END(scratch_holder);

  return rows;
}

#field_types

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 1702

static VALUE rb_mysql_result_fetch_field_types(VALUE self) {
  unsigned int i = 0;
  VALUE field_types;

  GET_RESULT(self);

  if (wrapper->fieldTypes == Qnil) {
    if (wrapper->resultFreed) {
      rb_raise(cMysql2Error, "Result set has already been freed");
    }
    wrapper->numberOfFields = mysql_num_fields(wrapper->result);
    wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
  }

  /* wrapper->fieldTypes lives on the C struct, not the Ruby stack: between
   * this assignment and the loop below finishing, it's reachable only
   * through wrapper, and each iteration allocates a String (a GC
   * safepoint). Keep a stack-local reference alive across the whole loop
   * so conservative stack scanning always finds it too, independent of
   * when the next mark pass would otherwise notice it via wrapper -- under
   * GC.stress a mark pass can land in that gap. See #1456. */
  field_types = wrapper->fieldTypes;

  if ((my_ulonglong)RARRAY_LEN(field_types) != wrapper->numberOfFields) {
    for (i=0; i<wrapper->numberOfFields; i++) {
      rb_mysql_result_fetch_field_type(self, i);
    }
  }

  RB_GC_GUARD(field_types);
  return wrapper->fieldTypes;
}

#fields

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 1627

static VALUE rb_mysql_result_fetch_fields(VALUE self) {
  unsigned int i = 0;
  short int symbolizeKeys = 0;
  VALUE defaults;
  VALUE fields;

  GET_RESULT(self);

  defaults = rb_ivar_get(self, intern_query_options);
  Check_Type(defaults, T_HASH);
  if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
    symbolizeKeys = 1;
  }

  if (wrapper->fields == Qnil) {
    if (wrapper->resultFreed) {
      rb_raise(cMysql2Error, "Result set has already been freed");
    }
    wrapper->numberOfFields = mysql_num_fields(wrapper->result);
    wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
  }

  /* See the identical guard in rb_mysql_result_fetch_field_types: keep a
   * stack-local reference alive across the fill loop so conservative stack
   * scanning finds this array too, independent of GC generation timing. */
  fields = wrapper->fields;

  if ((my_ulonglong)RARRAY_LEN(fields) != wrapper->numberOfFields) {
    for (i=0; i<wrapper->numberOfFields; i++) {
      rb_mysql_result_fetch_field(self, i, symbolizeKeys);
    }
  }

  RB_GC_GUARD(fields);
  return wrapper->fields;
}

#free

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 1752

static VALUE rb_mysql_result_free_(VALUE self) {
  rb_mysql_result_cache_metadata_and_free(self);
  return Qnil;
}

#server_flags

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 2099

static VALUE rb_mysql_result_server_flags(VALUE self) {
  GET_RESULT(self);

  if (NIL_P(wrapper->server_flags)) {
    VALUE server_flags = rb_hash_new();

#ifdef HAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED
    rb_hash_aset(server_flags, sym_no_good_index_used, flag_to_bool(SERVER_QUERY_NO_GOOD_INDEX_USED));
#else
    rb_hash_aset(server_flags, sym_no_good_index_used, Qnil);
#endif

#ifdef HAVE_CONST_SERVER_QUERY_NO_INDEX_USED
    rb_hash_aset(server_flags, sym_no_index_used, flag_to_bool(SERVER_QUERY_NO_INDEX_USED));
#else
    rb_hash_aset(server_flags, sym_no_index_used, Qnil);
#endif

#ifdef HAVE_CONST_SERVER_QUERY_WAS_SLOW
    rb_hash_aset(server_flags, sym_query_was_slow, flag_to_bool(SERVER_QUERY_WAS_SLOW));
#else
    rb_hash_aset(server_flags, sym_query_was_slow, Qnil);
#endif

    /* Memoize in the wrapper struct, marked from rb_mysql_result_mark: a
     * plain C field write, so it works even on a frozen Result (an ivar set
     * would raise FrozenError), and later calls return this same Hash object
     * (mutations included), as the eager version did. */
    wrapper->server_flags = server_flags;
  }

  return wrapper->server_flags;
}

#size Also known as: #count

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 2134

static VALUE rb_mysql_result_count(VALUE self) {
  GET_RESULT(self);

  if (wrapper->is_streaming) {
    /* This is an unsigned long per result.h */
    return ULONG2NUM(wrapper->numberOfRows);
  }

  if (wrapper->resultFreed) {
    /* Ruby arrays have platform signed long length */
    return LONG2NUM(RARRAY_LEN(wrapper->rows));
  } else {
    /* MySQL returns an unsigned 64-bit long here */
    if (wrapper->stmt_wrapper) {
      return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt));
    } else {
      return ULL2NUM(mysql_num_rows(wrapper->result));
    }
  }
}

#tables

[ GitHub ]

  
# File 'ext/mysql2/result.c', line 1664

static VALUE rb_mysql_result_fetch_tables(VALUE self) {
  unsigned int i = 0;

  GET_RESULT(self);

  if (wrapper->tables == Qnil) {
    wrapper->numberOfFields = mysql_num_fields(wrapper->result);
    wrapper->tables = rb_ary_new2(wrapper->numberOfFields);
  }

  if ((my_ulonglong)RARRAY_LEN(wrapper->tables) != wrapper->numberOfFields) {
    for (i=0; i<wrapper->numberOfFields; i++) {
      rb_mysql_result_fetch_table(self, i);
    }
  }

  return wrapper->tables;
}