Skip to content

Commit b7d5d6c

Browse files
committed
Feature request #130: add element reference operator #[] to Mysql2::Result using mysql_data_seek.
1 parent 13c77db commit b7d5d6c

File tree

2 files changed

+94
-39
lines changed

2 files changed

+94
-39
lines changed

ext/mysql2/result.c

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,66 @@ static void rb_mysql_row_query_options(VALUE opts, ID *db_timezone, ID *app_time
789789
}
790790
}
791791

792+
static VALUE rb_mysql_result_element(VALUE self, VALUE seek) {
793+
result_each_args args;
794+
MYSQL_FIELD *fields = NULL;
795+
long offset;
796+
ID db_timezone, app_timezone;
797+
int symbolizeKeys, asArray, castBool, cacheRows, cast;
798+
VALUE opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
799+
800+
GET_RESULT(self);
801+
802+
offset = NUM2LONG(seek);
803+
804+
if (!wrapper->numberOfRows) {
805+
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
806+
}
807+
808+
opts = rb_iv_get(self, "@query_options");
809+
rb_mysql_row_query_options(opts, &db_timezone, &app_timezone, &symbolizeKeys, &asArray, &castBool, &cast, &cacheRows);
810+
811+
if (wrapper->is_streaming) {
812+
rb_raise(cMysql2Error, "Element reference operator #[] cannot be used in streaming mode.");
813+
}
814+
815+
/* count back from the end if passed a negative number */
816+
if (offset < 0) {
817+
offset = wrapper->numberOfRows + offset;
818+
}
819+
820+
/* negative offset was too big */
821+
if (offset < 0) {
822+
return Qnil;
823+
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", offset, wrapper->numberOfRows); */
824+
}
825+
826+
if (wrapper->numberOfRows <= (unsigned long)offset) {
827+
return Qnil;
828+
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", offset, wrapper->numberOfRows); */
829+
}
830+
831+
mysql_data_seek(wrapper->result, offset);
832+
833+
// Backward compat
834+
args.symbolizeKeys = symbolizeKeys;
835+
args.asArray = asArray;
836+
args.castBool = castBool;
837+
args.cacheRows = cacheRows;
838+
args.cast = cast;
839+
args.db_timezone = db_timezone;
840+
args.app_timezone = app_timezone;
841+
args.block_given = Qnil;
842+
843+
if (wrapper->stmt) {
844+
fetch_row_func = rb_mysql_result_fetch_row_stmt;
845+
} else {
846+
fetch_row_func = rb_mysql_result_fetch_row;
847+
}
848+
849+
return fetch_row_func(self, fields, &args);
850+
}
851+
792852
static VALUE rb_mysql_result_each_(VALUE self,
793853
VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
794854
const result_each_args *args)
@@ -878,7 +938,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
878938
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
879939
result_each_args args;
880940
VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
881-
ID db_timezone, app_timezone, dbTz, appTz;
941+
ID db_timezone, app_timezone;
882942
int symbolizeKeys, asArray, castBool, cacheRows, cast;
883943

884944
GET_RESULT(self);
@@ -891,44 +951,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
891951
opts = defaults;
892952
}
893953

894-
symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
895-
asArray = rb_hash_aref(opts, sym_as) == sym_array;
896-
castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
897-
cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
898-
cast = RTEST(rb_hash_aref(opts, sym_cast));
899-
900-
if (wrapper->is_streaming && cacheRows) {
901-
rb_warn(":cache_rows is ignored if :stream is true");
902-
}
903-
904-
if (wrapper->stmt && !cacheRows && !wrapper->is_streaming) {
905-
rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
906-
}
907-
908-
if (wrapper->stmt && !cast) {
909-
rb_warn(":cast is forced for prepared statements");
910-
}
911-
912-
dbTz = rb_hash_aref(opts, sym_database_timezone);
913-
if (dbTz == sym_local) {
914-
db_timezone = intern_local;
915-
} else if (dbTz == sym_utc) {
916-
db_timezone = intern_utc;
917-
} else {
918-
if (!NIL_P(dbTz)) {
919-
rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
920-
}
921-
db_timezone = intern_local;
922-
}
923-
924-
appTz = rb_hash_aref(opts, sym_application_timezone);
925-
if (appTz == sym_local) {
926-
app_timezone = intern_local;
927-
} else if (appTz == sym_utc) {
928-
app_timezone = intern_utc;
929-
} else {
930-
app_timezone = Qnil;
931-
}
954+
rb_mysql_row_query_options(opts, &db_timezone, &app_timezone, &symbolizeKeys, &asArray, &castBool, &cast, &cacheRows);
932955

933956
if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) {
934957
wrapper->numberOfRows = wrapper->stmt ? mysql_stmt_num_rows(wrapper->stmt) : mysql_num_rows(wrapper->result);
@@ -1019,6 +1042,7 @@ void init_mysql2_result() {
10191042
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
10201043

10211044
cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
1045+
rb_define_method(cMysql2Result, "[]", rb_mysql_result_element, 1);
10221046
rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
10231047
rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
10241048
rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);

spec/mysql2/result_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,37 @@
9898
end
9999
end
100100

101+
context "#[]" do
102+
it "should return results when accessed by [offset]" do
103+
result = @client.query "SELECT 1 UNION SELECT 2"
104+
result[1][result.fields.first].should eql(2)
105+
result[0][result.fields.first].should eql(1)
106+
end
107+
108+
it "should return results when accessed by negative [offset]" do
109+
result = @client.query "SELECT 1 UNION SELECT 2"
110+
result[-1][result.fields.first].should eql(2)
111+
result[-2][result.fields.first].should eql(1)
112+
end
113+
114+
it "should return nil if we use too large [offset]" do
115+
result = @client.query "SELECT 1 UNION SELECT 2"
116+
result[2].should be_nil
117+
result[200].should be_nil
118+
end
119+
120+
it "should return nil if we use too negative [offset]" do
121+
result = @client.query "SELECT 1 UNION SELECT 2"
122+
result[-3].should be_nil
123+
result[-300].should be_nil
124+
end
125+
126+
it "should throw an exception if we use an [offset] in streaming mode" do
127+
result = @client.query "SELECT 1 UNION SELECT 2", :stream => true
128+
expect { result[0] }.to raise_exception(Mysql2::Error)
129+
end
130+
end
131+
101132
context "#fields" do
102133
before(:each) do
103134
@test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1")

0 commit comments

Comments
 (0)