diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index fbff98137..89d203fb9 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -169,6 +169,160 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo return rb_field; } +static VALUE rb_mysql_result_fetch_field_type(VALUE self, unsigned int idx) { + VALUE rb_field_type; + GET_RESULT(self); + + if (wrapper->fieldTypes == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields); + } + + unsigned long mb_per_char = 3; + + rb_field_type = rb_ary_entry(wrapper->fieldTypes, idx); + if (rb_field_type == Qnil) { + MYSQL_FIELD *field = NULL; + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); + + field = mysql_fetch_field_direct(wrapper->result, idx); + + switch(field->type) { + case MYSQL_TYPE_NULL: // NULL + rb_field_type = rb_str_new_cstr("null"); + break; + case MYSQL_TYPE_TINY: // signed char + rb_field_type = rb_sprintf("tinyint(%ld)", field->length); + break; + case MYSQL_TYPE_SHORT: // short int + rb_field_type = rb_sprintf("smallint(%ld)", field->length); + break; + case MYSQL_TYPE_YEAR: // short int + rb_field_type = rb_sprintf("year(%ld)", field->length); + break; + case MYSQL_TYPE_INT24: // int + rb_field_type = rb_sprintf("mediumint(%ld)", field->length); + break; + case MYSQL_TYPE_LONG: // int + rb_field_type = rb_sprintf("int(%ld)", field->length); + break; + case MYSQL_TYPE_LONGLONG: // long long int + rb_field_type = rb_sprintf("bigint(%ld)", field->length); + break; + case MYSQL_TYPE_FLOAT: // float + rb_field_type = rb_sprintf("float(%ld,%d)", field->length, field->decimals); + break; + case MYSQL_TYPE_DOUBLE: // double + rb_field_type = rb_sprintf("double(%ld,%d)", field->length, field->decimals); + break; + case MYSQL_TYPE_TIME: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("time"); + break; + case MYSQL_TYPE_DATE: // MYSQL_TIME + case MYSQL_TYPE_NEWDATE: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("date"); + break; + case MYSQL_TYPE_DATETIME: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("datetime"); + break; + case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME + rb_field_type = rb_str_new_cstr("timestamp"); + break; + case MYSQL_TYPE_DECIMAL: // char[] + case MYSQL_TYPE_NEWDECIMAL: // char[] + rb_field_type = rb_sprintf("decimal(%ld,%d)", field->length - 2, field->decimals); + break; + case MYSQL_TYPE_STRING: // char[] + if (field->flags & ENUM_FLAG) { + rb_field_type = rb_str_new_cstr("enum"); + } else if (field->flags & SET_FLAG) { + rb_field_type = rb_str_new_cstr("set"); + } else { + if (field->charsetnr == 63) { + rb_field_type = rb_sprintf("binary(%ld)", field->length); + } else { + rb_field_type = rb_sprintf("char(%ld)", field->length / mb_per_char); + } + } + break; + case MYSQL_TYPE_VAR_STRING: // char[] + if (field->charsetnr == 63) { + rb_field_type = rb_sprintf("varbinary(%ld)", field->length); + } else { + rb_field_type = rb_sprintf("varchar(%ld)", field->length / mb_per_char); + } + break; + case MYSQL_TYPE_VARCHAR: // char[] + rb_field_type = rb_sprintf("varchar(%ld)", field->length / mb_per_char); + break; + case MYSQL_TYPE_TINY_BLOB: // char[] + rb_field_type = rb_str_new_cstr("tinyblob"); + break; + case MYSQL_TYPE_BLOB: // char[] + if (field->charsetnr == 63) { + switch(field->length) { + case 255: + rb_field_type = rb_str_new_cstr("tinyblob"); + break; + case 65535: + rb_field_type = rb_str_new_cstr("blob"); + break; + case 16777215: + rb_field_type = rb_str_new_cstr("mediumblob"); + break; + case 4294967295: + rb_field_type = rb_str_new_cstr("longblob"); + default: + break; + } + } else { + if (field->length == (255 * mb_per_char)) { + rb_field_type = rb_str_new_cstr("tinytext"); + } else if (field->length == (65535 * mb_per_char)) { + rb_field_type = rb_str_new_cstr("text"); + } else if (field->length == (16777215 * mb_per_char)) { + rb_field_type = rb_str_new_cstr("mediumtext"); + } else if (field->length == 4294967295) { + rb_field_type = rb_str_new_cstr("longtext"); + } else { + rb_field_type = rb_sprintf("text(%ld)", field->length); + } + } + break; + case MYSQL_TYPE_MEDIUM_BLOB: // char[] + rb_field_type = rb_str_new_cstr("mediumblob"); + break; + case MYSQL_TYPE_LONG_BLOB: // char[] + rb_field_type = rb_str_new_cstr("longblob"); + break; + case MYSQL_TYPE_BIT: // char[] + rb_field_type = rb_sprintf("bit(%ld)", field->length); + break; + case MYSQL_TYPE_SET: // char[] + rb_field_type = rb_str_new_cstr("set"); + break; + case MYSQL_TYPE_ENUM: // char[] + rb_field_type = rb_str_new_cstr("enum"); + break; + case MYSQL_TYPE_GEOMETRY: // char[] + rb_field_type = rb_str_new_cstr("geometry"); + break; + default: + break; + } + + rb_enc_associate(rb_field_type, conn_enc); + if (default_internal_enc) { + rb_field_type = rb_str_export_to_enc(rb_field_type, default_internal_enc); + } + + rb_ary_store(wrapper->fieldTypes, idx, rb_field_type); + } + + return rb_field_type; +} + static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) { /* if binary flag is set, respect its wishes */ if (field.flags & BINARY_FLAG && field.charsetnr == 63) { @@ -182,7 +336,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e int enc_index; enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL; - + if (enc_name != NULL) { /* use the field encoding we were able to match */ enc_index = rb_enc_find_index(enc_name); @@ -716,6 +870,25 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { return wrapper->fields; } +static VALUE rb_mysql_result_fetch_field_types(VALUE self) { + unsigned int i = 0; + + GET_RESULT(self); + + if (wrapper->fieldTypes == Qnil) { + wrapper->numberOfFields = mysql_num_fields(wrapper->result); + wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields); + } + + if ((my_ulonglong)RARRAY_LEN(wrapper->fieldTypes) != wrapper->numberOfFields) { + for (i=0; inumberOfFields; i++) { + rb_mysql_result_fetch_field_type(self, i); + } + } + + return wrapper->fieldTypes; +} + static VALUE rb_mysql_result_each_(VALUE self, VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args), const result_each_args *args) @@ -934,6 +1107,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ wrapper->resultFreed = 0; wrapper->result = r; wrapper->fields = Qnil; + wrapper->fieldTypes = Qnil; wrapper->rows = Qnil; wrapper->encoding = encoding; wrapper->streamingComplete = 0; @@ -971,6 +1145,7 @@ void init_mysql2_result() { cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); + rb_define_method(cMysql2Result, "field_types", rb_mysql_result_fetch_field_types, 0); rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0); rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0); rb_define_alias(cMysql2Result, "size", "count"); diff --git a/ext/mysql2/result.h b/ext/mysql2/result.h index 0c25b24b6..3f58b1005 100644 --- a/ext/mysql2/result.h +++ b/ext/mysql2/result.h @@ -6,6 +6,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_ typedef struct { VALUE fields; + VALUE fieldTypes; VALUE rows; VALUE client; VALUE encoding; diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index a70b38ef0..f65badde2 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -9,6 +9,7 @@ r = Mysql2::Result.new expect { r.count }.to raise_error(TypeError) expect { r.fields }.to raise_error(TypeError) + expect { r.field_types }.to raise_error(TypeError) expect { r.size }.to raise_error(TypeError) expect { r.each }.to raise_error(TypeError) end @@ -119,6 +120,60 @@ end end + context "#field_types" do + let(:test_result) { @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") } + + it "method should exist" do + expect(test_result).to respond_to(:field_types) + end + + it "should return correct types" do + expected_types = %w[ + mediumint(9) + varchar(10) + bit(64) + bit(1) + tinyint(4) + tinyint(1) + smallint(6) + mediumint(9) + int(11) + bigint(20) + float(10,3) + float(10,3) + double(10,3) + decimal(10,3) + decimal(10,3) + date + datetime + timestamp + time + year(4) + char(10) + varchar(10) + binary(10) + varbinary(10) + tinyblob + tinytext + blob + text + mediumblob + mediumtext + longblob + longtext + enum + set + ] + + expect(test_result.field_types).to eql(expected_types) + end + + it "should return an array of field types in proper order" do + result = @client.query "SELECT cast('a' as char), cast(1 as unsigned), cast(1.2 as decimal(15, 2))" + expect(result.field_types).to eql(%w[varstring bigint decimal]) + end + end + context "streaming" do it "should maintain a count while streaming" do result = @client.query('SELECT 1', stream: true, cache_rows: false)