Skip to content

Commit

Permalink
Client now accepts :encoding option.
Browse files Browse the repository at this point in the history
When :encoding is supplied to the client, the charset for the MySQL server
is set accordingly. Additionally, we ensure that the client sends query
strings in the encoding the connection expects.

Co-authored-by: Jean Boussier <jean.boussier@shopify.com>
  • Loading branch information
adrianna-chang-shopify and byroot committed Mar 31, 2023
1 parent a269683 commit 07115bc
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions contrib/ruby/ext/trilogy-ruby/cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
},
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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);
Expand Down
53 changes: 53 additions & 0 deletions contrib/ruby/lib/trilogy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions contrib/ruby/test/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 07115bc

Please sign in to comment.