Skip to content

Commit 014e6a1

Browse files
committed
Result #[] with Array-like [offset, count] plus options in [offset, {opts hash}] and [offset, count, {opts hash}].
1 parent b7d5d6c commit 014e6a1

File tree

2 files changed

+79
-27
lines changed

2 files changed

+79
-27
lines changed

ext/mysql2/result.c

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -789,46 +789,68 @@ 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) {
792+
static VALUE rb_mysql_result_element(int argc, VALUE * argv, VALUE self) {
793793
result_each_args args;
794794
MYSQL_FIELD *fields = NULL;
795-
long offset;
796795
ID db_timezone, app_timezone;
796+
VALUE seek, count, row, rows;
797+
long i, c_seek, c_count = 0;
797798
int symbolizeKeys, asArray, castBool, cacheRows, cast;
798-
VALUE opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
799+
VALUE defaults, block, opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
799800

800801
GET_RESULT(self);
801802

802-
offset = NUM2LONG(seek);
803+
defaults = rb_iv_get(self, "@query_options");
804+
Check_Type(defaults, T_HASH);
805+
rb_scan_args(argc, argv, "12&", &seek, &count, &opts, &block);
803806

804-
if (!wrapper->numberOfRows) {
805-
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
807+
/* If the second arg is a hash, it's the opts and there's no count */
808+
if (TYPE(count) == T_HASH) {
809+
opts = count;
810+
count = Qnil;
811+
}
812+
813+
c_seek = NUM2LONG(seek);
814+
if (!NIL_P(count)) {
815+
c_count = NUM2LONG(count);
816+
/* Special case: ary[x, 0] returns []*/
817+
if (!c_count) return rb_ary_new();
818+
}
819+
820+
if (!NIL_P(opts)) {
821+
opts = rb_funcall(defaults, intern_merge, 1, opts);
822+
} else {
823+
opts = defaults;
806824
}
807825

808-
opts = rb_iv_get(self, "@query_options");
809826
rb_mysql_row_query_options(opts, &db_timezone, &app_timezone, &symbolizeKeys, &asArray, &castBool, &cast, &cacheRows);
810827

811828
if (wrapper->is_streaming) {
812829
rb_raise(cMysql2Error, "Element reference operator #[] cannot be used in streaming mode.");
813830
}
814831

832+
if (!wrapper->numberOfRows) {
833+
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
834+
}
835+
815836
/* count back from the end if passed a negative number */
816-
if (offset < 0) {
817-
offset = wrapper->numberOfRows + offset;
837+
if (c_seek < 0) {
838+
c_seek = wrapper->numberOfRows + c_seek;
818839
}
819840

820841
/* negative offset was too big */
821-
if (offset < 0) {
842+
if (c_seek < 0) {
822843
return Qnil;
823-
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", offset, wrapper->numberOfRows); */
844+
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", c_seek, wrapper->numberOfRows); */
824845
}
825846

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); */
847+
if (wrapper->numberOfRows <= (unsigned long)c_seek) {
848+
if (!c_count) return Qnil;
849+
else return rb_ary_new();
850+
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", c_seek, wrapper->numberOfRows); */
829851
}
830852

831-
mysql_data_seek(wrapper->result, offset);
853+
mysql_data_seek(wrapper->result, c_seek);
832854

833855
// Backward compat
834856
args.symbolizeKeys = symbolizeKeys;
@@ -838,15 +860,30 @@ static VALUE rb_mysql_result_element(VALUE self, VALUE seek) {
838860
args.cast = cast;
839861
args.db_timezone = db_timezone;
840862
args.app_timezone = app_timezone;
841-
args.block_given = Qnil;
863+
args.block_given = block;
842864

843865
if (wrapper->stmt) {
844866
fetch_row_func = rb_mysql_result_fetch_row_stmt;
845867
} else {
846868
fetch_row_func = rb_mysql_result_fetch_row;
847869
}
848870

849-
return fetch_row_func(self, fields, &args);
871+
if (!c_count) {
872+
return fetch_row_func(self, fields, &args);
873+
}
874+
875+
/* given ary = [1, 2, 3] then ary[1, 100] returns [2, 3] */
876+
if ((unsigned long)(c_seek + c_count) > wrapper->numberOfRows) {
877+
c_count = wrapper->numberOfRows - c_seek;
878+
}
879+
880+
/* return an array! */
881+
rows = rb_ary_new2(c_count);
882+
for (i = 0; i < c_count; i++) {
883+
row = fetch_row_func(self, fields, &args);
884+
rb_ary_store(rows, i, row);
885+
}
886+
return rows;
850887
}
851888

852889
static VALUE rb_mysql_result_each_(VALUE self,
@@ -1042,7 +1079,7 @@ void init_mysql2_result() {
10421079
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
10431080

10441081
cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
1045-
rb_define_method(cMysql2Result, "[]", rb_mysql_result_element, 1);
1082+
rb_define_method(cMysql2Result, "[]", rb_mysql_result_element, -1);
10461083
rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
10471084
rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
10481085
rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);

spec/mysql2/result_spec.rb

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,31 +100,46 @@
100100

101101
context "#[]" do
102102
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)
103+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
104+
result[1].should eql({"col" => 2})
105+
result[0].should eql({"col" => 1})
106106
end
107107

108108
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)
109+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
110+
result[-1].should eql({"col" => 2})
111+
result[-2].should eql({"col" => 1})
112+
end
113+
114+
it "should return array of results when accessed by [offset, count]" do
115+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
116+
result[1, 1].should eql([{"col" => 2}])
117+
result[-2, 10].should eql([{"col" => 1}, {"col" => 2}])
112118
end
113119

114120
it "should return nil if we use too large [offset]" do
115-
result = @client.query "SELECT 1 UNION SELECT 2"
121+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
116122
result[2].should be_nil
117123
result[200].should be_nil
118124
end
119125

120126
it "should return nil if we use too negative [offset]" do
121-
result = @client.query "SELECT 1 UNION SELECT 2"
127+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
122128
result[-3].should be_nil
123129
result[-300].should be_nil
124130
end
125131

132+
it "should accept hash args in [offset, {:foo => bar}] and [offset, count, {:foo => bar}]" do
133+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
134+
result[1, {:symbolize_keys => true}].should eql({:col => 2})
135+
result[1, 1, {:symbolize_keys => true}].should eql([{:col => 2}])
136+
# This syntax does not work in Ruby 1.8:
137+
# result[1, :symbolize_keys => true].should eql({:col => 2})
138+
# result[1, 1, :symbolize_keys => true].should eql([{:col => 2}])
139+
end
140+
126141
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
142+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col", :stream => true
128143
expect { result[0] }.to raise_exception(Mysql2::Error)
129144
end
130145
end

0 commit comments

Comments
 (0)