Skip to content

Commit

Permalink
Implementing type reflection from mysql result
Browse files Browse the repository at this point in the history
  • Loading branch information
danhuynhdev committed Sep 8, 2019
1 parent 9804551 commit 7f1c498
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 1 deletion.
177 changes: 176 additions & 1 deletion ext/mysql2/result.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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; i<wrapper->numberOfFields; 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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions ext/mysql2/result.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
55 changes: 55 additions & 0 deletions spec/mysql2/result_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 7f1c498

Please sign in to comment.