From e05513c19898efbda71d52befea901abb372e9cc Mon Sep 17 00:00:00 2001 From: errolgrannum Date: Tue, 23 Oct 2018 00:22:51 +0200 Subject: [PATCH 01/10] add support for result rows as structs --- ext/mysql2/client.c | 2 +- ext/mysql2/result.c | 116 +++++++++++++++++++++++++++++++------------- ext/mysql2/result.h | 1 + 3 files changed, 83 insertions(+), 36 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index d12f2c180..6b8a2d43a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -656,7 +656,7 @@ static VALUE do_query(void *args) { tvp->tv_usec = 0; } - for(;;) { + for (;;) { retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp); if (retval == 0) { diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 0ab38aabe..ac5a71d0a 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -22,7 +22,7 @@ static rb_encoding *binaryEncoding; typedef struct { int symbolizeKeys; - int asArray; + int rowsAs; int castBool; int cacheRows; int cast; @@ -37,9 +37,14 @@ static VALUE cMysql2Result, cDateTime, cDate; static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset, intern_merge, intern_BigDecimal; -static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, - sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, - sym_cache_rows, sym_cast, sym_stream, sym_name; +static VALUE sym_symbolize_keys, sym_as, sym_array, sym_struct, sym_database_timezone, + sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name; + +/* internal rowsAs constants */ +#define AS_HASH 0 +#define AS_ARRAY 1 +#define AS_STRUCT 2 + /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ static void rb_mysql_result_mark(void * wrapper) { @@ -48,6 +53,7 @@ static void rb_mysql_result_mark(void * wrapper) { rb_gc_mark(w->fields); rb_gc_mark(w->rows); rb_gc_mark(w->encoding); + rb_gc_mark(w->rowStruct); rb_gc_mark(w->client); rb_gc_mark(w->statement); } @@ -303,11 +309,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } - if (args->asArray) { - rowVal = rb_ary_new2(wrapper->numberOfFields); - } else { - rowVal = rb_hash_new(); - } if (wrapper->result_buffers == NULL) { rb_mysql_result_alloc_result_buffers(self, fields); @@ -336,6 +337,12 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } } + if (args->rowsAs == AS_HASH) { + rowVal = rb_hash_new(); + } else /* array or struct */ { + rowVal = rb_ary_new2(wrapper->numberOfFields); + } + for (i = 0; i < wrapper->numberOfFields; i++) { VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys); VALUE val = Qnil; @@ -464,13 +471,26 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } } - if (args->asArray) { - rb_ary_push(rowVal, val); - } else { + if (args->rowsAs == AS_HASH) { rb_hash_aset(rowVal, field, val); + } else /* array or struct */ { + rb_ary_push(rowVal, val); } } + /* create struct from intermediate array */ + if (args->rowsAs == AS_STRUCT) { + if (wrapper->rowStruct == Qnil) { + VALUE *argv_fields = ALLOCA_N(VALUE, wrapper->numberOfFields); + for (i = 0; i < wrapper->numberOfFields; i++) { + argv_fields[i] = rb_mysql_result_fetch_field(self, i, 1); + } + wrapper->rowStruct = rb_funcallv(rb_cStruct, intern_new, (int) wrapper->numberOfFields, argv_fields); + } + + rowVal = rb_struct_alloc(wrapper->rowStruct, rowVal); + } + return rowVal; } @@ -498,10 +518,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } - if (args->asArray) { - rowVal = rb_ary_new2(wrapper->numberOfFields); - } else { + + if (args->rowsAs == AS_HASH) { rowVal = rb_hash_new(); + } else /* array or struct */ { + /* struct uses array as an intermediary */ + rowVal = rb_ary_new2(wrapper->numberOfFields); } fieldLengths = mysql_fetch_lengths(wrapper->result); @@ -519,14 +541,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); } } else { - switch(type) { + switch (type) { case MYSQL_TYPE_NULL: /* NULL-type field */ val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ if (args->castBool && fields[i].length == 1) { val = *row[i] == 1 ? Qtrue : Qfalse; - }else{ + } else { val = rb_str_new(row[i], fieldLengths[i]); } break; @@ -546,9 +568,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */ if (fields[i].decimals == 0) { val = rb_cstr2inum(row[i], 10); - } else if (strtod(row[i], NULL) == 0.000000){ + } else if (strtod(row[i], NULL) == 0.000000) { val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero); - }else{ + } else { val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i])); } break; @@ -558,7 +580,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r column_to_double = strtod(row[i], NULL); if (column_to_double == 0.000000){ val = opt_float_zero; - }else{ + } else { val = rb_float_new(column_to_double); } break; @@ -671,22 +693,37 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r break; } } - if (args->asArray) { - rb_ary_push(rowVal, val); - } else { + if (args->rowsAs == AS_HASH) { rb_hash_aset(rowVal, field, val); + } else /* array or struct */ { + rb_ary_push(rowVal, val); } } else { - if (args->asArray) { - rb_ary_push(rowVal, Qnil); - } else { + if (args->rowsAs == AS_HASH) { rb_hash_aset(rowVal, field, Qnil); + } else /* array or struct */ { + rb_ary_push(rowVal, Qnil); } } } + + /* create struct from intermediate array */ + if (args->rowsAs == AS_STRUCT) { + if (wrapper->rowStruct == Qnil) { + VALUE *argv_fields = ALLOCA_N(VALUE, wrapper->numberOfFields); + for (i = 0; i < wrapper->numberOfFields; i++) { + argv_fields[i] = rb_mysql_result_fetch_field(self, i, 1); + } + wrapper->rowStruct = rb_funcallv(rb_cStruct, intern_new, (int) wrapper->numberOfFields, argv_fields); + } + + rowVal = rb_struct_alloc(wrapper->rowStruct, rowVal); + } + return rowVal; } + static VALUE rb_mysql_result_fetch_fields(VALUE self) { unsigned int i = 0; short int symbolizeKeys = 0; @@ -696,7 +733,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { defaults = rb_iv_get(self, "@query_options"); Check_Type(defaults, T_HASH); - if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) { + if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue || rb_hash_aref(defaults, sym_as) == sym_struct) { symbolizeKeys = 1; } @@ -809,7 +846,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); ID db_timezone, app_timezone, dbTz, appTz; - int symbolizeKeys, asArray, castBool, cacheRows, cast; + int symbolizeKeys, rowsAs, castBool, cacheRows, cast; GET_RESULT(self); @@ -826,11 +863,18 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys)); - asArray = rb_hash_aref(opts, sym_as) == sym_array; + rowsAs = AS_HASH; castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans)); cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); cast = RTEST(rb_hash_aref(opts, sym_cast)); + if (rb_hash_aref(opts, sym_as) == sym_array) { + rowsAs = AS_ARRAY; + } else if (rb_hash_aref(opts, sym_as) == sym_struct) { + rowsAs = AS_STRUCT; + symbolizeKeys = 1; /* force */ + } + if (wrapper->is_streaming && cacheRows) { rb_warn(":cache_rows is ignored if :stream is true"); } @@ -879,7 +923,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { // Backward compat args.symbolizeKeys = symbolizeKeys; - args.asArray = asArray; + args.rowsAs = rowsAs; args.castBool = castBool; args.cacheRows = cacheRows; args.cast = cast; @@ -930,6 +974,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->result = r; wrapper->fields = Qnil; wrapper->rows = Qnil; + wrapper->rowStruct = Qnil; wrapper->encoding = encoding; wrapper->streamingComplete = 0; wrapper->client = client; @@ -983,15 +1028,16 @@ void init_mysql2_result() { sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); sym_array = ID2SYM(rb_intern("array")); + sym_struct = ID2SYM(rb_intern("struct")); sym_local = ID2SYM(rb_intern("local")); sym_utc = ID2SYM(rb_intern("utc")); sym_cast_booleans = ID2SYM(rb_intern("cast_booleans")); sym_database_timezone = ID2SYM(rb_intern("database_timezone")); sym_application_timezone = ID2SYM(rb_intern("application_timezone")); - sym_cache_rows = ID2SYM(rb_intern("cache_rows")); - sym_cast = ID2SYM(rb_intern("cast")); - sym_stream = ID2SYM(rb_intern("stream")); - sym_name = ID2SYM(rb_intern("name")); + sym_cache_rows = ID2SYM(rb_intern("cache_rows")); + sym_cast = ID2SYM(rb_intern("cast")); + sym_stream = ID2SYM(rb_intern("stream")); + sym_name = ID2SYM(rb_intern("name")); opt_decimal_zero = rb_str_new2("0.0"); rb_global_variable(&opt_decimal_zero); /*never GC */ @@ -1002,4 +1048,4 @@ void init_mysql2_result() { opt_utc_offset = INT2NUM(0); binaryEncoding = rb_enc_find("binary"); -} +} \ No newline at end of file diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 0c25b24b6..afbed4a56 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -10,6 +10,7 @@ typedef struct { VALUE client; VALUE encoding; VALUE statement; + VALUE rowStruct; my_ulonglong numberOfFields; my_ulonglong numberOfRows; unsigned long lastRowProcessed; From 74603cfa922c5768756f094f294ff8eabe0d65cb Mon Sep 17 00:00:00 2001 From: errolgrannum Date: Tue, 23 Oct 2018 00:37:50 +0200 Subject: [PATCH 02/10] add rspec test for result rows as structs --- spec/mysql2/result_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index a70b38ef0..8495914b8 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -72,6 +72,12 @@ end end + it "should be able to return results as a struct" do + @result.each(as: :struct) do |row| + expect(row).to be_kind_of(Struct) + end + end + it "should cache previously yielded results by default" do expect(@result.first.object_id).to eql(@result.first.object_id) end From bec7b6139b15b9ff7979a90789aab967f660c5df Mon Sep 17 00:00:00 2001 From: errolgrannum Date: Tue, 23 Oct 2018 01:27:08 +0200 Subject: [PATCH 03/10] add rspec test to ensure fields are always an array of symbols when as struct is enabled --- spec/mysql2/result_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 8495914b8..ec2dc6656 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -123,6 +123,11 @@ result = @client.query "SELECT 'a', 'b', 'c'" expect(result.fields).to eql(%w[a b c]) end + + it "should return an array of symbolized field names if :as was set to :struct" do + result = @client.query "SELECT 'a', 'b', 'c'", as: :struct + expect(result.fields.first).to be_an_instance_of(Symbol) + end end context "streaming" do From 1793034f0201cde0de4d11865ef670adba43090a Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 19:40:37 -0400 Subject: [PATCH 04/10] undo some unnecessary format changes to reduce diff noise --- ext/mysql2/client.c | 2 +- ext/mysql2/result.c | 34 +++++++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 6b8a2d43a..d12f2c180 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -656,7 +656,7 @@ static VALUE do_query(void *args) { tvp->tv_usec = 0; } - for (;;) { + for(;;) { retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp); if (retval == 0) { diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index ac5a71d0a..637be9f35 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -37,8 +37,11 @@ static VALUE cMysql2Result, cDateTime, cDate; static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset, intern_merge, intern_BigDecimal; -static VALUE sym_symbolize_keys, sym_as, sym_array, sym_struct, sym_database_timezone, - sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name; +static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, + sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, + sym_cache_rows, sym_cast, sym_stream, sym_name; + +static VALUE sym_struct; /* internal rowsAs constants */ #define AS_HASH 0 @@ -541,14 +544,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); } } else { - switch (type) { + switch(type) { case MYSQL_TYPE_NULL: /* NULL-type field */ val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ if (args->castBool && fields[i].length == 1) { val = *row[i] == 1 ? Qtrue : Qfalse; - } else { + }else{ val = rb_str_new(row[i], fieldLengths[i]); } break; @@ -568,9 +571,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */ if (fields[i].decimals == 0) { val = rb_cstr2inum(row[i], 10); - } else if (strtod(row[i], NULL) == 0.000000) { + } else if (strtod(row[i], NULL) == 0.000000){ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero); - } else { + }else{ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i])); } break; @@ -580,7 +583,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r column_to_double = strtod(row[i], NULL); if (column_to_double == 0.000000){ val = opt_float_zero; - } else { + }else{ val = rb_float_new(column_to_double); } break; @@ -844,7 +847,7 @@ static VALUE rb_mysql_result_each_(VALUE self, static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; - VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); + VALUE defaults, opts, as_opt, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); ID db_timezone, app_timezone, dbTz, appTz; int symbolizeKeys, rowsAs, castBool, cacheRows, cast; @@ -868,9 +871,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); cast = RTEST(rb_hash_aref(opts, sym_cast)); - if (rb_hash_aref(opts, sym_as) == sym_array) { + as_opt = rb_hash_aref(opts, sym_as); + if (as_opt == sym_array) { rowsAs = AS_ARRAY; - } else if (rb_hash_aref(opts, sym_as) == sym_struct) { + } else if (as_opt == sym_struct) { rowsAs = AS_STRUCT; symbolizeKeys = 1; /* force */ } @@ -1034,10 +1038,10 @@ void init_mysql2_result() { sym_cast_booleans = ID2SYM(rb_intern("cast_booleans")); sym_database_timezone = ID2SYM(rb_intern("database_timezone")); sym_application_timezone = ID2SYM(rb_intern("application_timezone")); - sym_cache_rows = ID2SYM(rb_intern("cache_rows")); - sym_cast = ID2SYM(rb_intern("cast")); - sym_stream = ID2SYM(rb_intern("stream")); - sym_name = ID2SYM(rb_intern("name")); + sym_cache_rows = ID2SYM(rb_intern("cache_rows")); + sym_cast = ID2SYM(rb_intern("cast")); + sym_stream = ID2SYM(rb_intern("stream")); + sym_name = ID2SYM(rb_intern("name")); opt_decimal_zero = rb_str_new2("0.0"); rb_global_variable(&opt_decimal_zero); /*never GC */ @@ -1048,4 +1052,4 @@ void init_mysql2_result() { opt_utc_offset = INT2NUM(0); binaryEncoding = rb_enc_find("binary"); -} \ No newline at end of file +} From 92b7bed204d0cf359c37ff5493fb87c2ffca3ea1 Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 19:50:34 -0400 Subject: [PATCH 05/10] factor out casting code --- ext/mysql2/result.c | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 637be9f35..06e98d094 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -48,7 +48,6 @@ static VALUE sym_struct; #define AS_ARRAY 1 #define AS_STRUCT 2 - /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ static void rb_mysql_result_mark(void * wrapper) { mysql2_result_wrapper * w = wrapper; @@ -296,6 +295,21 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields } } +static VALUE cast_row_as_struct(VALUE self, VALUE rowVal, mysql2_result_wrapper *wrapper) +{ + /* create struct from intermediate array */ + if (wrapper->rowStruct == Qnil) { + unsigned int i; + VALUE *argv_fields = ALLOCA_N(VALUE, wrapper->numberOfFields); + for (i = 0; i < wrapper->numberOfFields; i++) { + argv_fields[i] = rb_mysql_result_fetch_field(self, i, 1); + } + wrapper->rowStruct = rb_funcallv(rb_cStruct, intern_new, (int) wrapper->numberOfFields, argv_fields); + } + + return rb_struct_alloc(wrapper->rowStruct, rowVal); +} + static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; @@ -481,17 +495,8 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co } } - /* create struct from intermediate array */ if (args->rowsAs == AS_STRUCT) { - if (wrapper->rowStruct == Qnil) { - VALUE *argv_fields = ALLOCA_N(VALUE, wrapper->numberOfFields); - for (i = 0; i < wrapper->numberOfFields; i++) { - argv_fields[i] = rb_mysql_result_fetch_field(self, i, 1); - } - wrapper->rowStruct = rb_funcallv(rb_cStruct, intern_new, (int) wrapper->numberOfFields, argv_fields); - } - - rowVal = rb_struct_alloc(wrapper->rowStruct, rowVal); + rowVal = cast_row_as_struct(self, rowVal, wrapper); } return rowVal; @@ -710,17 +715,8 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r } } - /* create struct from intermediate array */ if (args->rowsAs == AS_STRUCT) { - if (wrapper->rowStruct == Qnil) { - VALUE *argv_fields = ALLOCA_N(VALUE, wrapper->numberOfFields); - for (i = 0; i < wrapper->numberOfFields; i++) { - argv_fields[i] = rb_mysql_result_fetch_field(self, i, 1); - } - wrapper->rowStruct = rb_funcallv(rb_cStruct, intern_new, (int) wrapper->numberOfFields, argv_fields); - } - - rowVal = rb_struct_alloc(wrapper->rowStruct, rowVal); + rowVal = cast_row_as_struct(self, rowVal, wrapper); } return rowVal; From cc237dbd0304dfee514ebb806fe5cb927839a9f5 Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 19:59:44 -0400 Subject: [PATCH 06/10] clean up spec --- ext/mysql2/result.c | 1 - spec/mysql2/result_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 06e98d094..1abb9fd32 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -722,7 +722,6 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r return rowVal; } - static VALUE rb_mysql_result_fetch_fields(VALUE self) { unsigned int i = 0; short int symbolizeKeys = 0; diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index ec2dc6656..c51550ebd 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -124,7 +124,7 @@ expect(result.fields).to eql(%w[a b c]) end - it "should return an array of symbolized field names if :as was set to :struct" do + it "field names should be symbols if :as is set to :struct" do result = @client.query "SELECT 'a', 'b', 'c'", as: :struct expect(result.fields.first).to be_an_instance_of(Symbol) end From c3b9847540f705f1c683af99797cab4efefe74d5 Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 20:04:59 -0400 Subject: [PATCH 07/10] a little more spec cleanup --- spec/mysql2/result_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index c51550ebd..669037d3c 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -124,7 +124,7 @@ expect(result.fields).to eql(%w[a b c]) end - it "field names should be symbols if :as is set to :struct" do + it "should return field names as symbols if rows are structs" do result = @client.query "SELECT 'a', 'b', 'c'", as: :struct expect(result.fields.first).to be_an_instance_of(Symbol) end From 0204f003dee38490caad9f285dfafff3d518c3af Mon Sep 17 00:00:00 2001 From: Joe Holt <1596163+joe07734@users.noreply.github.com> Date: Thu, 1 Nov 2018 11:59:34 -0400 Subject: [PATCH 08/10] use more compatible alternate name for rb_funcallv --- ext/mysql2/result.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 1abb9fd32..29e653759 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -304,7 +304,7 @@ static VALUE cast_row_as_struct(VALUE self, VALUE rowVal, mysql2_result_wrapper for (i = 0; i < wrapper->numberOfFields; i++) { argv_fields[i] = rb_mysql_result_fetch_field(self, i, 1); } - wrapper->rowStruct = rb_funcallv(rb_cStruct, intern_new, (int) wrapper->numberOfFields, argv_fields); + wrapper->rowStruct = rb_funcall2(rb_cStruct, intern_new, (int) wrapper->numberOfFields, argv_fields); } return rb_struct_alloc(wrapper->rowStruct, rowVal); From bfe0c3f4e7b88ac1651a18e88fa76c3e451d2598 Mon Sep 17 00:00:00 2001 From: Joe Holt <1596163+joe07734@users.noreply.github.com> Date: Thu, 1 Nov 2018 14:35:28 -0400 Subject: [PATCH 09/10] add unit test for statements --- spec/mysql2/statement_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index dbc185e6b..1a3216659 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -294,6 +294,13 @@ def stmt_count end end + it "should be able to return results as a struct" do + @result = @client.prepare("SELECT 1").execute(as: :struct) + @result.each do |row| + expect(row).to be_kind_of(Struct) + end + end + it "should cache previously yielded results by default" do @result = @client.prepare("SELECT 1").execute expect(@result.first.object_id).to eql(@result.first.object_id) @@ -329,6 +336,11 @@ def stmt_count expect(stmt.fields).to eql(%w[a b c]) end + it "should return field names as symbols if rows are structs" do + result = @client.prepare("SELECT 'a', 'b', 'c'").execute(as: :struct) + expect(result.fields.first).to be_an_instance_of(Symbol) + end + it "should return nil for statement with no result fields" do stmt = @client.prepare("INSERT INTO mysql2_test () VALUES ()") expect(stmt.fields).to eql(nil) From db4a06d3e7b14029e42489eeba4a976200e5171b Mon Sep 17 00:00:00 2001 From: Joe Holt <1596163+joe07734@users.noreply.github.com> Date: Wed, 7 Nov 2018 18:58:44 -0500 Subject: [PATCH 10/10] rework how AS_HASH is assigned --- ext/mysql2/result.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 29e653759..aa99cad9f 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -861,7 +861,6 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys)); - rowsAs = AS_HASH; castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans)); cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); cast = RTEST(rb_hash_aref(opts, sym_cast)); @@ -872,6 +871,8 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { } else if (as_opt == sym_struct) { rowsAs = AS_STRUCT; symbolizeKeys = 1; /* force */ + } else { + rowsAs = AS_HASH; } if (wrapper->is_streaming && cacheRows) {