diff --git a/CHANGELOG.md b/CHANGELOG.md index e81c9982..ee4b0661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ### Fixed - Fix msec values for time columns. #61 +### Changed + - Encode query strings in encoding connection expects. #64 + ## 2.3.0 ### Added diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index 6ce4f2fe..2fe2326e 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -30,8 +30,16 @@ struct trilogy_ctx { trilogy_conn_t conn; char server_version[TRILOGY_SERVER_VERSION_SIZE + 1]; unsigned int query_flags; + VALUE encoding; }; +static void mark_trilogy(void *ptr) +{ + struct trilogy_ctx *ctx = ptr; + rb_gc_mark(ctx->encoding); +} + + static void free_trilogy(void *ptr) { struct trilogy_ctx *ctx = ptr; @@ -54,7 +62,7 @@ static size_t trilogy_memsize(const void *ptr) { static const rb_data_type_t trilogy_data_type = { .wrap_struct_name = "trilogy", .function = { - .dmark = NULL, + .dmark = mark_trilogy, .dfree = free_trilogy, .dsize = trilogy_memsize, }, @@ -356,12 +364,13 @@ static void authenticate(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake } } -static VALUE rb_trilogy_initialize(VALUE self, VALUE opts) +static VALUE rb_trilogy_initialize(VALUE self, VALUE encoding, VALUE opts) { struct trilogy_ctx *ctx = get_ctx(self); trilogy_sockopt_t connopt = {0}; trilogy_handshake_t handshake; VALUE val; + RB_OBJ_WRITE(self, &ctx->encoding, encoding); Check_Type(opts, T_HASH); rb_ivar_set(self, id_connection_options, opts); @@ -817,7 +826,7 @@ static VALUE rb_trilogy_query(VALUE self, VALUE query) { struct trilogy_ctx *ctx = get_open_ctx(self); - StringValue(query); + query = rb_str_export_to_enc(query, rb_to_encoding(ctx->encoding)); int rc = trilogy_query_send(&ctx->conn, RSTRING_PTR(query), RSTRING_LEN(query)); @@ -997,7 +1006,7 @@ RUBY_FUNC_EXPORTED void Init_cext() VALUE Trilogy = rb_const_get(rb_cObject, rb_intern("Trilogy")); rb_define_alloc_func(Trilogy, allocate_trilogy); - rb_define_method(Trilogy, "initialize", rb_trilogy_initialize, 1); + rb_define_private_method(Trilogy, "_initialize", rb_trilogy_initialize, 2); rb_define_method(Trilogy, "change_db", rb_trilogy_change_db, 1); rb_define_method(Trilogy, "query", rb_trilogy_query, 1); rb_define_method(Trilogy, "ping", rb_trilogy_ping, 0); diff --git a/contrib/ruby/lib/trilogy.rb b/contrib/ruby/lib/trilogy.rb index b3dda50b..0b4d5ca4 100644 --- a/contrib/ruby/lib/trilogy.rb +++ b/contrib/ruby/lib/trilogy.rb @@ -107,6 +107,59 @@ class ConnectionClosed < IOError include ConnectionError end + ENCODINGS_MAP = mysql_to_rb = { + "big5" => "Big5", + "dec8" => nil, + "cp850" => "CP850", + "hp8" => nil, + "koi8r" => "KOI8-R", + "latin1" => "ISO-8859-1", + "latin2" => "ISO-8859-2", + "swe7" => nil, + "ascii" => "US-ASCII", + "ujis" => "eucJP-ms", + "sjis" => "Shift_JIS", + "hebrew" => "ISO-8859-8", + "tis620" => "TIS-620", + "euckr" => "EUC-KR", + "koi8u" => "KOI8-R", + "gb2312" => "GB2312", + "greek" => "ISO-8859-7", + "cp1250" => "Windows-1250", + "gbk" => "GBK", + "latin5" => "ISO-8859-9", + "armscii8" => nil, + "utf8" => "UTF-8", + "ucs2" => "UTF-16BE", + "cp866" => "IBM866", + "keybcs2" => nil, + "macce" => "macCentEuro", + "macroman" => "macRoman", + "cp852" => "CP852", + "latin7" => "ISO-8859-13", + "utf8mb4" => "UTF-8", + "cp1251" => "Windows-1251", + "utf16" => "UTF-16", + "cp1256" => "Windows-1256", + "cp1257" => "Windows-1257", + "utf32" => "UTF-32", + "binary" => "ASCII-8BIT", + "geostd8" => nil, + "cp932" => "Windows-31J", + "eucjpms" => "eucJP-ms", + "utf16le" => "UTF-16LE", + "gb18030" => "GB18030", + } + + attr_reader :encoding + + def initialize(options = {}) + mysql_encoding = options[:encoding] || "utf8mb4" + @encoding = Encoding.find(ENCODINGS_MAP[mysql_encoding]) + _initialize(@encoding, **options) + query("SET NAMES #{mysql_encoding}") + end + def connection_options @connection_options.dup.freeze end diff --git a/contrib/ruby/test/client_test.rb b/contrib/ruby/test/client_test.rb index 87ce137b..72396fa2 100644 --- a/contrib/ruby/test/client_test.rb +++ b/contrib/ruby/test/client_test.rb @@ -883,4 +883,34 @@ def test_trilogy_decimal_sum_query assert sum.is_a?(BigDecimal) assert_equal 8, sum end + + def test_character_set_encoding_query + client = new_tcp_client(encoding: "cp932") + + assert_equal "cp932", client.query("SELECT @@character_set_client").first.first + assert_equal "cp932", client.query("SELECT @@character_set_results").first.first + assert_equal "cp932", client.query("SELECT @@character_set_connection").first.first + assert_equal "cp932_japanese_ci", client.query("SELECT @@collation_connection").first.first + + expected = "こんにちは".encode(Encoding::CP932) + assert_equal expected, client.query("SELECT 'こんにちは'").to_a.first.first + end + + def test_character_set_encoding_handles_binary_query + client = new_tcp_client + expected = "\xff".b + + result = client.query("SELECT _binary'#{expected}'").to_a.first.first + assert_equal expected, result + assert_equal Encoding::BINARY, result.encoding + + result = client.query("SELECT '#{expected}'").to_a.first.first + assert_equal expected.dup.force_encoding(client.encoding), result + assert_equal client.encoding, result.encoding + + client = new_tcp_client(encoding: "cp932") + result = client.query("SELECT '#{expected}'").to_a.first.first + assert_equal expected.dup.force_encoding(client.encoding), result + assert_equal client.encoding, result.encoding + end end