From a8e0aaac322cb51bf48a9eb0d5f74eed0d9d90e2 Mon Sep 17 00:00:00 2001 From: Ian Candy Date: Thu, 28 Sep 2023 14:11:07 -0400 Subject: [PATCH 1/3] Add field_type information to result Towards #99, this exposes additional field data on the query result via a `Result#columns` method. We were already using this data in the C extension to determine how to cast the column values. We now add a pointer to the `column_info` struct onto the result and then lazily add the values to the `Result` instance when called. This method could eventually replace the `#fields` method which at the moment only includes the column name. Co-authored-by: Daniel Colson Co-authored-by: Charlotte Wen --- contrib/ruby/ext/trilogy-ruby/cext.c | 93 +++++++++++++++++--- contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h | 1 + contrib/ruby/lib/trilogy/result.rb | 12 +++ contrib/ruby/test/client_test.rb | 3 + 4 files changed, 97 insertions(+), 12 deletions(-) diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index 3a8bddcd..106dc018 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -18,7 +18,7 @@ VALUE Trilogy_CastError; static VALUE Trilogy_BaseConnectionError, Trilogy_ProtocolError, Trilogy_SSLError, Trilogy_QueryError, Trilogy_ConnectionClosedError, Trilogy_ConnectionRefusedError, Trilogy_ConnectionResetError, - Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_EOFError; + Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_Result_Column, Trilogy_EOFError; static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_connect_timeout, id_read_timeout, id_write_timeout, id_keepalive_enabled, id_keepalive_idle, id_keepalive_interval, id_keepalive_count, @@ -34,6 +34,11 @@ struct trilogy_ctx { VALUE encoding; }; +struct trilogy_result_ctx { + struct column_info *column_info; + uint64_t column_count; +}; + static void mark_trilogy(void *ptr) { struct trilogy_ctx *ctx = ptr; @@ -49,6 +54,25 @@ static void free_trilogy(void *ptr) xfree(ptr); } +static void free_trilogy_result(void *ptr) +{ + struct trilogy_result_ctx *ctx = ptr; + if (ctx->column_info != NULL) { + xfree(ctx->column_info); + } + xfree(ptr); +} + +static size_t trilogy_result_memsize(const void *ptr) +{ + const struct trilogy_result_ctx *ctx = ptr; + size_t memsize = sizeof(struct trilogy_result_ctx); + if (ctx->column_info != NULL) { + memsize += sizeof(struct column_info) * ctx->column_count; + } + return memsize; +} + static size_t trilogy_memsize(const void *ptr) { const struct trilogy_ctx *ctx = ptr; size_t memsize = sizeof(struct trilogy_ctx); @@ -69,6 +93,15 @@ static const rb_data_type_t trilogy_data_type = { .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; +static const rb_data_type_t trilogy_result_data_type = { + .wrap_struct_name = "trilogy_result", + .function = { + .dfree = free_trilogy_result, + .dsize = trilogy_result_memsize, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED +}; + static struct trilogy_ctx *get_ctx(VALUE obj) { struct trilogy_ctx *ctx; @@ -183,6 +216,22 @@ static VALUE allocate_trilogy(VALUE klass) return obj; } +static VALUE allocate_trilogy_result(VALUE klass) +{ + struct trilogy_result_ctx *ctx; + + VALUE obj = TypedData_Make_Struct(klass, struct trilogy_result_ctx, &trilogy_result_data_type, ctx); + + return obj; +} + +static struct trilogy_result_ctx *get_trilogy_result_ctx(VALUE obj) +{ + struct trilogy_result_ctx *ctx; + TypedData_Get_Struct(obj, struct trilogy_result_ctx, &trilogy_result_data_type, ctx); + return ctx; +} + static int flush_writes(struct trilogy_ctx *ctx) { while (1) { @@ -764,10 +813,9 @@ static VALUE read_query_response(VALUE vargs) rb_ivar_set(result, id_ivar_last_insert_id, Qnil); rb_ivar_set(result, id_ivar_affected_rows, Qnil); } - - VALUE rb_column_info; - struct column_info *column_info = ALLOCV_N(struct column_info, rb_column_info, column_count); - + struct trilogy_result_ctx *trilogy_result_ctx = get_trilogy_result_ctx(result); + trilogy_result_ctx->column_info = ALLOC_N(struct column_info, column_count); + trilogy_result_ctx->column_count = column_count; for (uint64_t i = 0; i < column_count; i++) { trilogy_column_t column; @@ -797,11 +845,12 @@ static VALUE read_query_response(VALUE vargs) rb_ary_push(column_names, column_name); - column_info[i].type = column.type; - column_info[i].flags = column.flags; - column_info[i].len = column.len; - column_info[i].charset = column.charset; - column_info[i].decimals = column.decimals; + trilogy_result_ctx->column_info[i].name = column_name; + trilogy_result_ctx->column_info[i].type = column.type; + trilogy_result_ctx->column_info[i].flags = column.flags; + trilogy_result_ctx->column_info[i].len = column.len; + trilogy_result_ctx->column_info[i].charset = column.charset; + trilogy_result_ctx->column_info[i].decimals = column.decimals; } VALUE rb_row_values; @@ -828,12 +877,12 @@ static VALUE read_query_response(VALUE vargs) if (args->cast_options->flatten_rows) { for (uint64_t i = 0; i < column_count; i++) { - rb_ary_push(rows, rb_trilogy_cast_value(row_values + i, column_info + i, args->cast_options)); + rb_ary_push(rows, rb_trilogy_cast_value(row_values + i, trilogy_result_ctx->column_info + i, args->cast_options)); } } else { VALUE row = rb_ary_new2(column_count); for (uint64_t i = 0; i < column_count; i++) { - rb_ary_push(row, rb_trilogy_cast_value(row_values + i, column_info + i, args->cast_options)); + rb_ary_push(row, rb_trilogy_cast_value(row_values + i, trilogy_result_ctx->column_info + i, args->cast_options)); } rb_ary_push(rows, row); } @@ -1094,6 +1143,21 @@ static VALUE rb_trilogy_write_timeout_set(VALUE self, VALUE write_timeout) return write_timeout; } +static VALUE rb_trilogy_result_columns(VALUE self) +{ + struct trilogy_result_ctx *trilogy_result_ctx = get_trilogy_result_ctx(self); + VALUE cols = rb_ary_new(); + for (uint64_t i = 0; i < trilogy_result_ctx->column_count; i++) { + VALUE obj = rb_funcall( + Trilogy_Result_Column, rb_intern("new"), 6, trilogy_result_ctx->column_info[i].name, + rb_int_new(trilogy_result_ctx->column_info[i].type), rb_int_new(trilogy_result_ctx->column_info[i].len), + rb_int_new(trilogy_result_ctx->column_info[i].flags), + rb_int_new(trilogy_result_ctx->column_info[i].charset), rb_int_new(trilogy_result_ctx->column_info[i].decimals)); + rb_ary_push(cols, obj); + } + return cols; +} + static VALUE rb_trilogy_server_status(VALUE self) { return LONG2FIX(get_open_ctx(self)->conn.server_status); } static VALUE rb_trilogy_server_version(VALUE self) { return rb_str_new_cstr(get_open_ctx(self)->server_version); } @@ -1171,7 +1235,12 @@ RUBY_FUNC_EXPORTED void Init_cext() rb_global_variable(&Trilogy_ConnectionClosedError); Trilogy_Result = rb_const_get(Trilogy, rb_intern("Result")); + rb_define_alloc_func(Trilogy_Result, allocate_trilogy_result); rb_global_variable(&Trilogy_Result); + rb_define_private_method(Trilogy_Result, "_columns", rb_trilogy_result_columns, 0); + + Trilogy_Result_Column = rb_const_get(Trilogy_Result, rb_intern("Column")); + rb_global_variable(&Trilogy_Result_Column); Trilogy_SyscallError = rb_const_get(Trilogy, rb_intern("SyscallError")); rb_global_variable(&Trilogy_SyscallError); diff --git a/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h b/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h index 606a3167..846d7e9f 100644 --- a/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h +++ b/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h @@ -21,6 +21,7 @@ struct rb_trilogy_cast_options { }; struct column_info { + VALUE name; TRILOGY_TYPE_t type; TRILOGY_CHARSET_t charset; uint32_t len; diff --git a/contrib/ruby/lib/trilogy/result.rb b/contrib/ruby/lib/trilogy/result.rb index 3a09bb93..fc7eeddf 100644 --- a/contrib/ruby/lib/trilogy/result.rb +++ b/contrib/ruby/lib/trilogy/result.rb @@ -28,6 +28,18 @@ def each(&bk) rows.each(&bk) end + def columns + @columns ||= _columns + end + include Enumerable + + class Column + attr_reader :name, :type, :length, :flags, :charset, :decimals + + def initialize(name, type, length, flags, charset, decimals) + @name, @type, @length, @flags, @charset, @decimals = name, type, length, flags, charset, decimals + end + end end end diff --git a/contrib/ruby/test/client_test.rb b/contrib/ruby/test/client_test.rb index e25811cd..f065ab2a 100644 --- a/contrib/ruby/test/client_test.rb +++ b/contrib/ruby/test/client_test.rb @@ -254,6 +254,8 @@ def test_trilogy_query_values result = client.query_with_flags("SELECT id, int_test FROM trilogy_test", client.query_flags | Trilogy::QUERY_FLAGS_FLATTEN_ROWS) assert_equal ["id", "int_test"], result.fields + assert_equal 2, result.columns.count + assert_equal ["id", "int_test"], result.columns.map(&:name) assert_equal [1, 4, 2, 3, 3, 1], result.rows end @@ -314,6 +316,7 @@ def test_trilogy_query_result_object result = client.query "SELECT 1 AS a, 2 AS b" assert_equal ["a", "b"], result.fields + assert_equal ["a", "b"], result.columns.map(&:name) assert_equal [[1, 2]], result.rows assert_equal [{ "a" => 1, "b" => 2 }], result.each_hash.to_a assert_equal [[1, 2]], result.to_a From f4d22d7a23a8b36a5ef64eb004a31884665b2958 Mon Sep 17 00:00:00 2001 From: Ian Candy Date: Fri, 29 Sep 2023 15:03:30 -0400 Subject: [PATCH 2/3] Use symbols for type, charset We want to set some of these values as symbols instead of ints on the Column to make it easier to work with. In this commit, we created loopups for int to string using macros, then changed the type to a symbol via `rb_intern` + `rb_id2sym`. It's possible there's a single function to do this, but we couldn't find one. I also added a downcase function to make sure we get the values in lower instead of uppercase. I'm sure there is a better way to do this. I have no idea how to write C. Co-authored-by: Daniel Colson --- contrib/ruby/ext/trilogy-ruby/cext.c | 30 +++++++++++++++++++++++++--- contrib/ruby/test/client_test.rb | 2 ++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index 106dc018..84910d98 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -1143,6 +1143,29 @@ static VALUE rb_trilogy_write_timeout_set(VALUE self, VALUE write_timeout) return write_timeout; } +const char *const trilogy_type_names[] = { +#define XX(name, code) [name] = (char *)#name + strlen("TRILOGY_TYPE_"), + TRILOGY_TYPES(XX) +#undef XX +}; + +const char *const trilogy_charset_names[] = { +#define XX(name, code) [name] = (char *)#name + strlen("TRILOGY_CHARSET_"), + TRILOGY_CHARSETS(XX) +#undef XX +}; + +static char *downcase(const char *str) +{ + char *new_str = ALLOC_N(char, strlen(str) + 1); + char *p = new_str; + while (*str) { + *p++ = tolower(*str++); + } + *p = '\0'; + return new_str; +} + static VALUE rb_trilogy_result_columns(VALUE self) { struct trilogy_result_ctx *trilogy_result_ctx = get_trilogy_result_ctx(self); @@ -1150,9 +1173,10 @@ static VALUE rb_trilogy_result_columns(VALUE self) for (uint64_t i = 0; i < trilogy_result_ctx->column_count; i++) { VALUE obj = rb_funcall( Trilogy_Result_Column, rb_intern("new"), 6, trilogy_result_ctx->column_info[i].name, - rb_int_new(trilogy_result_ctx->column_info[i].type), rb_int_new(trilogy_result_ctx->column_info[i].len), - rb_int_new(trilogy_result_ctx->column_info[i].flags), - rb_int_new(trilogy_result_ctx->column_info[i].charset), rb_int_new(trilogy_result_ctx->column_info[i].decimals)); + rb_id2sym(rb_intern(downcase(trilogy_type_names[trilogy_result_ctx->column_info[i].type]))), + rb_int_new(trilogy_result_ctx->column_info[i].len), rb_int_new(trilogy_result_ctx->column_info[i].flags), + rb_id2sym(rb_intern(downcase(trilogy_charset_names[trilogy_result_ctx->column_info[i].charset]))), + rb_int_new(trilogy_result_ctx->column_info[i].decimals)); rb_ary_push(cols, obj); } return cols; diff --git a/contrib/ruby/test/client_test.rb b/contrib/ruby/test/client_test.rb index f065ab2a..3304b657 100644 --- a/contrib/ruby/test/client_test.rb +++ b/contrib/ruby/test/client_test.rb @@ -256,6 +256,8 @@ def test_trilogy_query_values assert_equal ["id", "int_test"], result.fields assert_equal 2, result.columns.count assert_equal ["id", "int_test"], result.columns.map(&:name) + assert_equal :long, result.columns.first.type + assert_equal :binary, result.columns.first.charset assert_equal [1, 4, 2, 3, 3, 1], result.rows end From 1131f774d1066288c6471b29ca5600f29b8c82e0 Mon Sep 17 00:00:00 2001 From: Ian Candy Date: Fri, 29 Sep 2023 16:35:10 -0400 Subject: [PATCH 3/3] Hold struct reference in separate object Previously, we had added a pointer to our column_info struct onto our `Result` for lazy loading the data. This resulted in changing the type of `Result` to `T_DATA` which could have negative performance implications. Instead, we can create a separate `Columns` class to store the pointer to our struct. A reference to `columns` now gets eagerly loaded onto the result, but we only populate the actual information for the columns when calling `Result#columns`. This seems like a good trade off for lazy loading (vs. putting the pointer directly on `Result`). Having said that, it's probably worth asking if the lazy loading is worth this extra complexity at all, or if we shoudl simply populate `@columns` with an already loaded array for the `Result` immediately. --- contrib/ruby/ext/trilogy-ruby/cext.c | 67 ++++++++++++++++------------ contrib/ruby/lib/trilogy/result.rb | 17 ++++++- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index 84910d98..6d416455 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -17,12 +17,12 @@ VALUE Trilogy_CastError; static VALUE Trilogy_BaseConnectionError, Trilogy_ProtocolError, Trilogy_SSLError, Trilogy_QueryError, - Trilogy_ConnectionClosedError, Trilogy_ConnectionRefusedError, Trilogy_ConnectionResetError, - Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_Result_Column, Trilogy_EOFError; + Trilogy_ConnectionClosedError, Trilogy_ConnectionRefusedError, Trilogy_ConnectionResetError, Trilogy_TimeoutError, + Trilogy_SyscallError, Trilogy_Result, Trilogy_Result_Columns, Trilogy_Result_Column, Trilogy_EOFError; static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_connect_timeout, id_read_timeout, id_write_timeout, id_keepalive_enabled, id_keepalive_idle, id_keepalive_interval, id_keepalive_count, - id_ivar_affected_rows, id_ivar_fields, id_ivar_last_insert_id, id_ivar_rows, id_ivar_query_time, id_password, + id_ivar_affected_rows, id_ivar_fields, id_ivar_columns, id_ivar_last_insert_id, id_ivar_rows, id_ivar_query_time, id_password, id_database, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key, id_ssl_mode, id_tls_ciphersuites, id_tls_min_version, id_tls_max_version, id_multi_statement, id_multi_result, id_from_code, id_from_errno, id_connection_options, id_max_allowed_packet; @@ -34,7 +34,7 @@ struct trilogy_ctx { VALUE encoding; }; -struct trilogy_result_ctx { +struct trilogy_result_columns_ctx { struct column_info *column_info; uint64_t column_count; }; @@ -54,19 +54,19 @@ static void free_trilogy(void *ptr) xfree(ptr); } -static void free_trilogy_result(void *ptr) +static void free_trilogy_result_columns(void *ptr) { - struct trilogy_result_ctx *ctx = ptr; + struct trilogy_result_columns_ctx *ctx = ptr; if (ctx->column_info != NULL) { xfree(ctx->column_info); } xfree(ptr); } -static size_t trilogy_result_memsize(const void *ptr) +static size_t trilogy_result_columns_memsize(const void *ptr) { - const struct trilogy_result_ctx *ctx = ptr; - size_t memsize = sizeof(struct trilogy_result_ctx); + const struct trilogy_result_columns_ctx *ctx = ptr; + size_t memsize = sizeof(struct trilogy_result_columns_ctx); if (ctx->column_info != NULL) { memsize += sizeof(struct column_info) * ctx->column_count; } @@ -93,11 +93,11 @@ static const rb_data_type_t trilogy_data_type = { .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; -static const rb_data_type_t trilogy_result_data_type = { - .wrap_struct_name = "trilogy_result", +static const rb_data_type_t trilogy_result_columns_data_type = { + .wrap_struct_name = "trilogy_result_columns", .function = { - .dfree = free_trilogy_result, - .dsize = trilogy_result_memsize, + .dfree = free_trilogy_result_columns, + .dsize = trilogy_result_columns_memsize, }, .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; @@ -216,19 +216,19 @@ static VALUE allocate_trilogy(VALUE klass) return obj; } -static VALUE allocate_trilogy_result(VALUE klass) +static VALUE allocate_trilogy_result_columns(VALUE klass) { - struct trilogy_result_ctx *ctx; + struct trilogy_result_columns_ctx *ctx; - VALUE obj = TypedData_Make_Struct(klass, struct trilogy_result_ctx, &trilogy_result_data_type, ctx); + VALUE obj = TypedData_Make_Struct(klass, struct trilogy_result_columns_ctx, &trilogy_result_columns_data_type, ctx); return obj; } -static struct trilogy_result_ctx *get_trilogy_result_ctx(VALUE obj) +static struct trilogy_result_columns_ctx *get_trilogy_result_columns_ctx(VALUE obj) { - struct trilogy_result_ctx *ctx; - TypedData_Get_Struct(obj, struct trilogy_result_ctx, &trilogy_result_data_type, ctx); + struct trilogy_result_columns_ctx *ctx; + TypedData_Get_Struct(obj, struct trilogy_result_columns_ctx, &trilogy_result_columns_data_type, ctx); return ctx; } @@ -798,6 +798,9 @@ static VALUE read_query_response(VALUE vargs) VALUE column_names = rb_ary_new2(column_count); rb_ivar_set(result, id_ivar_fields, column_names); + VALUE columns = rb_obj_alloc(Trilogy_Result_Columns); + rb_ivar_set(result, id_ivar_columns, columns); + VALUE rows = rb_ary_new(); rb_ivar_set(result, id_ivar_rows, rows); @@ -813,7 +816,7 @@ static VALUE read_query_response(VALUE vargs) rb_ivar_set(result, id_ivar_last_insert_id, Qnil); rb_ivar_set(result, id_ivar_affected_rows, Qnil); } - struct trilogy_result_ctx *trilogy_result_ctx = get_trilogy_result_ctx(result); + struct trilogy_result_columns_ctx *trilogy_result_ctx = get_trilogy_result_columns_ctx(columns); trilogy_result_ctx->column_info = ALLOC_N(struct column_info, column_count); trilogy_result_ctx->column_count = column_count; for (uint64_t i = 0; i < column_count; i++) { @@ -1168,15 +1171,15 @@ static char *downcase(const char *str) static VALUE rb_trilogy_result_columns(VALUE self) { - struct trilogy_result_ctx *trilogy_result_ctx = get_trilogy_result_ctx(self); + struct trilogy_result_columns_ctx *trilogy_result_columns_ctx = get_trilogy_result_columns_ctx(self); VALUE cols = rb_ary_new(); - for (uint64_t i = 0; i < trilogy_result_ctx->column_count; i++) { + for (uint64_t i = 0; i < trilogy_result_columns_ctx->column_count; i++) { VALUE obj = rb_funcall( - Trilogy_Result_Column, rb_intern("new"), 6, trilogy_result_ctx->column_info[i].name, - rb_id2sym(rb_intern(downcase(trilogy_type_names[trilogy_result_ctx->column_info[i].type]))), - rb_int_new(trilogy_result_ctx->column_info[i].len), rb_int_new(trilogy_result_ctx->column_info[i].flags), - rb_id2sym(rb_intern(downcase(trilogy_charset_names[trilogy_result_ctx->column_info[i].charset]))), - rb_int_new(trilogy_result_ctx->column_info[i].decimals)); + Trilogy_Result_Column, rb_intern("new"), 6, trilogy_result_columns_ctx->column_info[i].name, + rb_id2sym(rb_intern(downcase(trilogy_type_names[trilogy_result_columns_ctx->column_info[i].type]))), + rb_int_new(trilogy_result_columns_ctx->column_info[i].len), rb_int_new(trilogy_result_columns_ctx->column_info[i].flags), + rb_id2sym(rb_intern(downcase(trilogy_charset_names[trilogy_result_columns_ctx->column_info[i].charset]))), + rb_int_new(trilogy_result_columns_ctx->column_info[i].decimals)); rb_ary_push(cols, obj); } return cols; @@ -1259,9 +1262,14 @@ RUBY_FUNC_EXPORTED void Init_cext() rb_global_variable(&Trilogy_ConnectionClosedError); Trilogy_Result = rb_const_get(Trilogy, rb_intern("Result")); - rb_define_alloc_func(Trilogy_Result, allocate_trilogy_result); rb_global_variable(&Trilogy_Result); - rb_define_private_method(Trilogy_Result, "_columns", rb_trilogy_result_columns, 0); + + Trilogy_Result_Columns = rb_const_get(Trilogy_Result, rb_intern("Columns")); + rb_define_alloc_func(Trilogy_Result_Columns, allocate_trilogy_result_columns); + + rb_define_private_method(Trilogy_Result_Columns, "_all", rb_trilogy_result_columns, 0); + + rb_global_variable(&Trilogy_Result_Columns); Trilogy_Result_Column = rb_const_get(Trilogy_Result, rb_intern("Column")); rb_global_variable(&Trilogy_Result_Column); @@ -1307,6 +1315,7 @@ RUBY_FUNC_EXPORTED void Init_cext() id_from_errno = rb_intern("from_errno"); id_ivar_affected_rows = rb_intern("@affected_rows"); id_ivar_fields = rb_intern("@fields"); + id_ivar_columns = rb_intern("@columns"); id_ivar_last_insert_id = rb_intern("@last_insert_id"); id_ivar_rows = rb_intern("@rows"); id_ivar_query_time = rb_intern("@query_time"); diff --git a/contrib/ruby/lib/trilogy/result.rb b/contrib/ruby/lib/trilogy/result.rb index fc7eeddf..b1d5df9f 100644 --- a/contrib/ruby/lib/trilogy/result.rb +++ b/contrib/ruby/lib/trilogy/result.rb @@ -29,11 +29,26 @@ def each(&bk) end def columns - @columns ||= _columns + @columns.all end include Enumerable + + class Columns + def all + @all ||= _all + end + + def count + all.count + end + + def each(&bk) + all.each(&bk) + end + end + class Column attr_reader :name, :type, :length, :flags, :charset, :decimals