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
-
#count
Alias for #size.
- #dbs
- #each(*args)
- #field_types
- #fields
- #free
- #server_flags
- #size (also: #count)
- #tables
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;
}