Skip to content

Commit 887ac01

Browse files
committed
Feature request #130: add element reference operator #[] to Mysql2::Result using mysql_data_seek.
1 parent 81fd9bc commit 887ac01

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
@@ -786,6 +786,66 @@ static void rb_mysql_row_query_options(VALUE opts, ID *db_timezone, ID *app_time
786786
}
787787
}
788788

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