Skip to content

Commit 5f0f545

Browse files
committed
Result #[] with Array-like [offset, count] plus options in [offset, {opts hash}] and [offset, count, {opts hash}].
1 parent 34a958f commit 5f0f545

File tree

2 files changed

+74
-27
lines changed

2 files changed

+74
-27
lines changed

ext/mysql2/result.c

+50-18
Original file line numberDiff line numberDiff line change
@@ -475,49 +475,81 @@ static void rb_mysql_row_query_options(VALUE opts, ID *db_timezone, ID *app_time
475475
}
476476
}
477477

478-
static VALUE rb_mysql_result_element(VALUE self, VALUE seek) {
479-
VALUE row, opts;
478+
static VALUE rb_mysql_result_element(int argc, VALUE * argv, VALUE self) {
479+
VALUE seek, count, defaults, opts;
480+
VALUE row, rows;
480481
ID db_timezone, app_timezone;
481-
long offset;
482+
long i, c_seek, c_count = 0;
482483
int symbolizeKeys, asArray, castBool, cacheRows, cast;
483484
mysql2_result_wrapper * wrapper;
484485

485486
GetMysql2Result(self, wrapper);
486487

487-
offset = NUM2LONG(seek);
488+
rb_scan_args(argc, argv, "12", &seek, &count, &opts);
489+
/* If the second arg is a hash, it's the opts and there's no count */
490+
if (TYPE(count) == T_HASH) {
491+
opts = count;
492+
count = Qnil;
493+
}
488494

489-
if (!wrapper->numberOfRows) {
490-
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
495+
c_seek = NUM2LONG(seek);
496+
if (!NIL_P(count)) {
497+
c_count = NUM2LONG(count);
498+
/* Special case: ary[x, 0] returns []*/
499+
if (!c_count) return rb_ary_new();
491500
}
492501

493-
opts = rb_iv_get(self, "@query_options");
502+
defaults = rb_iv_get(self, "@query_options");
503+
if (!NIL_P(opts)) {
504+
opts = rb_funcall(defaults, intern_merge, 1, opts);
505+
} else {
506+
opts = defaults;
507+
}
494508
rb_mysql_row_query_options(opts, &db_timezone, &app_timezone, &symbolizeKeys, &asArray, &castBool, &cast, &cacheRows);
495509

496510
if (wrapper->is_streaming) {
497511
rb_raise(cMysql2Error, "Element reference operator #[] cannot be used in streaming mode.");
498512
}
499513

514+
if (!wrapper->numberOfRows) {
515+
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
516+
}
517+
500518
/* count back from the end if passed a negative number */
501-
if (offset < 0) {
502-
offset = wrapper->numberOfRows + offset;
519+
if (c_seek < 0) {
520+
c_seek = wrapper->numberOfRows + c_seek;
503521
}
504522

505523
/* negative offset was too big */
506-
if (offset < 0) {
524+
if (c_seek < 0) {
507525
return Qnil;
508-
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", offset, wrapper->numberOfRows); */
526+
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", c_seek, wrapper->numberOfRows); */
509527
}
510528

511-
if (wrapper->numberOfRows <= (unsigned long)offset) {
512-
return Qnil;
513-
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", offset, wrapper->numberOfRows); */
529+
if (wrapper->numberOfRows <= (unsigned long)c_seek) {
530+
if (!c_count) return Qnil;
531+
else return rb_ary_new();
532+
/* rb_raise(cMysql2Error, "Out of range: offset %ld is beyond %lu rows (offset begins at 0).", c_seek, wrapper->numberOfRows); */
514533
}
515534

516-
mysql_data_seek(wrapper->result, offset);
535+
mysql_data_seek(wrapper->result, c_seek);
517536

518-
row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
537+
if (!c_count) {
538+
return rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
539+
}
540+
541+
/* given ary = [1, 2, 3] then ary[1, 100] returns [2, 3] */
542+
if ((unsigned long)(c_seek + c_count) > wrapper->numberOfRows) {
543+
c_count = wrapper->numberOfRows - c_seek;
544+
}
519545

520-
return row;
546+
/* return an array! */
547+
rows = rb_ary_new2(c_count);
548+
for (i = 0; i < c_count; i++) {
549+
row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
550+
rb_ary_store(rows, i, row);
551+
}
552+
return rows;
521553
}
522554

523555
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
@@ -679,7 +711,7 @@ void init_mysql2_result() {
679711
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
680712

681713
cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
682-
rb_define_method(cMysql2Result, "[]", rb_mysql_result_element, 1);
714+
rb_define_method(cMysql2Result, "[]", rb_mysql_result_element, -1);
683715
rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
684716
rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
685717
rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);

spec/mysql2/result_spec.rb

+24-9
Original file line numberDiff line numberDiff line change
@@ -92,31 +92,46 @@
9292

9393
context "#[]" do
9494
it "should return results when accessed by [offset]" do
95-
result = @client.query "SELECT 1 UNION SELECT 2"
96-
result[1][result.fields.first].should eql(2)
97-
result[0][result.fields.first].should eql(1)
95+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
96+
result[1].should eql({"col" => 2})
97+
result[0].should eql({"col" => 1})
9898
end
9999

100100
it "should return results when accessed by negative [offset]" do
101-
result = @client.query "SELECT 1 UNION SELECT 2"
102-
result[-1][result.fields.first].should eql(2)
103-
result[-2][result.fields.first].should eql(1)
101+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
102+
result[-1].should eql({"col" => 2})
103+
result[-2].should eql({"col" => 1})
104+
end
105+
106+
it "should return array of results when accessed by [offset, count]" do
107+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
108+
result[1, 1].should eql([{"col" => 2}])
109+
result[-2, 10].should eql([{"col" => 1}, {"col" => 2}])
104110
end
105111

106112
it "should return nil if we use too large [offset]" do
107-
result = @client.query "SELECT 1 UNION SELECT 2"
113+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
108114
result[2].should be_nil
109115
result[200].should be_nil
110116
end
111117

112118
it "should return nil if we use too negative [offset]" do
113-
result = @client.query "SELECT 1 UNION SELECT 2"
119+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
114120
result[-3].should be_nil
115121
result[-300].should be_nil
116122
end
117123

124+
it "should accept hash args in [offset, {:foo => bar}] and [offset, count, {:foo => bar}]" do
125+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col"
126+
result[1, {:symbolize_keys => true}].should eql({:col => 2})
127+
result[1, 1, {:symbolize_keys => true}].should eql([{:col => 2}])
128+
# This syntax does not work in Ruby 1.8:
129+
# result[1, :symbolize_keys => true].should eql({:col => 2})
130+
# result[1, 1, :symbolize_keys => true].should eql([{:col => 2}])
131+
end
132+
118133
it "should throw an exception if we use an [offset] in streaming mode" do
119-
result = @client.query "SELECT 1 UNION SELECT 2", :stream => true
134+
result = @client.query "SELECT 1 AS col UNION SELECT 2 AS col", :stream => true
120135
expect { result[0] }.to raise_exception(Mysql2::Error)
121136
end
122137
end

0 commit comments

Comments
 (0)