Skip to content

Commit

Permalink
Merge branch 'main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
bensheldon authored Jan 24, 2024
2 parents 667c899 + 90d81d2 commit 390c0f9
Show file tree
Hide file tree
Showing 19 changed files with 303 additions and 124 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
distribution: "debian:buster"
ruby: "2.8"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: docker login
run: echo $GITHUB_TOKEN | docker login ghcr.io --username trilogy --password-stdin
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
matrix:
mysql: ["5.7", "8.0"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup MySQL
run: |
brew install mysql@${{ matrix.mysql }}
Expand All @@ -34,7 +34,7 @@ jobs:
matrix:
mysql: ["5.7", "8.0"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup MySQL
run: |
brew install mysql@${{ matrix.mysql }}
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,54 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## Unreleased

## 2.7.0

### Changed

- `Trilogy::SyscallError::*` errors now use the standard `Module#===` implementation #143
- `Trilogy::TimeoutError` no longer inherits from `Errno::ETIMEDOUT` #143
- Deprecated `Trilogy::ConnectionRefusedError` and `Trilogy::ConnectionResetError`,
replaced by `Trilogy::SyscallError::ECONNREFUSED` and `Trilogy::SyscallError::ECONNRESET` #143

## 2.6.1

### Fixed

- Report `EOFError: TRILOGY_CLOSED_CONNECTION` for `SSL_ERROR_ZERO_RETURN`
- `write_timeout` on connection now raises `Trilogy::TimeoutError` (previously it raised `EINPROGRESS`)
- Fix memory leak on failed connections
- Fix memory leak when connecting to unix socket

## 2.6.0

### Changed

- `TCP_NODELAY` is enabled on all TCP connections #122
- `Trilogy::EOFError` is now raised for `TRILOGY_CLOSED_CONNECTION` instead
of the generic `Trilogy::QueryError` #118
- `Trilogy::SyscallError` now inherits `Trilogy::ConnectionError` #118

## 2.5.0

### Fixed
- Fix build with LibreSSL #73
- Fix build error on FreeBSD #82
- Fix Trilogy.new with no arguments #94
- Fix issues with OpenSSL #95 #112
- Avoid closing connections that are not connected
- Always close socket on error
- Clear error queue after close
- Clear error queue before each operation to defend against other misbehaving libraries
- Close connection if interrupted by a Ruby timeout #110
- Correctly cast time of 00:00:00 #97

### Added
- Add option to disable multi_result capability #77
- Add option to validate max_allowed_packet #84
- Add binary protocol/prepared statement support to the C library #3
- Cast port option to integer #100
- Add select_db as an alias for change_db #101

## 2.4.1

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Contributing

[fork]: https://github.com/github/trilogy/fork
[pr]: https://github.com/github/trilogy/compare
[fork]: https://github.com/trilogy-libraries/trilogy/fork
[pr]: https://github.com/trilogy-libraries/trilogy/compare

Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ARG RUBY_VERSION=3.2
FROM ${DISTRIBUTION}
LABEL maintainer="github@github.com"

RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y build-essential ca-certificates wget libssl-dev default-libmysqlclient-dev clang clang-tools valgrind netcat
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y build-essential ca-certificates wget libssl-dev default-libmysqlclient-dev clang clang-tools llvm valgrind netcat

RUN update-ca-certificates

Expand Down
5 changes: 4 additions & 1 deletion contrib/ruby/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
trilogy (2.4.1)
trilogy (2.7.0)

GEM
remote: https://rubygems.org/
Expand All @@ -22,3 +22,6 @@ DEPENDENCIES
mysql2
rake-compiler (~> 1.0)
trilogy!

BUNDLED WITH
2.4.12
3 changes: 1 addition & 2 deletions contrib/ruby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ $ gem install trilogy
``` ruby
client = Trilogy.new(host: "127.0.0.1", port: 3306, username: "root", read_timeout: 2)
if client.ping
client.query_options[:database_timezone] = :utc
client.change_db "mydb"

result = client.query("SELECT id, created_at FROM users LIMIT 10")
Expand Down Expand Up @@ -63,7 +62,7 @@ bundle exec rake build

The official Ruby bindings are inside of the canonical trilogy repository itself.

1. Fork it ( https://github.com/github/trilogy/fork )
1. Fork it ( https://github.com/trilogy-libraries/trilogy/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
Expand Down
73 changes: 43 additions & 30 deletions contrib/ruby/ext/trilogy-ruby/cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

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_ConnectionClosedError,
Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, 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,
Expand All @@ -43,9 +43,7 @@ static void mark_trilogy(void *ptr)
static void free_trilogy(void *ptr)
{
struct trilogy_ctx *ctx = ptr;
if (ctx->conn.socket != NULL) {
trilogy_free(&ctx->conn);
}
trilogy_free(&ctx->conn);
xfree(ptr);
}

Expand Down Expand Up @@ -90,16 +88,26 @@ static struct trilogy_ctx *get_open_ctx(VALUE obj)
NORETURN(static void trilogy_syserr_fail_str(int, VALUE));
static void trilogy_syserr_fail_str(int e, VALUE msg)
{
if (e == ECONNREFUSED) {
rb_raise(Trilogy_ConnectionRefusedError, "%" PRIsVALUE, msg);
} else if (e == ECONNRESET) {
rb_raise(Trilogy_ConnectionResetError, "%" PRIsVALUE, msg);
if (e == EPIPE) {
// Backwards compatibility: This error message is a bit odd, but includes "TRILOGY_CLOSED_CONNECTION" to match legacy string matching
rb_raise(Trilogy_EOFError, "%" PRIsVALUE ": TRILOGY_CLOSED_CONNECTION: EPIPE", msg);
} else {
VALUE exc = rb_funcall(Trilogy_SyscallError, id_from_errno, 2, INT2NUM(e), msg);
rb_exc_raise(exc);
}
}

static int trilogy_error_recoverable_p(int rc)
{
// TRILOGY_OPENSSL_ERR and TRILOGY_SYSERR (which can result from an SSL error) must shut down the socket, as further
// SSL calls would be invalid.
// TRILOGY_ERR, which represents an error message sent to us from the server, is recoverable.
// TRILOGY_MAX_PACKET_EXCEEDED is also recoverable as we do not send data when it occurs.
// For other exceptions we will also close the socket to prevent further use, as the connection is probably in an
// invalid state.
return rc == TRILOGY_ERR || rc == TRILOGY_MAX_PACKET_EXCEEDED;
}

NORETURN(static void handle_trilogy_error(struct trilogy_ctx *, int, const char *, ...));
static void handle_trilogy_error(struct trilogy_ctx *ctx, int rc, const char *msg, ...)
{
Expand All @@ -108,14 +116,20 @@ static void handle_trilogy_error(struct trilogy_ctx *ctx, int rc, const char *ms
VALUE rbmsg = rb_vsprintf(msg, args);
va_end(args);

if (!trilogy_error_recoverable_p(rc)) {
if (ctx->conn.socket != NULL) {
// trilogy_sock_shutdown may affect errno
int errno_was = errno;
trilogy_sock_shutdown(ctx->conn.socket);
errno = errno_was;
}
}

switch (rc) {
case TRILOGY_SYSERR:
trilogy_syserr_fail_str(errno, rbmsg);

case TRILOGY_TIMEOUT:
if (ctx->conn.socket != NULL) {
trilogy_sock_shutdown(ctx->conn.socket);
}
rb_raise(Trilogy_TimeoutError, "%" PRIsVALUE, rbmsg);

case TRILOGY_ERR: {
Expand All @@ -131,18 +145,17 @@ static void handle_trilogy_error(struct trilogy_ctx *ctx, int rc, const char *ms
int err_reason = ERR_GET_REASON(ossl_error);
trilogy_syserr_fail_str(err_reason, rbmsg);
}
// We can't recover from OpenSSL level errors if there's
// an active connection.
if (ctx->conn.socket != NULL) {
trilogy_sock_shutdown(ctx->conn.socket);
}
rb_raise(Trilogy_SSLError, "%" PRIsVALUE ": SSL Error: %s", rbmsg, ERR_reason_error_string(ossl_error));
}

case TRILOGY_DNS_ERR: {
rb_raise(Trilogy_BaseConnectionError, "%" PRIsVALUE ": TRILOGY_DNS_ERROR", rbmsg);
}

case TRILOGY_CLOSED_CONNECTION: {
rb_raise(Trilogy_EOFError, "%" PRIsVALUE ": TRILOGY_CLOSED_CONNECTION", rbmsg);
}

default:
rb_raise(Trilogy_QueryError, "%" PRIsVALUE ": %s", rbmsg, trilogy_error(rc));
}
Expand Down Expand Up @@ -290,15 +303,18 @@ static int try_connect(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake,
int rc = args.rc;

if (rc != TRILOGY_OK) {
trilogy_sock_close(sock);
return rc;
}

/* replace the default wait callback with our GVL-aware callback so we can
escape the GVL on each wait operation without going through call_without_gvl */
sock->wait_cb = _cb_ruby_wait;
rc = trilogy_connect_send_socket(&ctx->conn, sock);
if (rc < 0)
if (rc < 0) {
trilogy_sock_close(sock);
return rc;
}

while (1) {
rc = trilogy_connect_recv(&ctx->conn, handshake);
Expand Down Expand Up @@ -406,7 +422,7 @@ static void authenticate(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake
}
}

static VALUE rb_trilogy_initialize(VALUE self, VALUE encoding, VALUE charset, VALUE opts)
static VALUE rb_trilogy_connect(VALUE self, VALUE encoding, VALUE charset, VALUE opts)
{
struct trilogy_ctx *ctx = get_ctx(self);
trilogy_sockopt_t connopt = {0};
Expand All @@ -417,7 +433,6 @@ static VALUE rb_trilogy_initialize(VALUE self, VALUE encoding, VALUE charset, VA
connopt.encoding = NUM2INT(charset);

Check_Type(opts, T_HASH);
rb_ivar_set(self, id_connection_options, opts);

if ((val = rb_hash_lookup(opts, ID2SYM(id_ssl_mode))) != Qnil) {
Check_Type(val, T_FIXNUM);
Expand Down Expand Up @@ -559,9 +574,6 @@ static VALUE rb_trilogy_initialize(VALUE self, VALUE encoding, VALUE charset, VA
}

int rc = try_connect(ctx, &handshake, &connopt);
if (rc == TRILOGY_TIMEOUT) {
rb_raise(Trilogy_TimeoutError, "trilogy_connect_recv");
}
if (rc != TRILOGY_OK) {
if (connopt.path) {
handle_trilogy_error(ctx, rc, "trilogy_connect - unable to connect to %s", connopt.path);
Expand Down Expand Up @@ -982,6 +994,10 @@ static VALUE rb_trilogy_close(VALUE self)
}
}

// We aren't checking or raising errors here (we need close to always close the socket and free the connection), so
// we must clear any SSL errors left in the queue from a read/write.
ERR_clear_error();

trilogy_free(&ctx->conn);

return Qnil;
Expand Down Expand Up @@ -1081,7 +1097,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_private_method(Trilogy, "_initialize", rb_trilogy_initialize, 3);
rb_define_private_method(Trilogy, "_connect", rb_trilogy_connect, 3);
rb_define_method(Trilogy, "change_db", rb_trilogy_change_db, 1);
rb_define_alias(Trilogy, "select_db", "change_db");
rb_define_method(Trilogy, "query", rb_trilogy_query, 1);
Expand Down Expand Up @@ -1136,12 +1152,6 @@ RUBY_FUNC_EXPORTED void Init_cext()
Trilogy_TimeoutError = rb_const_get(Trilogy, rb_intern("TimeoutError"));
rb_global_variable(&Trilogy_TimeoutError);

Trilogy_ConnectionRefusedError = rb_const_get(Trilogy, rb_intern("ConnectionRefusedError"));
rb_global_variable(&Trilogy_ConnectionRefusedError);

Trilogy_ConnectionResetError = rb_const_get(Trilogy, rb_intern("ConnectionResetError"));
rb_global_variable(&Trilogy_ConnectionResetError);

Trilogy_BaseConnectionError = rb_const_get(Trilogy, rb_intern("BaseConnectionError"));
rb_global_variable(&Trilogy_BaseConnectionError);

Expand All @@ -1157,6 +1167,9 @@ RUBY_FUNC_EXPORTED void Init_cext()
Trilogy_CastError = rb_const_get(Trilogy, rb_intern("CastError"));
rb_global_variable(&Trilogy_CastError);

Trilogy_EOFError = rb_const_get(Trilogy, rb_intern("EOFError"));
rb_global_variable(&Trilogy_EOFError);

id_socket = rb_intern("socket");
id_host = rb_intern("host");
id_port = rb_intern("port");
Expand Down
6 changes: 5 additions & 1 deletion contrib/ruby/ext/trilogy-ruby/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

# concatenate trilogy library sources to allow the compiler to optimise across
# source files

trilogy_src_dir = File.realpath("src", __dir__)
File.binwrite("trilogy.c",
Dir["#{__dir__}/src/**/*.c"].map { |src| File.binread(src) }.join)
Dir["#{trilogy_src_dir}/**/*.c"].map { |src|
%{#line 1 "#{src}"\n} + File.binread(src)
}.join)

$objs = %w[trilogy.o cast.o cext.o]
$CFLAGS << " -I #{__dir__}/inc -std=gnu99 -fvisibility=hidden"
Expand Down
6 changes: 4 additions & 2 deletions contrib/ruby/lib/trilogy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

class Trilogy
def initialize(options = {})
options[:port] = options[:port].to_i if options.key?(:port)
options[:port] = options[:port].to_i if options[:port]
mysql_encoding = options[:encoding] || "utf8mb4"
encoding = Trilogy::Encoding.find(mysql_encoding)
charset = Trilogy::Encoding.charset(mysql_encoding)
@connection_options = options
@connected_host = nil

_initialize(encoding, charset, options)
_connect(encoding, charset, options)
end

def connection_options
Expand Down
Loading

0 comments on commit 390c0f9

Please sign in to comment.