Skip to content

Commit bbab643

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

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
@@ -786,46 +786,68 @@ 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) {
789+
static VALUE rb_mysql_result_element(int argc, VALUE * argv, VALUE self) {
790790
result_each_args args;
791791
MYSQL_FIELD *fields = NULL;
792-
long offset;
793792
ID db_timezone, app_timezone;
793+
VALUE seek, count, row, rows;
794+
long i, c_seek, c_count = 0;
794795
int symbolizeKeys, asArray, castBool, cacheRows, cast;
795-
VALUE opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
796+
VALUE defaults, block, opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
796797

797798
GET_RESULT(self);
798799

799-
offset = NUM2LONG(seek);
800+
defaults = rb_iv_get(self, "@query_options");
801+
Check_Type(defaults, T_HASH);
802+
rb_scan_args(argc, argv, "12&", &seek, &count, &opts, &block);
800803

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

805-
opts = rb_iv_get(self, "@query_options");
806823
rb_mysql_row_query_options(opts, &db_timezone, &app_timezone, &symbolizeKeys, &asArray, &castBool, &cast, &cacheRows);
807824

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

829+
if (!wrapper->numberOfRows) {
830+
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
831+
}
832+
812833
/* count back from the end if passed a negative number */
813-
if (offset < 0) {
814-
offset = wrapper->numberOfRows + offset;
834+
if (c_seek < 0) {
835+
c_seek = wrapper->numberOfRows + c_seek;
815836
}
816837

817838
/* negative offset was too big */
818-
if (offset < 0) {
839+
if (c_seek < 0) {
819840
return Qnil;
820-
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", offset, wrapper->numberOfRows); */
841+
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", c_seek, wrapper->numberOfRows); */
821842
}
822843

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

828-
mysql_data_seek(wrapper->result, offset);
850+
mysql_data_seek(wrapper->result, c_seek);
829851

830852
// Backward compat
831853
args.symbolizeKeys = symbolizeKeys;
@@ -835,15 +857,30 @@ static VALUE rb_mysql_result_element(VALUE self, VALUE seek) {
835857
args.cast = cast;
836858
args.db_timezone = db_timezone;
837859
args.app_timezone = app_timezone;
838-
args.block_given = Qnil;
860+
args.block_given = block;
839861

840862
if (wrapper->stmt) {
841863
fetch_row_func = rb_mysql_result_fetch_row_stmt;
842864
} else {
843865
fetch_row_func = rb_mysql_result_fetch_row;
844866
}
845867

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

849886
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)