From 1d203a9f1464dbc5d77b5be3b6b5d3711752fed4 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 16 Dec 2021 20:40:02 -0800 Subject: [PATCH 01/26] Add binary protocol/prepared statement support This adds support for the binary protocol/prepared in the C library API. --- README.md | 2 +- inc/trilogy/blocking.h | 15 + inc/trilogy/builder.h | 4 + inc/trilogy/client.h | 24 + inc/trilogy/error.h | 3 +- inc/trilogy/protocol.h | 172 +++++++ inc/trilogy/reader.h | 4 + src/blocking.c | 117 +++++ src/builder.c | 42 ++ src/client.c | 178 +++++++ src/protocol.c | 487 ++++++++++++++++++ src/reader.c | 42 ++ test/client/stmt_close_test.c | 163 ++++++ test/client/stmt_execute_test.c | 376 ++++++++++++++ test/client/stmt_prepare_test.c | 159 ++++++ test/client/stmt_reset_test.c | 319 ++++++++++++ .../building/stmt_bind_data_packet_test.c | 43 ++ .../building/stmt_close_packet_test.c | 39 ++ .../building/stmt_execute_packet_test.c | 43 ++ .../building/stmt_prepare_packet_test.c | 40 ++ .../building/stmt_reset_packet_test.c | 39 ++ test/runner.c | 11 +- test/test.h | 1 + 23 files changed, 2320 insertions(+), 3 deletions(-) create mode 100644 test/client/stmt_close_test.c create mode 100644 test/client/stmt_execute_test.c create mode 100644 test/client/stmt_prepare_test.c create mode 100644 test/client/stmt_reset_test.c create mode 100644 test/protocol/building/stmt_bind_data_packet_test.c create mode 100644 test/protocol/building/stmt_close_packet_test.c create mode 100644 test/protocol/building/stmt_execute_packet_test.c create mode 100644 test/protocol/building/stmt_prepare_packet_test.c create mode 100644 test/protocol/building/stmt_reset_packet_test.c diff --git a/README.md b/README.md index 8ea0f5f4..a7b0a93f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ It's currently in production use on github.com. ## Limitations -* Only supports the parts of the text protocol that are in common use. There's no support for the binary protocol or prepared statements +* Only supports the parts of the text protocol that are in common use. * No support for `LOAD DATA INFILE` on local files diff --git a/inc/trilogy/blocking.h b/inc/trilogy/blocking.h index 1f84099a..22984138 100644 --- a/inc/trilogy/blocking.h +++ b/inc/trilogy/blocking.h @@ -177,4 +177,19 @@ int trilogy_ping(trilogy_conn_t *conn); */ int trilogy_close(trilogy_conn_t *conn); +int trilogy_stmt_prepare(trilogy_conn_t *conn, const char *stmt, size_t stmt_len, trilogy_stmt_t *stmt_out); + +int trilogy_stmt_execute(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint8_t flags, trilogy_binary_value_t *binds, + uint64_t *column_count_out); + +int trilogy_stmt_bind_data(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint16_t param_num, uint8_t *data, + size_t data_len); + +int trilogy_stmt_read_full_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_column_packet_t *columns, + trilogy_binary_value_t *values_out); + +int trilogy_stmt_reset(trilogy_conn_t *conn, trilogy_stmt_t *stmt); + +int trilogy_stmt_close(trilogy_conn_t *conn, trilogy_stmt_t *stmt); + #endif diff --git a/inc/trilogy/builder.h b/inc/trilogy/builder.h index 3147cc69..a43cd5e6 100644 --- a/inc/trilogy/builder.h +++ b/inc/trilogy/builder.h @@ -117,6 +117,10 @@ int trilogy_builder_write_uint32(trilogy_builder_t *builder, uint32_t val); */ int trilogy_builder_write_uint64(trilogy_builder_t *builder, uint64_t val); +int trilogy_builder_write_float(trilogy_builder_t *builder, float val); + +int trilogy_builder_write_double(trilogy_builder_t *builder, double val); + /* trilogy_builder_write_lenenc - Append a length-encoded integer to the packet * buffer. * diff --git a/inc/trilogy/client.h b/inc/trilogy/client.h index 0629908d..288fb1f1 100644 --- a/inc/trilogy/client.h +++ b/inc/trilogy/client.h @@ -601,4 +601,28 @@ void trilogy_free(trilogy_conn_t *conn); */ int trilogy_discard(trilogy_conn_t *conn); +int trilogy_stmt_prepare_send(trilogy_conn_t *conn, const char *stmt, size_t stmt_len); + +/* trilogy_stmt_t - The trilogy client's prepared statement type. + */ +typedef trilogy_stmt_ok_packet_t trilogy_stmt_t; + +int trilogy_stmt_prepare_recv(trilogy_conn_t *conn, trilogy_stmt_t *stmt_out); + +int trilogy_stmt_bind_data_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint16_t param_num, uint8_t *data, + size_t data_len); + +int trilogy_stmt_execute_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint8_t flags, trilogy_binary_value_t *binds); + +int trilogy_stmt_execute_recv(trilogy_conn_t *conn, uint64_t *column_count_out); + +int trilogy_stmt_read_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_column_packet_t *columns, + trilogy_binary_value_t *values_out); + +int trilogy_stmt_reset_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt); + +int trilogy_stmt_reset_recv(trilogy_conn_t *conn); + +int trilogy_stmt_close_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt); + #endif diff --git a/inc/trilogy/error.h b/inc/trilogy/error.h index e3ee5134..5756eae5 100644 --- a/inc/trilogy/error.h +++ b/inc/trilogy/error.h @@ -22,7 +22,8 @@ XX(TRILOGY_UNSUPPORTED, -17) \ XX(TRILOGY_DNS_ERR, -18) \ XX(TRILOGY_AUTH_SWITCH, -19) \ - XX(TRILOGY_MAX_PACKET_EXCEEDED, -20) + XX(TRILOGY_MAX_PACKET_EXCEEDED, -20) \ + XX(TRILOGY_UNKNOWN_TYPE, -21) enum { #define XX(name, code) name = code, diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 0fd0bb46..ef9fe3a0 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -564,6 +564,13 @@ typedef struct { size_t last_gtid_len; } trilogy_ok_packet_t; +typedef struct { + uint32_t id; + uint16_t column_count; + uint16_t parameter_count; + uint16_t warning_count; +} trilogy_stmt_ok_packet_t; + typedef struct { uint16_t warning_count; uint16_t status_flags; @@ -614,6 +621,127 @@ typedef struct { size_t data_len; } trilogy_value_t; +typedef struct { + bool is_null; + + TRILOGY_TYPE_t type; + + union { + double dbl; + + int64_t int64; + uint64_t uint64; + + float flt; + + uint32_t uint32; + int32_t int32; + + uint16_t uint16; + int16_t int16; + + uint8_t uint8; + int8_t int8; + + struct { + const void *data; + size_t len; + } str; + + struct { + uint16_t year; + uint8_t month, day; + struct { + bool is_negative; + uint32_t days; + uint8_t hour, minute, second; + uint32_t micro_seconds; + } time; + } date; + } as; +} trilogy_binary_value_t; + +/* trilogy_build_stmt_prepare_packet - Build a prepared statement prepare command packet. + * + * builder - A pointer to a pre-initialized trilogy_builder_t. + * query - The query string to be used by the prepared statement. + * query_len - The length of query in bytes. + * + * Return values: + * TRILOGY_OK - The packet was successfully built and written to the + * builder's internal buffer. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_build_stmt_prepare_packet(trilogy_builder_t *builder, const char *sql, size_t sql_len); + +// Prepared statement flags +typedef enum { + TRILOGY_CURSOR_TYPE_NO_CURSOR = 0x00, + TRILOGY_CURSOR_TYPE_READ_ONLY = 0x01, + TRILOGY_CURSOR_TYPE_FOR_UPDATE = 0x02, + TRILOGY_CURSOR_TYPE_SCROLLABLE = 0x04, + TRILOGY_CURSOR_TYPE_UNKNOWN +} TRILOGY_STMT_FLAGS_t; + +/* trilogy_build_stmt_execute_packet - Build a prepared statement execute command packet. + * + * builder - A pointer to a pre-initialized trilogy_builder_t. + * stmt_id - The statement id for which to build the execute packet with. + * flags - The flags (TRILOGY_STMT_FLAGS_t) to be used with this execute command packet. + * binds - Pointer to an array of trilogy_binary_value_t's. + * num_binds - The number of elements in the binds array above. + * + * Return values: + * TRILOGY_OK - The packet was successfully built and written to the + * builder's internal buffer. + * TRILOGY_PROTOCOL_VIOLATION - num_binds is > 0 but binds is NULL. + * TRILOGY_UNKNOWN_TYPE - An unsupported or unknown MySQL type was used in the list + * of binds. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_id, uint8_t flags, + trilogy_binary_value_t *binds, uint16_t num_binds); + +/* trilogy_build_stmt_bind_data_packet - Build a prepared statement send long data command packet. + * + * builder - A pointer to a pre-initialized trilogy_builder_t. + * stmt_id - The statement id for which to build the bind data packet with. + * param_id - The parameter index for which the supplied data should be bound to. + * data - A pointer to the buffer containing the data to be bound. + * data_len - The length of the data buffer. + * + * Return values: + * TRILOGY_OK - The packet was successfully built and written to the + * builder's internal buffer. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_build_stmt_bind_data_packet(trilogy_builder_t *builder, uint32_t stmt_id, uint16_t param_id, uint8_t *data, + size_t data_len); + +/* trilogy_build_stmt_reset_packet - Build a prepared statement reset command packet. + * + * builder - A pointer to a pre-initialized trilogy_builder_t. + * stmt_id - The statement id for which to build the reset packet with. + * + * Return values: + * TRILOGY_OK - The packet was successfully built and written to the + * builder's internal buffer. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_build_stmt_reset_packet(trilogy_builder_t *builder, uint32_t stmt_id); + +/* trilogy_build_stmt_close_packet - Build a prepared statement close command packet. + * + * builder - A pointer to a pre-initialized trilogy_builder_t. + * stmt_id - The statement id for which to build the close packet with. + * + * Return values: + * TRILOGY_OK - The packet was successfully built and written to the + * builder's internal buffer. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_build_stmt_close_packet(trilogy_builder_t *builder, uint32_t stmt_id); + /* The following parsing functions assume the buffer and length passed in point * to one full MySQL-compatible packet. If the buffer contains more than one packet or * has any extra data at the end, these functions will return @@ -770,4 +898,48 @@ int trilogy_parse_column_packet(const uint8_t *buff, size_t len, bool field_list */ int trilogy_parse_row_packet(const uint8_t *buff, size_t len, uint64_t column_count, trilogy_value_t *out_values); +/* trilogy_parse_stmt_ok_packet - Parse a prepared statement ok packet. + * + * buff - A pointer to the buffer containing the result packet data. + * len - The length of buffer in bytes. + * out_packet - Out parameter; A pointer to a pre-allocated trilogy_stmt_ok_packet_t. + * + * Return values: + * TRILOGY_OK - The packet was was parsed and the out + * parameter has been filled in. + * TRILOGY_TRUNCATED_PACKET - There isn't enough data in the buffer + * to parse the packet. + * TRILOGY_PROTOCOL_VIOLATION - Filler byte was something other than zero. + * TRILOGY_EXTRA_DATA_IN_PACKET - There are unparsed bytes left in the + * buffer. + */ +int trilogy_parse_stmt_ok_packet(const uint8_t *buff, size_t len, trilogy_stmt_ok_packet_t *out_packet); + +/* trilogy_parse_stmt_row_packet - Parse a prepared statement row packet. + * + * buff - A pointer to the buffer containing the result packet data. + * len - The length of buffer in bytes. + * columns - The list of columns from the prepared statement. This parser needs + * this in order to match up the value types. + * column_count - The number of columns in prepared statement. This parser needs this + * in order to know how many values to parse. + * out_values - Out parameter; A pointer to a pre-allocated array of + * trilogy_binary_value_t's. There must be enough space to fit all of the + * values. This can be computed with: + * `(sizeof(trilogy_value_t) * column_count)`. + * + * Return values: + * TRILOGY_OK - The packet was was parsed and the out + * parameter has been filled in. + * TRILOGY_TRUNCATED_PACKET - There isn't enough data in the buffer + * to parse the packet. + * TRILOGY_PROTOCOL_VIOLATION - Invalid length parsed for a TIME/DATETIME/TIMESTAMP value. + * TRILOGY_UNKNOWN_TYPE - An unsupported or unknown MySQL type was used in the list + * of binds. + * TRILOGY_EXTRA_DATA_IN_PACKET - There are unparsed bytes left in the + * buffer. + */ +int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_column_packet_t *columns, + uint64_t column_count, trilogy_binary_value_t *out_values); + #endif diff --git a/inc/trilogy/reader.h b/inc/trilogy/reader.h index cc46ad8d..0707e691 100644 --- a/inc/trilogy/reader.h +++ b/inc/trilogy/reader.h @@ -90,6 +90,10 @@ int trilogy_reader_get_uint32(trilogy_reader_t *reader, uint32_t *out); */ int trilogy_reader_get_uint64(trilogy_reader_t *reader, uint64_t *out); +int trilogy_reader_get_float(trilogy_reader_t *reader, float *out); + +int trilogy_reader_get_double(trilogy_reader_t *reader, double *out); + /* trilogy_reader_get_lenenc - Parse an unsigned, length-encoded integer. * * reader - A pointer to a pre-initialized trilogy_reader_t. diff --git a/src/blocking.c b/src/blocking.c index eaa65e73..3afa562e 100644 --- a/src/blocking.c +++ b/src/blocking.c @@ -261,4 +261,121 @@ int trilogy_close(trilogy_conn_t *conn) } } +int trilogy_stmt_prepare(trilogy_conn_t *conn, const char *stmt, size_t stmt_len, trilogy_stmt_t *stmt_out) +{ + int rc = trilogy_stmt_prepare_send(conn, stmt, stmt_len); + + if (rc == TRILOGY_AGAIN) { + rc = flush_full(conn); + } + + if (rc < 0) { + return rc; + } + + while (1) { + rc = trilogy_stmt_prepare_recv(conn, stmt_out); + + if (rc != TRILOGY_AGAIN) { + return rc; + } + + CHECKED(trilogy_sock_wait_read(conn->socket)); + } +} + +int trilogy_stmt_execute(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint8_t flags, trilogy_binary_value_t *binds, + uint64_t *column_count_out) +{ + int rc = trilogy_stmt_execute_send(conn, stmt, flags, binds); + + if (rc == TRILOGY_AGAIN) { + rc = flush_full(conn); + } + + if (rc < 0) { + return rc; + } + + while (1) { + rc = trilogy_stmt_execute_recv(conn, column_count_out); + + if (rc != TRILOGY_AGAIN) { + return rc; + } + + CHECKED(trilogy_sock_wait_read(conn->socket)); + } +} + +int trilogy_stmt_bind_data(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint16_t param_num, uint8_t *data, + size_t data_len) +{ + int rc = trilogy_stmt_bind_data_send(conn, stmt, param_num, data, data_len); + + if (rc == TRILOGY_AGAIN) { + rc = flush_full(conn); + } + + if (rc < 0) { + return rc; + } + + return TRILOGY_OK; +} + +int trilogy_stmt_read_full_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_column_packet_t *columns, + trilogy_binary_value_t *values_out) +{ + int rc; + + while (1) { + rc = trilogy_stmt_read_row(conn, stmt, columns, values_out); + + if (rc != TRILOGY_AGAIN) { + return rc; + } + + CHECKED(trilogy_sock_wait_read(conn->socket)); + } +} + +int trilogy_stmt_reset(trilogy_conn_t *conn, trilogy_stmt_t *stmt) +{ + int rc = trilogy_stmt_reset_send(conn, stmt); + + if (rc == TRILOGY_AGAIN) { + rc = flush_full(conn); + } + + if (rc < 0) { + return rc; + } + + while (1) { + rc = trilogy_stmt_reset_recv(conn); + + if (rc != TRILOGY_AGAIN) { + return rc; + } + + CHECKED(trilogy_sock_wait_read(conn->socket)); + } +} + +int trilogy_stmt_close(trilogy_conn_t *conn, trilogy_stmt_t *stmt) +{ + int rc = trilogy_stmt_close_send(conn, stmt); + + if (rc == TRILOGY_AGAIN) { + rc = flush_full(conn); + } + + if (rc < 0) { + return rc; + } + + return TRILOGY_OK; +} + #undef CHECKED diff --git a/src/builder.c b/src/builder.c index 44728662..ef5af504 100644 --- a/src/builder.c +++ b/src/builder.c @@ -120,6 +120,48 @@ int trilogy_builder_write_uint64(trilogy_builder_t *builder, uint64_t val) return TRILOGY_OK; } +typedef union { + float f; + uint32_t u; +} trilogy_float_buf_t; + +int trilogy_builder_write_float(trilogy_builder_t *builder, float val) +{ + trilogy_float_buf_t float_val; + + float_val.f = val; + + CHECKED(trilogy_builder_write_uint8(builder, float_val.u & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (float_val.u >> 8) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (float_val.u >> 16) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (float_val.u >> 24) & 0xff)); + + return TRILOGY_OK; +} + +typedef union { + double d; + uint64_t u; +} trilogy_double_buf_t; + +int trilogy_builder_write_double(trilogy_builder_t *builder, double val) +{ + trilogy_double_buf_t double_val; + + double_val.d = val; + + CHECKED(trilogy_builder_write_uint8(builder, double_val.u & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (double_val.u >> 8) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (double_val.u >> 16) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (double_val.u >> 24) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (double_val.u >> 32) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (double_val.u >> 40) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (double_val.u >> 48) & 0xff)); + CHECKED(trilogy_builder_write_uint8(builder, (double_val.u >> 56) & 0xff)); + + return TRILOGY_OK; +} + int trilogy_builder_write_lenenc(trilogy_builder_t *builder, uint64_t val) { if (val < 251) { diff --git a/src/client.c b/src/client.c index d729555e..ab5875aa 100644 --- a/src/client.c +++ b/src/client.c @@ -777,4 +777,182 @@ int trilogy_discard(trilogy_conn_t *conn) return rc; } +int trilogy_stmt_prepare_send(trilogy_conn_t *conn, const char *stmt, size_t stmt_len) +{ + trilogy_builder_t builder; + int err = begin_command_phase(&builder, conn, 0); + if (err < 0) { + return err; + } + + err = trilogy_build_stmt_prepare_packet(&builder, stmt, stmt_len); + if (err < 0) { + return err; + } + + return begin_write(conn); +} + +int trilogy_stmt_prepare_recv(trilogy_conn_t *conn, trilogy_stmt_t *stmt_out) +{ + int err = read_packet(conn); + + if (err < 0) { + return err; + } + + switch (current_packet_type(conn)) { + case TRILOGY_PACKET_OK: { + err = trilogy_parse_stmt_ok_packet(conn->packet_buffer.buff, conn->packet_buffer.len, stmt_out); + + if (err < 0) { + return err; + } + + conn->warning_count = stmt_out->warning_count; + + return TRILOGY_OK; + } + + case TRILOGY_PACKET_ERR: + return read_err_packet(conn); + + default: + return TRILOGY_UNEXPECTED_PACKET; + } +} + +int trilogy_stmt_execute_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint8_t flags, trilogy_binary_value_t *binds) +{ + trilogy_builder_t builder; + int err = begin_command_phase(&builder, conn, 0); + if (err < 0) { + return err; + } + + err = trilogy_build_stmt_execute_packet(&builder, stmt->id, flags, binds, stmt->parameter_count); + + if (err < 0) { + return err; + } + + conn->packet_parser.sequence_number = builder.seq; + + return begin_write(conn); +} + +int trilogy_stmt_execute_recv(trilogy_conn_t *conn, uint64_t *column_count_out) +{ + int err = read_packet(conn); + + if (err < 0) { + return err; + } + + switch (current_packet_type(conn)) { + case TRILOGY_PACKET_OK: + return read_ok_packet(conn); + + case TRILOGY_PACKET_ERR: + return read_err_packet(conn); + + default: { + trilogy_result_packet_t result_packet; + err = trilogy_parse_result_packet(conn->packet_buffer.buff, conn->packet_buffer.len, &result_packet); + + if (err < 0) { + return err; + } + + conn->column_count = result_packet.column_count; + *column_count_out = result_packet.column_count; + + return TRILOGY_OK; + } + } +} + +// FYI: there is no `trilogy_stmt_bind_data_recv` because the server doesn't send a response. +int trilogy_stmt_bind_data_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint16_t param_num, uint8_t *data, + size_t data_len) +{ + trilogy_builder_t builder; + int err = begin_command_phase(&builder, conn, 0); + if (err < 0) { + return err; + } + + err = trilogy_build_stmt_bind_data_packet(&builder, stmt->id, param_num, data, data_len); + + if (err < 0) { + return err; + } + + return begin_write(conn); +} + +int trilogy_stmt_read_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_column_packet_t *columns, + trilogy_binary_value_t *values_out) +{ + int err = read_packet(conn); + + if (err < 0) { + return err; + } + + if (conn->capabilities & TRILOGY_CAPABILITIES_DEPRECATE_EOF && current_packet_type(conn) == TRILOGY_PACKET_EOF) { + if ((err = read_ok_packet(conn)) != TRILOGY_OK) { + return err; + } + + return TRILOGY_EOF; + } else if (current_packet_type(conn) == TRILOGY_PACKET_EOF && conn->packet_buffer.len < 9) { + return read_eof_packet(conn); + } else if (current_packet_type(conn) == TRILOGY_PACKET_ERR) { + return read_err_packet(conn); + } else { + return trilogy_parse_stmt_row_packet(conn->packet_buffer.buff, conn->packet_buffer.len, columns, + stmt->column_count, values_out); + } +} + +int trilogy_stmt_reset_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt) +{ + int err = 0; + + trilogy_builder_t builder; + err = begin_command_phase(&builder, conn, 0); + if (err < 0) { + return err; + } + + err = trilogy_build_stmt_reset_packet(&builder, stmt->id); + if (err < 0) { + return err; + } + + return begin_write(conn); +} + +int trilogy_stmt_reset_recv(trilogy_conn_t *conn) { + return read_generic_response(conn); +} + +int trilogy_stmt_close_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt) +{ + trilogy_builder_t builder; + int err = begin_command_phase(&builder, conn, 0); + if (err < 0) { + return err; + } + + err = trilogy_build_stmt_close_packet(&builder, stmt->id); + + if (err < 0) { + return err; + } + + return begin_write(conn); +} + #undef CHECKED diff --git a/src/protocol.c b/src/protocol.c index 945c75ae..df0482ff 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -12,6 +12,12 @@ #define TRILOGY_CMD_PING 0x0e #define TRILOGY_CMD_SET_OPTION 0x1b +#define TRILOGY_CMD_STMT_PREPARE 0x16 +#define TRILOGY_CMD_STMT_EXECUTE 0x17 +#define TRILOGY_CMD_STMT_SEND_LONG_DATA 0x18 +#define TRILOGY_CMD_STMT_CLOSE 0x19 +#define TRILOGY_CMD_STMT_RESET 0x1a + #define SCRAMBLE_LEN 20 static size_t min(size_t a, size_t b) @@ -395,6 +401,37 @@ int trilogy_parse_column_packet(const uint8_t *buff, size_t len, bool field_list return rc; } +int trilogy_parse_stmt_ok_packet(const uint8_t *buff, size_t len, trilogy_stmt_ok_packet_t *out_packet) +{ + int rc; + + trilogy_reader_t reader = TRILOGY_READER(buff, len); + + // skip packet type + CHECKED(trilogy_reader_get_uint8(&reader, NULL)); + + CHECKED(trilogy_reader_get_uint32(&reader, &out_packet->id)); + + CHECKED(trilogy_reader_get_uint16(&reader, &out_packet->column_count)); + + CHECKED(trilogy_reader_get_uint16(&reader, &out_packet->parameter_count)); + + uint8_t filler; + + CHECKED(trilogy_reader_get_uint8(&reader, &filler)); + + if (filler != 0) { + return TRILOGY_PROTOCOL_VIOLATION; + } + + CHECKED(trilogy_reader_get_uint16(&reader, &out_packet->warning_count)); + + return trilogy_reader_finish(&reader); + +fail: + return rc; +} + static void trilogy_pack_scramble_native_hash(const char *scramble, const char *password, size_t password_len, uint8_t *buffer, unsigned int *buffer_len) { @@ -683,4 +720,454 @@ int trilogy_build_ssl_request_packet(trilogy_builder_t *builder, TRILOGY_CAPABIL return rc; } +int trilogy_build_stmt_prepare_packet(trilogy_builder_t *builder, const char *sql, size_t sql_len) +{ + int rc = TRILOGY_OK; + + CHECKED(trilogy_builder_write_uint8(builder, TRILOGY_CMD_STMT_PREPARE)); + + CHECKED(trilogy_builder_write_buffer(builder, sql, sql_len)); + + trilogy_builder_finalize(builder); + + return TRILOGY_OK; + +fail: + return rc; +} + +int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_id, uint8_t flags, + trilogy_binary_value_t *binds, uint16_t num_binds) +{ + int rc = TRILOGY_OK; + + CHECKED(trilogy_builder_write_uint8(builder, TRILOGY_CMD_STMT_EXECUTE)); + + CHECKED(trilogy_builder_write_uint32(builder, stmt_id)); + + CHECKED(trilogy_builder_write_uint8(builder, flags)); + + // apparently, iteration-count is always 1 + CHECKED(trilogy_builder_write_uint32(builder, 1)); + + int i; + + if (num_binds > 0) { + if (binds == NULL) { + return TRILOGY_PROTOCOL_VIOLATION; + } + + int bind_off = 0; + int bm_bytes = (num_binds + 7) / 8; + + for (i = 0; i < bm_bytes; i++) { + uint8_t nb = 0; + + for (; bind_off < num_binds; bind_off++) { + if (binds[bind_off].is_null) { + nb |= (bind_off % 8); + } + } + + CHECKED(trilogy_builder_write_uint8(builder, nb)); + } + + // new params bound flag + CHECKED(trilogy_builder_write_uint8(builder, 0x1)); + + for (i = 0; i < num_binds; i++) { + CHECKED(trilogy_builder_write_uint8(builder, binds[i].type)); + CHECKED(trilogy_builder_write_uint8(builder, 0x0)); + } + + for (i = 0; i < num_binds; i++) { + trilogy_binary_value_t val = binds[i]; + + switch (val.type) { + case TRILOGY_TYPE_TINY: + CHECKED(trilogy_builder_write_uint8(builder, val.as.uint8)); + + break; + case TRILOGY_TYPE_YEAR: + case TRILOGY_TYPE_SHORT: + CHECKED(trilogy_builder_write_uint16(builder, val.as.uint16)); + + break; + case TRILOGY_TYPE_INT24: + case TRILOGY_TYPE_LONG: + CHECKED(trilogy_builder_write_uint32(builder, val.as.uint32)); + + break; + case TRILOGY_TYPE_LONGLONG: + CHECKED(trilogy_builder_write_uint64(builder, val.as.uint64)); + + break; + case TRILOGY_TYPE_FLOAT: + CHECKED(trilogy_builder_write_float(builder, val.as.flt)); + + break; + case TRILOGY_TYPE_DOUBLE: + CHECKED(trilogy_builder_write_double(builder, val.as.dbl)); + + break; + case TRILOGY_TYPE_TIME: { + uint8_t field_len = 0; + + if (val.as.date.time.micro_seconds) { + field_len = 12; + } else if (val.as.date.time.hour || val.as.date.time.minute || val.as.date.time.second) { + field_len = 8; + } else { + field_len = 0; + } + + CHECKED(trilogy_builder_write_uint8(builder, field_len)); + + if (field_len > 0) { + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.is_negative)); + + CHECKED(trilogy_builder_write_uint32(builder, val.as.date.time.days)); + + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.hour)); + + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.minute)); + + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.second)); + + if (field_len > 8) { + CHECKED(trilogy_builder_write_uint32(builder, val.as.date.time.micro_seconds)); + } + } + + break; + } + case TRILOGY_TYPE_DATE: + case TRILOGY_TYPE_DATETIME: + case TRILOGY_TYPE_TIMESTAMP: { + uint8_t field_len = 0; + + if (val.as.date.time.micro_seconds) { + field_len = 11; + } else if (val.as.date.time.hour || val.as.date.time.minute || val.as.date.time.second) { + field_len = 7; + } else if (val.as.date.year || val.as.date.month || val.as.date.day) { + field_len = 4; + } else { + field_len = 0; + } + + CHECKED(trilogy_builder_write_uint8(builder, field_len)); + + if (field_len > 0) { + CHECKED(trilogy_builder_write_uint16(builder, val.as.date.year)); + + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.month)); + + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.day)); + + if (field_len > 4) { + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.hour)); + + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.minute)); + + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.second)); + + if (field_len > 7) { + CHECKED(trilogy_builder_write_uint32(builder, val.as.date.time.micro_seconds)); + } + } + } + + break; + } + case TRILOGY_TYPE_DECIMAL: + case TRILOGY_TYPE_VARCHAR: + case TRILOGY_TYPE_BIT: + case TRILOGY_TYPE_NEWDECIMAL: + case TRILOGY_TYPE_ENUM: + case TRILOGY_TYPE_SET: + case TRILOGY_TYPE_TINY_BLOB: + case TRILOGY_TYPE_BLOB: + case TRILOGY_TYPE_MEDIUM_BLOB: + case TRILOGY_TYPE_LONG_BLOB: + case TRILOGY_TYPE_VAR_STRING: + case TRILOGY_TYPE_STRING: + case TRILOGY_TYPE_GEOMETRY: + CHECKED(trilogy_builder_write_lenenc_buffer(builder, val.as.str.data, val.as.str.len)); + + break; + case TRILOGY_TYPE_NULL: + // already handled by the null bitmap + break; + default: + return TRILOGY_UNKNOWN_TYPE; + } + } + } + + trilogy_builder_finalize(builder); + + return TRILOGY_OK; + +fail: + return rc; +} + +int trilogy_build_stmt_bind_data_packet(trilogy_builder_t *builder, uint32_t stmt_id, uint16_t param_id, uint8_t *data, + size_t data_len) +{ + int rc = TRILOGY_OK; + + CHECKED(trilogy_builder_write_uint8(builder, TRILOGY_CMD_STMT_SEND_LONG_DATA)); + + CHECKED(trilogy_builder_write_uint32(builder, stmt_id)); + + CHECKED(trilogy_builder_write_uint16(builder, param_id)); + + CHECKED(trilogy_builder_write_buffer(builder, data, data_len)); + + trilogy_builder_finalize(builder); + + return TRILOGY_OK; + +fail: + return rc; +} + +int trilogy_build_stmt_reset_packet(trilogy_builder_t *builder, uint32_t stmt_id) +{ + int rc = TRILOGY_OK; + + CHECKED(trilogy_builder_write_uint8(builder, TRILOGY_CMD_STMT_RESET)); + + CHECKED(trilogy_builder_write_uint32(builder, stmt_id)); + + trilogy_builder_finalize(builder); + + return TRILOGY_OK; + +fail: + return rc; +} + +int trilogy_build_stmt_close_packet(trilogy_builder_t *builder, uint32_t stmt_id) +{ + int rc = TRILOGY_OK; + + CHECKED(trilogy_builder_write_uint8(builder, TRILOGY_CMD_STMT_CLOSE)); + + CHECKED(trilogy_builder_write_uint32(builder, stmt_id)); + + trilogy_builder_finalize(builder); + + return TRILOGY_OK; + +fail: + return rc; +} + +static inline int is_null(uint8_t *null_bitmap, uint64_t bitmap_len, uint64_t column_offset, bool *col_is_null) +{ + if (column_offset > (bitmap_len * 8) - 1) { + return TRILOGY_PROTOCOL_VIOLATION; + } + + column_offset += 2; + + uint64_t byte_offset = column_offset / 8; + + // for the binary protocol result row packet, we need to offset the bit check + // by 2 + *col_is_null = (null_bitmap[byte_offset] & (1 << (column_offset % 8))) != 0; + + return TRILOGY_OK; +} + +int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_column_packet_t *columns, + uint64_t column_count, trilogy_binary_value_t *out_values) +{ + int rc; + + trilogy_reader_t reader = TRILOGY_READER(buff, len); + + // skip packet header + CHECKED(trilogy_reader_get_uint8(&reader, NULL)); + + uint8_t *null_bitmap = NULL; + uint64_t bitmap_len = (column_count + 7 + 2) / 8; + + CHECKED(trilogy_reader_get_buffer(&reader, bitmap_len, (const void **)&null_bitmap)); + + for (uint64_t i = 0; i < column_count; i++) { + CHECKED(is_null(null_bitmap, bitmap_len, i, &out_values[i].is_null)); + if (out_values[i].is_null) { + out_values[i].type = TRILOGY_TYPE_NULL; + } else { + out_values[i].is_null = false; + + out_values[i].type = columns[i].type; + + switch (columns[i].type) { + case TRILOGY_TYPE_STRING: + case TRILOGY_TYPE_VARCHAR: + case TRILOGY_TYPE_VAR_STRING: + case TRILOGY_TYPE_ENUM: + case TRILOGY_TYPE_SET: + case TRILOGY_TYPE_LONG_BLOB: + case TRILOGY_TYPE_MEDIUM_BLOB: + case TRILOGY_TYPE_BLOB: + case TRILOGY_TYPE_TINY_BLOB: + case TRILOGY_TYPE_GEOMETRY: + case TRILOGY_TYPE_BIT: + case TRILOGY_TYPE_DECIMAL: + case TRILOGY_TYPE_NEWDECIMAL: + case TRILOGY_TYPE_JSON: + CHECKED(trilogy_reader_get_lenenc_buffer(&reader, &out_values[i].as.str.len, + (const void **)&out_values[i].as.str.data)); + + break; + case TRILOGY_TYPE_LONGLONG: + CHECKED(trilogy_reader_get_uint64(&reader, &out_values[i].as.uint64)); + + break; + case TRILOGY_TYPE_DOUBLE: + CHECKED(trilogy_reader_get_double(&reader, &out_values[i].as.dbl)); + + break; + case TRILOGY_TYPE_LONG: + case TRILOGY_TYPE_INT24: + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.uint32)); + + break; + case TRILOGY_TYPE_FLOAT: + CHECKED(trilogy_reader_get_float(&reader, &out_values[i].as.flt)); + + break; + case TRILOGY_TYPE_SHORT: + CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.uint16)); + + break; + case TRILOGY_TYPE_YEAR: + CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); + + break; + case TRILOGY_TYPE_TINY: + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.uint8)); + + break; + case TRILOGY_TYPE_DATE: + case TRILOGY_TYPE_DATETIME: + case TRILOGY_TYPE_TIMESTAMP: { + uint8_t time_len; + + CHECKED(trilogy_reader_get_uint8(&reader, &time_len)); + + out_values[i].as.date.year = 0; + out_values[i].as.date.month = 0; + out_values[i].as.date.day = 0; + out_values[i].as.date.time.hour = 0; + out_values[i].as.date.time.minute = 0; + out_values[i].as.date.time.second = 0; + out_values[i].as.date.time.micro_seconds = 0; + + switch (time_len) { + case 0: + break; + case 4: + CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.month)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.day)); + + break; + case 7: + CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.month)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.day)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); + + break; + case 11: + CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.month)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.day)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.micro_seconds)); + + break; + default: + return TRILOGY_PROTOCOL_VIOLATION; + } + + break; + } + case TRILOGY_TYPE_TIME: { + uint8_t time_len; + + CHECKED(trilogy_reader_get_uint8(&reader, &time_len)); + + out_values[i].as.date.time.is_negative = false; + out_values[i].as.date.time.days = 0; + out_values[i].as.date.time.hour = 0; + out_values[i].as.date.time.minute = 0; + out_values[i].as.date.time.second = 0; + out_values[i].as.date.time.micro_seconds = 0; + + switch (time_len) { + case 0: + break; + case 8: { + uint8_t is_negative; + + CHECKED(trilogy_reader_get_uint8(&reader, &is_negative)); + + out_values[i].as.date.time.is_negative = is_negative == 1; + + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.days)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.micro_seconds)); + + break; + } + case 12: { + uint8_t is_negative; + + CHECKED(trilogy_reader_get_uint8(&reader, &is_negative)); + + out_values[i].as.date.time.is_negative = is_negative == 1; + + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.days)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.micro_seconds)); + + break; + } + default: + return TRILOGY_PROTOCOL_VIOLATION; + } + + break; + } + case TRILOGY_TYPE_NULL: + default: + // we cover TRILOGY_TYPE_NULL here because we should never hit this case + // explicitly as it should be covered in the null bitmap + return TRILOGY_UNKNOWN_TYPE; + } + } + } + + return trilogy_reader_finish(&reader); + +fail: + return rc; +} + #undef CHECKED diff --git a/src/reader.c b/src/reader.c index a35b6c91..dcb49250 100644 --- a/src/reader.c +++ b/src/reader.c @@ -95,6 +95,48 @@ int trilogy_reader_get_uint64(trilogy_reader_t *reader, uint64_t *out) return TRILOGY_OK; } +typedef union { + float f; + uint32_t u; +} trilogy_float_buf_t; + +int trilogy_reader_get_float(trilogy_reader_t *reader, float *out) +{ + CHECK(4); + + trilogy_float_buf_t float_val; + + int rc = trilogy_reader_get_uint32(reader, &float_val.u); + if (rc != TRILOGY_OK) { + return rc; + } + + *out = float_val.f; + + return TRILOGY_OK; +} + +typedef union { + double d; + uint64_t u; +} trilogy_double_buf_t; + +int trilogy_reader_get_double(trilogy_reader_t *reader, double *out) +{ + CHECK(8); + + trilogy_double_buf_t double_val; + + int rc = trilogy_reader_get_uint64(reader, &double_val.u); + if (rc != TRILOGY_OK) { + return rc; + } + + *out = double_val.d; + + return TRILOGY_OK; +} + int trilogy_reader_get_lenenc(trilogy_reader_t *reader, uint64_t *out) { CHECK(1); diff --git a/test/client/stmt_close_test.c b/test/client/stmt_close_test.c new file mode 100644 index 00000000..b1ec96cb --- /dev/null +++ b/test/client/stmt_close_test.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include + +#include "../test.h" + +#include "trilogy/blocking.h" +#include "trilogy/client.h" +#include "trilogy/error.h" + +#define do_connect(CONN) \ + do { \ + int err = trilogy_init(CONN); \ + ASSERT_OK(err); \ + err = trilogy_connect(CONN, get_connopt()); \ + ASSERT_OK(err); \ + } while (0) + +TEST test_stmt_close_send() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + err = trilogy_stmt_close_send(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_close_send_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + close_socket(&conn); + + err = trilogy_stmt_close_send(&conn, &stmt); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +int client_stmt_close_test() +{ + RUN_TEST(test_stmt_close_send); + RUN_TEST(test_stmt_close_send_closed_socket); + + return 0; +} diff --git a/test/client/stmt_execute_test.c b/test/client/stmt_execute_test.c new file mode 100644 index 00000000..49d509b5 --- /dev/null +++ b/test/client/stmt_execute_test.c @@ -0,0 +1,376 @@ +#include +#include +#include +#include +#include + +#include "../test.h" + +#include "trilogy/blocking.h" +#include "trilogy/client.h" +#include "trilogy/error.h" + +#define do_connect(CONN) \ + do { \ + int err = trilogy_init(CONN); \ + ASSERT_OK(err); \ + err = trilogy_connect(CONN, get_connopt()); \ + ASSERT_OK(err); \ + } while (0) + +TEST test_stmt_execute_send() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + uint8_t flags = 0x00; + const char str[] = {'t','e','s','t'}; + size_t len = sizeof(str); + trilogy_binary_value_t binds[] = { + {.is_null = false, .type = TRILOGY_TYPE_VAR_STRING, .as.str.data = str, .as.str.len = len}}; + + err = trilogy_stmt_execute_send(&conn, &stmt, flags, binds); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_execute_send_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + close_socket(&conn); + + uint8_t flags = 0x00; + const char str[] = {'t', 'e', 's', 't'}; + size_t len = sizeof(str); + trilogy_binary_value_t binds[] = { + {.is_null = false, .type = TRILOGY_TYPE_VAR_STRING, .as.str.data = str, .as.str.len = len}}; + + err = trilogy_stmt_execute_send(&conn, &stmt, flags, binds); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_execute_recv() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + uint8_t flags = 0x00; + const char str[] = {'t', 'e', 's', 't'}; + size_t len = sizeof(str); + trilogy_binary_value_t binds[] = { + {.is_null = false, .type = TRILOGY_TYPE_VAR_STRING, .as.str.data = str, .as.str.len = len}}; + + err = trilogy_stmt_execute_send(&conn, &stmt, flags, binds); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + uint64_t column_count; + + err = trilogy_stmt_execute_recv(&conn, &column_count); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_execute_recv(&conn, &column_count); + } + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + + for (uint64_t i = 0; i < column_count; i++) { + trilogy_column_packet_t *column = &columns[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + trilogy_binary_value_t values[1]; + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_MEM_EQ(values[0].as.str.data, "test", values[0].as.str.len); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_execute_recv_closed_socket() +{ + + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + free(params); + + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + free(column_defs); + + return err; + } + } + + uint8_t flags = 0x00; + const char str[] = {'t', 'e', 's', 't'}; + size_t len = sizeof(str); + trilogy_binary_value_t binds[] = { + {.is_null = false, .type = TRILOGY_TYPE_VAR_STRING, .as.str.data = str, .as.str.len = len}}; + + err = trilogy_stmt_execute_send(&conn, &stmt, flags, binds); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + close_socket(&conn); + + uint64_t column_count; + + err = trilogy_stmt_execute_recv(&conn, &column_count); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +int client_stmt_execute_test() +{ + RUN_TEST(test_stmt_execute_send); + RUN_TEST(test_stmt_execute_send_closed_socket); + RUN_TEST(test_stmt_execute_recv); + RUN_TEST(test_stmt_execute_recv_closed_socket); + + return 0; +} diff --git a/test/client/stmt_prepare_test.c b/test/client/stmt_prepare_test.c new file mode 100644 index 00000000..b6b0ab3e --- /dev/null +++ b/test/client/stmt_prepare_test.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include + +#include "../test.h" + +#include "trilogy/blocking.h" +#include "trilogy/client.h" +#include "trilogy/error.h" + +#define do_connect(CONN) \ + do { \ + int err = trilogy_init(CONN); \ + ASSERT_OK(err); \ + err = trilogy_connect(CONN, get_connopt()); \ + ASSERT_OK(err); \ + } while (0) + +TEST test_stmt_prepare_send() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_prepare_send_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + close_socket(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_prepare_recv() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_prepare_recv_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + close_socket(&conn); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +int client_stmt_prepare_test() +{ + RUN_TEST(test_stmt_prepare_send); + RUN_TEST(test_stmt_prepare_send_closed_socket); + RUN_TEST(test_stmt_prepare_recv); + RUN_TEST(test_stmt_prepare_recv_closed_socket); + + return 0; +} diff --git a/test/client/stmt_reset_test.c b/test/client/stmt_reset_test.c new file mode 100644 index 00000000..ee80a4d5 --- /dev/null +++ b/test/client/stmt_reset_test.c @@ -0,0 +1,319 @@ +#include +#include +#include +#include +#include + +#include "../test.h" + +#include "trilogy/blocking.h" +#include "trilogy/client.h" +#include "trilogy/error.h" + +#define do_connect(CONN) \ + do { \ + int err = trilogy_init(CONN); \ + ASSERT_OK(err); \ + err = trilogy_connect(CONN, get_connopt()); \ + ASSERT_OK(err); \ + } while (0) + +TEST test_stmt_reset_send() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + err = trilogy_stmt_reset_send(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_reset_send_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + close_socket(&conn); + + err = trilogy_stmt_reset_send(&conn, &stmt); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_reset_recv() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + err = trilogy_stmt_reset_send(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + err = trilogy_stmt_reset_recv(&conn); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_reset_recv(&conn); + } + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_stmt_reset_recv_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_stmt_t stmt; + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_stmt_prepare_recv(&conn, &stmt); + } + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t params[1]; + + for (uint64_t i = 0; i < stmt.parameter_count; i++) { + trilogy_column_packet_t *param = ¶ms[i]; + + err = trilogy_read_full_column(&conn, param); + + if (err < 0) { + return err; + } + } + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_defs[1]; + + for (uint64_t i = 0; i < stmt.column_count; i++) { + trilogy_column_packet_t *column = &column_defs[i]; + + err = trilogy_read_full_column(&conn, column); + + if (err < 0) { + return err; + } + } + + err = trilogy_stmt_reset_send(&conn, &stmt); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + close_socket(&conn); + + err = trilogy_stmt_reset_recv(&conn); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +int client_stmt_reset_test() +{ + RUN_TEST(test_stmt_reset_send); + RUN_TEST(test_stmt_reset_send_closed_socket); + RUN_TEST(test_stmt_reset_recv); + RUN_TEST(test_stmt_reset_recv_closed_socket); + + return 0; +} diff --git a/test/protocol/building/stmt_bind_data_packet_test.c b/test/protocol/building/stmt_bind_data_packet_test.c new file mode 100644 index 00000000..efeff67e --- /dev/null +++ b/test/protocol/building/stmt_bind_data_packet_test.c @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "../../test.h" + +#include "trilogy/error.h" +#include "trilogy/protocol.h" + +TEST test_stmt_bind_data_packet() +{ + trilogy_builder_t builder; + trilogy_buffer_t buff; + + int err = trilogy_buffer_init(&buff, 1); + ASSERT_OK(err); + + err = trilogy_builder_init(&builder, &buff, 0); + ASSERT_OK(err); + + uint32_t stmt_id = 1; + uint32_t param_id = 2; + uint8_t data[] = {'d', 'a', 't', 'a'}; + size_t data_len = sizeof(data); + + err = trilogy_build_stmt_bind_data_packet(&builder, stmt_id, param_id, data, data_len); + ASSERT_OK(err); + + static const uint8_t expected[] = {0x0b, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 'd', 'a', + 't', 'a'}; + + ASSERT_MEM_EQ(buff.buff, expected, buff.len); + + trilogy_buffer_free(&buff); + PASS(); +} + +int stmt_bind_data_packet_test() +{ + RUN_TEST(test_stmt_bind_data_packet); + + return 0; +} diff --git a/test/protocol/building/stmt_close_packet_test.c b/test/protocol/building/stmt_close_packet_test.c new file mode 100644 index 00000000..5d16ee7c --- /dev/null +++ b/test/protocol/building/stmt_close_packet_test.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "../../test.h" + +#include "trilogy/error.h" +#include "trilogy/protocol.h" + +TEST test_stmt_close_packet() +{ + trilogy_builder_t builder; + trilogy_buffer_t buff; + + int err = trilogy_buffer_init(&buff, 1); + ASSERT_OK(err); + + err = trilogy_builder_init(&builder, &buff, 0); + ASSERT_OK(err); + + uint32_t stmt_id = 1; + + err = trilogy_build_stmt_close_packet(&builder, stmt_id); + ASSERT_OK(err); + + static const uint8_t expected[] = {0x05, 0x00, 0x00, 0x00, 0x19, 0x01, 0x00, 0x00, 0x00}; + + ASSERT_MEM_EQ(buff.buff, expected, buff.len); + + trilogy_buffer_free(&buff); + PASS(); +} + +int stmt_close_packet_test() +{ + RUN_TEST(test_stmt_close_packet); + + return 0; +} diff --git a/test/protocol/building/stmt_execute_packet_test.c b/test/protocol/building/stmt_execute_packet_test.c new file mode 100644 index 00000000..7c8dc0d0 --- /dev/null +++ b/test/protocol/building/stmt_execute_packet_test.c @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "../../test.h" + +#include "trilogy/error.h" +#include "trilogy/protocol.h" + +TEST test_stmt_execute_packet() +{ + trilogy_builder_t builder; + trilogy_buffer_t buff; + + int err = trilogy_buffer_init(&buff, 1); + ASSERT_OK(err); + + err = trilogy_builder_init(&builder, &buff, 0); + ASSERT_OK(err); + + uint32_t stmt_id = 1; + uint8_t flags = 1; + trilogy_binary_value_t binds[] = {{.is_null = false, .type = TRILOGY_TYPE_LONG, .as.uint32 = 15}}; + uint16_t num_binds = 1; + + err = trilogy_build_stmt_execute_packet(&builder, stmt_id, flags, binds, num_binds); + ASSERT_OK(err); + + static const uint8_t expected[] = {0x12, 0x00, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00}; + + ASSERT_MEM_EQ(buff.buff, expected, buff.len); + + trilogy_buffer_free(&buff); + PASS(); +} + +int stmt_execute_packet_test() +{ + RUN_TEST(test_stmt_execute_packet); + + return 0; +} diff --git a/test/protocol/building/stmt_prepare_packet_test.c b/test/protocol/building/stmt_prepare_packet_test.c new file mode 100644 index 00000000..1dae628c --- /dev/null +++ b/test/protocol/building/stmt_prepare_packet_test.c @@ -0,0 +1,40 @@ +#include +#include +#include + +#include "../../test.h" + +#include "trilogy/error.h" +#include "trilogy/protocol.h" + +TEST test_stmt_prepare_packet() +{ + trilogy_builder_t builder; + trilogy_buffer_t buff; + + int err = trilogy_buffer_init(&buff, 1); + ASSERT_OK(err); + + err = trilogy_builder_init(&builder, &buff, 0); + ASSERT_OK(err); + + const char *sql = "SELECT ?"; + size_t sql_len = strlen(sql); + + err = trilogy_build_stmt_prepare_packet(&builder, sql, sql_len); + ASSERT_OK(err); + + static const uint8_t expected[] = {0x09, 0x00, 0x00, 0x00, 0x16, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '?'}; + + ASSERT_MEM_EQ(buff.buff, expected, buff.len); + + trilogy_buffer_free(&buff); + PASS(); +} + +int stmt_prepare_packet_test() +{ + RUN_TEST(test_stmt_prepare_packet); + + return 0; +} diff --git a/test/protocol/building/stmt_reset_packet_test.c b/test/protocol/building/stmt_reset_packet_test.c new file mode 100644 index 00000000..58c4fb42 --- /dev/null +++ b/test/protocol/building/stmt_reset_packet_test.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "../../test.h" + +#include "trilogy/error.h" +#include "trilogy/protocol.h" + +TEST test_stmt_reset_packet() +{ + trilogy_builder_t builder; + trilogy_buffer_t buff; + + int err = trilogy_buffer_init(&buff, 1); + ASSERT_OK(err); + + err = trilogy_builder_init(&builder, &buff, 0); + ASSERT_OK(err); + + uint32_t stmt_id = 1; + + err = trilogy_build_stmt_reset_packet(&builder, stmt_id); + ASSERT_OK(err); + + static const uint8_t expected[] = {0x05, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x00}; + + ASSERT_MEM_EQ(buff.buff, expected, buff.len); + + trilogy_buffer_free(&buff); + PASS(); +} + +int stmt_reset_packet_test() +{ + RUN_TEST(test_stmt_reset_packet); + + return 0; +} diff --git a/test/runner.c b/test/runner.c index 6b45baaf..f2400a36 100644 --- a/test/runner.c +++ b/test/runner.c @@ -33,12 +33,21 @@ const trilogy_sockopt_t *get_connopt(void) { return &connopt; } SUITE(build_quit_packet_test) \ SUITE(build_set_option_packet_test) \ SUITE(build_query_packet_test) \ + SUITE(stmt_prepare_packet_test) \ + SUITE(stmt_bind_data_packet_test) \ + SUITE(stmt_execute_packet_test) \ + SUITE(stmt_reset_packet_test) \ + SUITE(stmt_close_packet_test) \ SUITE(client_connect_test) \ SUITE(client_escape_test) \ SUITE(client_auth_test) \ SUITE(client_change_db_test) \ SUITE(client_set_option_test) \ - SUITE(client_ping_test) + SUITE(client_ping_test) \ + SUITE(client_stmt_prepare_test) \ + SUITE(client_stmt_execute_test) \ + SUITE(client_stmt_reset_test) \ + SUITE(client_stmt_close_test) \ #define XX(name) extern int name(); ALL_SUITES(XX) diff --git a/test/test.h b/test/test.h index 5539e13f..0868d539 100644 --- a/test/test.h +++ b/test/test.h @@ -8,6 +8,7 @@ #define ASSERT_ERR(EXP, GOT) ASSERT_ENUM_EQ((EXP), (GOT), trilogy_error) #define ASSERT_OK(GOT) ASSERT_ERR(TRILOGY_OK, (GOT)) +#define ASSERT_EOF(GOT) ASSERT_ERR(TRILOGY_EOF, (GOT)) /* Helpers */ From a827d685e4e12f234fbc55bc96e0bb7739f7bcb1 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 28 Dec 2021 17:18:02 -0800 Subject: [PATCH 02/26] Tweak trilogy_build_stmt_bind_data_packet docs --- inc/trilogy/protocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index ef9fe3a0..43a71ade 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -702,7 +702,7 @@ typedef enum { int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_id, uint8_t flags, trilogy_binary_value_t *binds, uint16_t num_binds); -/* trilogy_build_stmt_bind_data_packet - Build a prepared statement send long data command packet. +/* trilogy_build_stmt_bind_data_packet - Build a prepared statement bind long data command packet. * * builder - A pointer to a pre-initialized trilogy_builder_t. * stmt_id - The statement id for which to build the bind data packet with. From 99ae250c039efbec50c5fa7720134777c8c4bd3e Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 28 Dec 2021 19:58:30 -0800 Subject: [PATCH 03/26] Add docs to prepared statement client C API --- inc/trilogy/client.h | 184 +++++++++++++++++++++++++++++++++++++++++++ src/client.c | 1 - 2 files changed, 184 insertions(+), 1 deletion(-) diff --git a/inc/trilogy/client.h b/inc/trilogy/client.h index 288fb1f1..457669f1 100644 --- a/inc/trilogy/client.h +++ b/inc/trilogy/client.h @@ -601,28 +601,212 @@ void trilogy_free(trilogy_conn_t *conn); */ int trilogy_discard(trilogy_conn_t *conn); +/* trilogy_stmt_prepare_send - Send a prepared statement prepare command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * + * Return values: + * TRILOGY_OK - The quit command was successfully sent to the server. + * TRILOGY_AGAIN - The socket wasn't ready for writing. The caller should wait + * for writeability using `conn->sock`. Then call + * trilogy_flush_writes. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_prepare_send(trilogy_conn_t *conn, const char *stmt, size_t stmt_len); /* trilogy_stmt_t - The trilogy client's prepared statement type. */ typedef trilogy_stmt_ok_packet_t trilogy_stmt_t; +/* trilogy_stmt_prepare_recv - Read the prepared statement prepare command response + * from the MySQL-compatible server. + * + * This should be called after all data written by trilogy_stmt_prepare_send is flushed + * to the network. Calling this at any other time during the connection + * lifecycle is undefined. + * + * Following a successful call to this function, the caller will also need to read off + * `trilogy_stmt_t.column_count` parameters as column packets, then + * `trilogy_stmt_t.column_count` columns as column packets. This must be done before + * the socket will be command-ready again. + * + * conn - A pre-initialized trilogy_conn_t pointer. It can also be connected but + * a disconnected trilogy_conn_t will also return TRILOGY_OK. + * stmt_out - A pointer to a pre-allocated trilogy_stmt_t. + * + * Return values: + * TRILOGY_OK - The prepare command response successfully read from + * the server. + * TRILOGY_AGAIN - The socket wasn't ready for reading. The caller + * should wait for readability using `conn->sock`. + * Then call this function until it returns a + * different value. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network + * packet. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_prepare_recv(trilogy_conn_t *conn, trilogy_stmt_t *stmt_out); +/* trilogy_stmt_bind_data_send - Send a prepared statement bind long data command to the server. + * + * There is no pairing `trilogy_stmt_bind_data_recv` fucntion to this one because the server + * doesn't send a response to this command. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement for which + * to bind the supplied parameter data to. + * param_num - The parameter index for which the supplied data should be bound to. + * data - A pointer to the buffer containing the data to be bound. + * data_len - The length of the data buffer. + * + * Return values: + * TRILOGY_OK - The bind data command was successfully sent to the server. + * TRILOGY_AGAIN - The socket wasn't ready for writing. The caller should wait + * for writeability using `conn->sock`. Then call + * trilogy_flush_writes. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_bind_data_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint16_t param_num, uint8_t *data, size_t data_len); +/* trilogy_stmt_execute_send - Send a prepared statement execute command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to execute. + * flags - The flags (TRILOGY_STMT_FLAGS_t) to be used with this execute command packet. + * binds - Pointer to an array of trilogy_binary_value_t's. The array size should match that + * of `trilogy_stmt_t.column_count`. + * + * Return values: + * TRILOGY_OK - The execute command was successfully sent to the server. + * TRILOGY_AGAIN - The socket wasn't ready for writing. The caller should wait + * for writeability using `conn->sock`. Then call + * trilogy_flush_writes. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_execute_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint8_t flags, trilogy_binary_value_t *binds); +/* trilogy_stmt_execute_recv - Read the prepared statement execute command response + * from the MySQL-compatible server. + * + * This should be called after all data written by trilogy_stmt_execute_send is flushed + * to the network. Calling this at any other time during the connection + * lifecycle is undefined. + * + * conn - A pre-initialized trilogy_conn_t pointer. It can also be connected but + * a disconnected trilogy_conn_t will also return TRILOGY_OK. + * column_count_out - Out parameter; A pointer to a pre-allocated uint64_t. Represents the + * number of columns in the response. + * + * Return values: + * TRILOGY_OK - The prepare command response successfully read from + * the server. + * TRILOGY_AGAIN - The socket wasn't ready for reading. The caller + * should wait for readability using `conn->sock`. + * Then call this function until it returns a + * different value. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network + * packet. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_execute_recv(trilogy_conn_t *conn, uint64_t *column_count_out); +/* trilogy_stmt_read_row - Read a row from the prepared statement execute response. + * + * This should only be called after a sucessful call to trilogy_stmt_execute_recv. + * You should continue calling this until TRILOGY_EOF is returned. Denoting the end + * of the result set. + * + * conn - A pre-initialized trilogy_conn_t pointer. It can also be connected but + * a disconnected trilogy_conn_t will also return TRILOGY_OK. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to execute. + * columns - The list of columns from the prepared statement. + * column_count - The number of columns in prepared statement. + * values_out - Out parameter; A pointer to a pre-allocated array of + * trilogy_binary_value_t's. There must be enough space to fit all of the + * values. This can be computed with: + * `(sizeof(trilogy_binary_value_t) * column_count)`. + * + * Return values: + * TRILOGY_OK - The prepare command response successfully read from + * the server. + * TRILOGY_AGAIN - The socket wasn't ready for reading. The caller + * should wait for readability using `conn->sock`. + * Then call this function until it returns a + * different value. + * TRILOGY_EOF - There are no more rows to read from the result set. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - Invalid length parsed for a TIME/DATETIME/TIMESTAMP value. + * TRILOGY_UNKNOWN_TYPE - An unsupported or unknown MySQL type was seen. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_read_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_column_packet_t *columns, trilogy_binary_value_t *values_out); +/* trilogy_stmt_reset_send - Send a prepared statement reset command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to reset. + * + * Return values: + * TRILOGY_OK - The reset command was successfully sent to the server. + * TRILOGY_AGAIN - The socket wasn't ready for writing. The caller should wait + * for writeability using `conn->sock`. Then call + * trilogy_flush_writes. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_reset_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt); +/* trilogy_stmt_reset_recv - Read the prepared statement reset command response + * from the MySQL-compatible server. + * + * This should be called after all data written by trilogy_stmt_reset_send is flushed + * to the network. Calling this at any other time during the connection + * lifecycle is undefined. + * + * conn - A pre-initialized trilogy_conn_t pointer. It can also be connected but + * a disconnected trilogy_conn_t will also return TRILOGY_OK. + * + * Return values: + * TRILOGY_OK - The reset command response successfully read from + * the server. + * TRILOGY_AGAIN - The socket wasn't ready for reading. The caller + * should wait for readability using `conn->sock`. + * Then call this function until it returns a + * different value. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network + * packet. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_reset_recv(trilogy_conn_t *conn); +/* trilogy_stmt_close_send - Send a prepared statement close command to the server. + * + * There is no pairing `trilogy_stmt_close_recv` fucntion to this one because the server + * doesn't send a response to this command. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to close. + * + * Return values: + * TRILOGY_OK - The close command was successfully sent to the server. + * TRILOGY_AGAIN - The socket wasn't ready for writing. The caller should wait + * for writeability using `conn->sock`. Then call + * trilogy_flush_writes. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_close_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt); #endif diff --git a/src/client.c b/src/client.c index ab5875aa..0d8e496a 100644 --- a/src/client.c +++ b/src/client.c @@ -872,7 +872,6 @@ int trilogy_stmt_execute_recv(trilogy_conn_t *conn, uint64_t *column_count_out) } } -// FYI: there is no `trilogy_stmt_bind_data_recv` because the server doesn't send a response. int trilogy_stmt_bind_data_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint16_t param_num, uint8_t *data, size_t data_len) { From e08c384e7ee6ccca7db57a1e6706c4a15bb4c8fa Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 28 Dec 2021 19:58:52 -0800 Subject: [PATCH 04/26] Small tweaks to the docs for the prepared statement protocol C API --- inc/trilogy/protocol.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 43a71ade..39508d51 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -926,7 +926,7 @@ int trilogy_parse_stmt_ok_packet(const uint8_t *buff, size_t len, trilogy_stmt_o * out_values - Out parameter; A pointer to a pre-allocated array of * trilogy_binary_value_t's. There must be enough space to fit all of the * values. This can be computed with: - * `(sizeof(trilogy_value_t) * column_count)`. + * `(sizeof(trilogy_binary_value_t) * column_count)`. * * Return values: * TRILOGY_OK - The packet was was parsed and the out @@ -934,8 +934,7 @@ int trilogy_parse_stmt_ok_packet(const uint8_t *buff, size_t len, trilogy_stmt_o * TRILOGY_TRUNCATED_PACKET - There isn't enough data in the buffer * to parse the packet. * TRILOGY_PROTOCOL_VIOLATION - Invalid length parsed for a TIME/DATETIME/TIMESTAMP value. - * TRILOGY_UNKNOWN_TYPE - An unsupported or unknown MySQL type was used in the list - * of binds. + * TRILOGY_UNKNOWN_TYPE - An unsupported or unknown MySQL type was seen in the packet. * TRILOGY_EXTRA_DATA_IN_PACKET - There are unparsed bytes left in the * buffer. */ From 824a85b222443562da64ac7f4a1c42f64cf3c8c1 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 29 Dec 2021 09:39:19 -0800 Subject: [PATCH 05/26] Add docs to the prepared statement blocking C API --- inc/trilogy/blocking.h | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/inc/trilogy/blocking.h b/inc/trilogy/blocking.h index 22984138..90e168ee 100644 --- a/inc/trilogy/blocking.h +++ b/inc/trilogy/blocking.h @@ -177,19 +177,122 @@ int trilogy_ping(trilogy_conn_t *conn); */ int trilogy_close(trilogy_conn_t *conn); +/* trilogy_stmt_prepare - Send a prepared statement prepare command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - A pointer to the buffer containing the statement to prepare. + * stmt_len - The length of the data buffer. + * stmt_out - A pointer to a pre-allocated trilogy_stmt_t. + * + * Return values: + * TRILOGY_OK - The prepare command was successfully sent to the server. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network + * packet. + * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. + */ int trilogy_stmt_prepare(trilogy_conn_t *conn, const char *stmt, size_t stmt_len, trilogy_stmt_t *stmt_out); +/* trilogy_stmt_execute - Send a prepared statement execute command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to execute. + * flags - The flags (TRILOGY_STMT_FLAGS_t) to be used with this execute command packet. + * binds - Pointer to an array of trilogy_binary_value_t's. The array size should + * match that of `trilogy_stmt_t.column_count`. + * column_count_out - Out parameter; A pointer to a pre-allocated uint64_t. Represents the + * number of columns in the response. + * + * Return values: + * TRILOGY_OK - The execute command was successfully sent to the server. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network + * packet. + * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. + */ int trilogy_stmt_execute(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint8_t flags, trilogy_binary_value_t *binds, uint64_t *column_count_out); +/* trilogy_stmt_bind_data - Send a prepared statement bind long data command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement for which + * to bind the supplied parameter data to. + * param_num - The parameter index for which the supplied data should be bound to. + * data - A pointer to the buffer containing the data to be bound. + * data_len - The length of the data buffer. + * + * Return values: + * TRILOGY_OK - The bind data command was successfully sent to the server. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_bind_data(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint16_t param_num, uint8_t *data, size_t data_len); +/* trilogy_stmt_read_full_row - Read a row from the prepared statement execute response. + * + * This should only be called after a sucessful call to trilogy_stmt_execute. + * You should continue calling this until TRILOGY_EOF is returned. Denoting the end + * of the result set. + * + * conn - A pre-initialized trilogy_conn_t pointer. It can also be connected but + * a disconnected trilogy_conn_t will also return TRILOGY_OK. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to execute. + * columns - The list of columns from the prepared statement. + * column_count - The number of columns in prepared statement. + * values_out - Out parameter; A pointer to a pre-allocated array of + * trilogy_binary_value_t's. There must be enough space to fit all of the + * values. This can be computed with: + * `(sizeof(trilogy_binary_value_t) * column_count)`. + * + * Return values: + * TRILOGY_OK - The prepare command response successfully read from + * the server. + * TRILOGY_EOF - There are no more rows to read from the result set. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - Invalid length parsed for a TIME/DATETIME/TIMESTAMP value. + * TRILOGY_UNKNOWN_TYPE - An unsupported or unknown MySQL type was seen. + * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. + */ int trilogy_stmt_read_full_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_column_packet_t *columns, trilogy_binary_value_t *values_out); +/* trilogy_stmt_reset - Send a prepared statement reset command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to reset. + * + * Return values: + * TRILOGY_OK - The reset command was successfully sent to the server. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network + * packet. + * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. + */ int trilogy_stmt_reset(trilogy_conn_t *conn, trilogy_stmt_t *stmt); +/* trilogy_stmt_close_send - Send a prepared statement close command to the server. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - Pointer to a valid trilogy_stmt_t, representing the prepared statement you're + * requesting to close. + * + * Return values: + * TRILOGY_OK - The close command was successfully sent to the server. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ int trilogy_stmt_close(trilogy_conn_t *conn, trilogy_stmt_t *stmt); #endif From 652d7e54d9f1f44060d75229e64286b558a125c7 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 29 Dec 2021 09:39:47 -0800 Subject: [PATCH 06/26] More tweaks to prepared statement client C API docs --- inc/trilogy/client.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/inc/trilogy/client.h b/inc/trilogy/client.h index 457669f1..afce6273 100644 --- a/inc/trilogy/client.h +++ b/inc/trilogy/client.h @@ -603,8 +603,10 @@ int trilogy_discard(trilogy_conn_t *conn); /* trilogy_stmt_prepare_send - Send a prepared statement prepare command to the server. * - * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is - * undefined. + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * stmt - A pointer to the buffer containing the statement to prepare. + * stmt_len - The length of the data buffer. * * Return values: * TRILOGY_OK - The quit command was successfully sent to the server. @@ -646,6 +648,7 @@ typedef trilogy_stmt_ok_packet_t trilogy_stmt_t; * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network * packet. * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. */ int trilogy_stmt_prepare_recv(trilogy_conn_t *conn, trilogy_stmt_t *stmt_out); @@ -714,6 +717,7 @@ int trilogy_stmt_execute_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt, uint8_ * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network * packet. * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. */ int trilogy_stmt_execute_recv(trilogy_conn_t *conn, uint64_t *column_count_out); @@ -746,6 +750,7 @@ int trilogy_stmt_execute_recv(trilogy_conn_t *conn, uint64_t *column_count_out); * TRILOGY_PROTOCOL_VIOLATION - Invalid length parsed for a TIME/DATETIME/TIMESTAMP value. * TRILOGY_UNKNOWN_TYPE - An unsupported or unknown MySQL type was seen. * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. */ int trilogy_stmt_read_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_column_packet_t *columns, trilogy_binary_value_t *values_out); @@ -787,6 +792,7 @@ int trilogy_stmt_reset_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt); * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network * packet. * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. */ int trilogy_stmt_reset_recv(trilogy_conn_t *conn); From ec3631c7083f5c57e20e896bbad7966616aaeb1a Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 29 Dec 2021 16:04:38 -0800 Subject: [PATCH 07/26] Fix null bitmap building to allow for more than 8 columns Previously, the code as it was would only write a single byte to the packet buffer. Which can only represent 8 bits, and thus columns. So far as I can tell, MySQL can support up to 4096 columns which would require 512 bytes for the bitmap. This change allows for that by filling out the bitmap and writing it to the packet buffer a byte at a time. A better way to handle this would probably be to ask the buffer code to "reserve" bytes for us in a single allocation. From which we could just fill in the bits along that pre-allocated memory in one shot. But that will require some more thought for the buffer API. So I'll save it for another day :) --- src/protocol.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/protocol.c b/src/protocol.c index df0482ff..07cae267 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -757,19 +757,24 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ return TRILOGY_PROTOCOL_VIOLATION; } - int bind_off = 0; - int bm_bytes = (num_binds + 7) / 8; + uint8_t current_bits = 0; - for (i = 0; i < bm_bytes; i++) { - uint8_t nb = 0; + for (i = 0; i < num_binds; i++) { + if (binds[i].is_null) { + current_bits |= 1 << (i % 8); + } - for (; bind_off < num_binds; bind_off++) { - if (binds[bind_off].is_null) { - nb |= (bind_off % 8); - } + // If we hit a byte boundary, write the bits we have so far and continue + if ((i % 8) == 7) { + CHECKED(trilogy_builder_write_uint8(builder, current_bits)) + + current_bits = 0; } + } - CHECKED(trilogy_builder_write_uint8(builder, nb)); + // If there would have been any remainder bits, finally write those as well + if (num_binds % 8) { + CHECKED(trilogy_builder_write_uint8(builder, current_bits)) } // new params bound flag From 9eed8d263c1a813dc73fe96fbc37d4febc00a0a3 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 4 Jan 2022 07:06:50 -0800 Subject: [PATCH 08/26] Remove some free calls from old code iteration in test --- test/client/stmt_execute_test.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/client/stmt_execute_test.c b/test/client/stmt_execute_test.c index 49d509b5..010287d7 100644 --- a/test/client/stmt_execute_test.c +++ b/test/client/stmt_execute_test.c @@ -317,8 +317,6 @@ TEST test_stmt_execute_recv_closed_socket() err = trilogy_read_full_column(&conn, param); if (err < 0) { - free(params); - return err; } } @@ -333,8 +331,6 @@ TEST test_stmt_execute_recv_closed_socket() err = trilogy_read_full_column(&conn, column); if (err < 0) { - free(column_defs); - return err; } } From 292bfa842c248c5db632c67e855681133a124147 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 4 Jan 2022 10:39:53 -0800 Subject: [PATCH 09/26] Fix Ruby build To be honest, this "fix" feels kinda gross. But I'm happy to discuss other options. Being as though this struct is for internal use, I didn't want to expose it in a header. --- src/builder.c | 15 ++++++++++----- src/reader.c | 15 ++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/builder.c b/src/builder.c index ef5af504..ee4c457f 100644 --- a/src/builder.c +++ b/src/builder.c @@ -120,11 +120,21 @@ int trilogy_builder_write_uint64(trilogy_builder_t *builder, uint64_t val) return TRILOGY_OK; } +#ifndef TRILOGY_FLOAT_BUF +#define TRILOGY_FLOAT_BUF + typedef union { float f; uint32_t u; } trilogy_float_buf_t; +typedef union { + double d; + uint64_t u; +} trilogy_double_buf_t; + +#endif + int trilogy_builder_write_float(trilogy_builder_t *builder, float val) { trilogy_float_buf_t float_val; @@ -139,11 +149,6 @@ int trilogy_builder_write_float(trilogy_builder_t *builder, float val) return TRILOGY_OK; } -typedef union { - double d; - uint64_t u; -} trilogy_double_buf_t; - int trilogy_builder_write_double(trilogy_builder_t *builder, double val) { trilogy_double_buf_t double_val; diff --git a/src/reader.c b/src/reader.c index dcb49250..07c7ddb5 100644 --- a/src/reader.c +++ b/src/reader.c @@ -95,11 +95,21 @@ int trilogy_reader_get_uint64(trilogy_reader_t *reader, uint64_t *out) return TRILOGY_OK; } +#ifndef TRILOGY_FLOAT_BUF +#define TRILOGY_FLOAT_BUF + typedef union { float f; uint32_t u; } trilogy_float_buf_t; +typedef union { + double d; + uint64_t u; +} trilogy_double_buf_t; + +#endif + int trilogy_reader_get_float(trilogy_reader_t *reader, float *out) { CHECK(4); @@ -116,11 +126,6 @@ int trilogy_reader_get_float(trilogy_reader_t *reader, float *out) return TRILOGY_OK; } -typedef union { - double d; - uint64_t u; -} trilogy_double_buf_t; - int trilogy_reader_get_double(trilogy_reader_t *reader, double *out) { CHECK(8); From 06ab59e255271d0a2a4048c0e671ea6af3b48390 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Tue, 4 Jan 2022 11:09:37 -0800 Subject: [PATCH 10/26] Make sure we can represent unsigned trilogy_binary_value_t's This fixes the building and parsing code to check for and set a `is_unsigned` boolean field on trilogy_binary_value_t. This lets the caller specify the parameter value being passed should be treated as unsigned on the server. As well as making sure we tell the caller that a value received from the server is unsigned or not. --- inc/trilogy/protocol.h | 1 + src/protocol.c | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 39508d51..9d94dcd7 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -623,6 +623,7 @@ typedef struct { typedef struct { bool is_null; + bool is_unsigned; TRILOGY_TYPE_t type; diff --git a/src/protocol.c b/src/protocol.c index 07cae267..bc6817cf 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -782,7 +782,12 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ for (i = 0; i < num_binds; i++) { CHECKED(trilogy_builder_write_uint8(builder, binds[i].type)); - CHECKED(trilogy_builder_write_uint8(builder, 0x0)); + + if (binds[i].is_unsigned) { + CHECKED(trilogy_builder_write_uint8(builder, 0x80)); + } else { + CHECKED(trilogy_builder_write_uint8(builder, 0x00)); + } } for (i = 0; i < num_binds; i++) { @@ -1012,6 +1017,10 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum out_values[i].type = columns[i].type; + if (columns[i].flags & TRILOGY_COLUMN_FLAG_UNSIGNED) { + out_values[i].is_unsigned = true; + } + switch (columns[i].type) { case TRILOGY_TYPE_STRING: case TRILOGY_TYPE_VARCHAR: From 4272c008860e3b538df37fb7bb64a1de575f5d68 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 5 Jan 2022 12:03:36 -0800 Subject: [PATCH 11/26] Small code cleanup For consistency, let's initialize this local var in-line. Co-authored-by: Daniel Colson --- src/client.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/client.c b/src/client.c index 0d8e496a..e78abd6e 100644 --- a/src/client.c +++ b/src/client.c @@ -917,10 +917,8 @@ int trilogy_stmt_read_row(trilogy_conn_t *conn, trilogy_stmt_t *stmt, trilogy_co int trilogy_stmt_reset_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt) { - int err = 0; - trilogy_builder_t builder; - err = begin_command_phase(&builder, conn, 0); + int err = begin_command_phase(&builder, conn, 0); if (err < 0) { return err; } From ba303f9a96ab6ab9cc198bee66d96bdf08afbd80 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Fri, 7 Jan 2022 14:29:42 -0800 Subject: [PATCH 12/26] Add some prepared statement blocking API tests --- test/blocking_test.c | 148 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/test/blocking_test.c b/test/blocking_test.c index dea0e525..bb97e0bb 100644 --- a/test/blocking_test.c +++ b/test/blocking_test.c @@ -165,6 +165,150 @@ TEST test_blocking_query_no_rows() PASS(); } +TEST test_blocking_stmt_prepare() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_execute() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + const char str[] = {'t', 'e', 's', 't'}; + size_t len = sizeof(str); + trilogy_binary_value_t binds[] = { + {.is_null = false, .type = TRILOGY_TYPE_VAR_STRING, .as.str.data = str, .as.str.len = len}}; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_MEM_EQ(values[0].as.str.data, "test", values[0].as.str.len); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_reset() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + err = trilogy_stmt_reset(&conn, &stmt); + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_close() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + err = trilogy_stmt_close(&conn, &stmt); + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + int blocking_test() { RUN_TEST(test_blocking_connect); @@ -174,6 +318,10 @@ int blocking_test() RUN_TEST(test_blocking_query); RUN_TEST(test_blocking_query_error); RUN_TEST(test_blocking_query_no_rows); + RUN_TEST(test_blocking_stmt_prepare); + RUN_TEST(test_blocking_stmt_execute); + RUN_TEST(test_blocking_stmt_reset); + RUN_TEST(test_blocking_stmt_close); return 0; } From 3d0bdcd6dbdd49dfd35be6341fab6713ffacb1ac Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 26 Jan 2022 09:13:45 -0800 Subject: [PATCH 13/26] Move time field up a level in trilogy_binary_value_t This saves a little memory for this union. --- inc/trilogy/protocol.h | 13 ++++--- src/protocol.c | 86 +++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 9d94dcd7..cd33023d 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -652,13 +652,14 @@ typedef struct { struct { uint16_t year; uint8_t month, day; - struct { - bool is_negative; - uint32_t days; - uint8_t hour, minute, second; - uint32_t micro_seconds; - } time; } date; + + struct { + bool is_negative; + uint32_t days; + uint8_t hour, minute, second; + uint32_t micro_seconds; + } time; } as; } trilogy_binary_value_t; diff --git a/src/protocol.c b/src/protocol.c index bc6817cf..309c260b 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -823,9 +823,9 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ case TRILOGY_TYPE_TIME: { uint8_t field_len = 0; - if (val.as.date.time.micro_seconds) { + if (val.as.time.micro_seconds) { field_len = 12; - } else if (val.as.date.time.hour || val.as.date.time.minute || val.as.date.time.second) { + } else if (val.as.time.hour || val.as.time.minute || val.as.time.second) { field_len = 8; } else { field_len = 0; @@ -834,18 +834,18 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ CHECKED(trilogy_builder_write_uint8(builder, field_len)); if (field_len > 0) { - CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.is_negative)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.time.is_negative)); - CHECKED(trilogy_builder_write_uint32(builder, val.as.date.time.days)); + CHECKED(trilogy_builder_write_uint32(builder, val.as.time.days)); - CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.hour)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.time.hour)); - CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.minute)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.time.minute)); - CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.second)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.time.second)); if (field_len > 8) { - CHECKED(trilogy_builder_write_uint32(builder, val.as.date.time.micro_seconds)); + CHECKED(trilogy_builder_write_uint32(builder, val.as.time.micro_seconds)); } } @@ -856,9 +856,9 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ case TRILOGY_TYPE_TIMESTAMP: { uint8_t field_len = 0; - if (val.as.date.time.micro_seconds) { + if (val.as.time.micro_seconds) { field_len = 11; - } else if (val.as.date.time.hour || val.as.date.time.minute || val.as.date.time.second) { + } else if (val.as.time.hour || val.as.time.minute || val.as.time.second) { field_len = 7; } else if (val.as.date.year || val.as.date.month || val.as.date.day) { field_len = 4; @@ -876,14 +876,14 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ CHECKED(trilogy_builder_write_uint8(builder, val.as.date.day)); if (field_len > 4) { - CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.hour)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.time.hour)); - CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.minute)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.time.minute)); - CHECKED(trilogy_builder_write_uint8(builder, val.as.date.time.second)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.time.second)); if (field_len > 7) { - CHECKED(trilogy_builder_write_uint32(builder, val.as.date.time.micro_seconds)); + CHECKED(trilogy_builder_write_uint32(builder, val.as.time.micro_seconds)); } } } @@ -1079,10 +1079,10 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum out_values[i].as.date.year = 0; out_values[i].as.date.month = 0; out_values[i].as.date.day = 0; - out_values[i].as.date.time.hour = 0; - out_values[i].as.date.time.minute = 0; - out_values[i].as.date.time.second = 0; - out_values[i].as.date.time.micro_seconds = 0; + out_values[i].as.time.hour = 0; + out_values[i].as.time.minute = 0; + out_values[i].as.time.second = 0; + out_values[i].as.time.micro_seconds = 0; switch (time_len) { case 0: @@ -1097,19 +1097,19 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.month)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.day)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.second)); break; case 11: CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.month)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.day)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); - CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.micro_seconds)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.second)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.time.micro_seconds)); break; default: @@ -1123,12 +1123,12 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum CHECKED(trilogy_reader_get_uint8(&reader, &time_len)); - out_values[i].as.date.time.is_negative = false; - out_values[i].as.date.time.days = 0; - out_values[i].as.date.time.hour = 0; - out_values[i].as.date.time.minute = 0; - out_values[i].as.date.time.second = 0; - out_values[i].as.date.time.micro_seconds = 0; + out_values[i].as.time.is_negative = false; + out_values[i].as.time.days = 0; + out_values[i].as.time.hour = 0; + out_values[i].as.time.minute = 0; + out_values[i].as.time.second = 0; + out_values[i].as.time.micro_seconds = 0; switch (time_len) { case 0: @@ -1138,13 +1138,13 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum CHECKED(trilogy_reader_get_uint8(&reader, &is_negative)); - out_values[i].as.date.time.is_negative = is_negative == 1; + out_values[i].as.time.is_negative = is_negative == 1; - CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.days)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); - CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.micro_seconds)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.time.days)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.second)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.time.micro_seconds)); break; } @@ -1153,13 +1153,13 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum CHECKED(trilogy_reader_get_uint8(&reader, &is_negative)); - out_values[i].as.date.time.is_negative = is_negative == 1; + out_values[i].as.time.is_negative = is_negative == 1; - CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.days)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.hour)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.minute)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.time.second)); - CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.time.micro_seconds)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.time.days)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.second)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.time.micro_seconds)); break; } From a54c5a630d1fddb1888a5106d01649f24d9a5df5 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 26 Jan 2022 09:17:31 -0800 Subject: [PATCH 14/26] Move float/double serialization helpers inline There's no real need to define these as typedefs. --- src/builder.c | 25 ++++++++----------------- src/reader.c | 25 ++++++++----------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/builder.c b/src/builder.c index ee4c457f..6603412f 100644 --- a/src/builder.c +++ b/src/builder.c @@ -120,24 +120,12 @@ int trilogy_builder_write_uint64(trilogy_builder_t *builder, uint64_t val) return TRILOGY_OK; } -#ifndef TRILOGY_FLOAT_BUF -#define TRILOGY_FLOAT_BUF - -typedef union { - float f; - uint32_t u; -} trilogy_float_buf_t; - -typedef union { - double d; - uint64_t u; -} trilogy_double_buf_t; - -#endif - int trilogy_builder_write_float(trilogy_builder_t *builder, float val) { - trilogy_float_buf_t float_val; + union { + float f; + uint32_t u; + } float_val; float_val.f = val; @@ -151,7 +139,10 @@ int trilogy_builder_write_float(trilogy_builder_t *builder, float val) int trilogy_builder_write_double(trilogy_builder_t *builder, double val) { - trilogy_double_buf_t double_val; + union { + double d; + uint64_t u; + } double_val; double_val.d = val; diff --git a/src/reader.c b/src/reader.c index 07c7ddb5..b9c37a17 100644 --- a/src/reader.c +++ b/src/reader.c @@ -95,26 +95,14 @@ int trilogy_reader_get_uint64(trilogy_reader_t *reader, uint64_t *out) return TRILOGY_OK; } -#ifndef TRILOGY_FLOAT_BUF -#define TRILOGY_FLOAT_BUF - -typedef union { - float f; - uint32_t u; -} trilogy_float_buf_t; - -typedef union { - double d; - uint64_t u; -} trilogy_double_buf_t; - -#endif - int trilogy_reader_get_float(trilogy_reader_t *reader, float *out) { CHECK(4); - trilogy_float_buf_t float_val; + union { + float f; + uint32_t u; + } float_val; int rc = trilogy_reader_get_uint32(reader, &float_val.u); if (rc != TRILOGY_OK) { @@ -130,7 +118,10 @@ int trilogy_reader_get_double(trilogy_reader_t *reader, double *out) { CHECK(8); - trilogy_double_buf_t double_val; + union { + double d; + uint64_t u; + } double_val; int rc = trilogy_reader_get_uint64(reader, &double_val.u); if (rc != TRILOGY_OK) { From 7ac41bda36679e517c9d22968802d0f4eeba91f3 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 26 Jan 2022 15:25:05 -0800 Subject: [PATCH 15/26] Document trilogy_binary_value_t some. --- inc/trilogy/protocol.h | 86 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index cd33023d..0926f8f9 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -621,39 +621,125 @@ typedef struct { size_t data_len; } trilogy_value_t; +/* trilogy_binary_value_t - MySQL binary protocol value type + * + * See https://dev.mysql.com/doc/internals/en/binary-protocol-value.html for more detail. + */ typedef struct { + // Flag denoting the value is NULL. bool is_null; + + /* Flag denoting the numeric value is unsigned. + * If this is true, the unsigned numerical value types should be used + * from the `as` union below. + * + * For example, if the value's MySQL type is TRILOGY_TYPE_LONGLONG and + * `is_unsigned` is `true`, the caller should use the `.as.uint64` field + * below to access the properly unsigned value. + */ bool is_unsigned; + // The MySQL column type of this value. TRILOGY_TYPE_t type; + /* This union is used for accessing the underlying binary type for the value. + * Each field member is documented with the MySQL column/value type it maps to. + */ union { + /* MySQL types that use this field: + * + * TRILOGY_TYPE_DOUBLE + */ double dbl; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_LONGLONG + * + * Refer to the `is_unsigned` field above to see which member below should + * be used to access the value. + */ int64_t int64; uint64_t uint64; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_FLOAT + */ float flt; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_LONG + * TRILOGY_TYPE_INT24 + * + * Refer to the `is_unsigned` field above to see which member below should + * be used to access the value. + */ uint32_t uint32; int32_t int32; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_SHORT + * + * Refer to the `is_unsigned` field above to see which member below should + * be used to access the value. + */ uint16_t uint16; int16_t int16; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_TINY + * + * Refer to the `is_unsigned` field above to see which member below should + * be used to access the value. + */ uint8_t uint8; int8_t int8; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_STRING + * TRILOGY_TYPE_VARCHAR + * TRILOGY_TYPE_VAR_STRING + * TRILOGY_TYPE_ENUM + * TRILOGY_TYPE_SET + * TRILOGY_TYPE_LONG_BLOB + * TRILOGY_TYPE_MEDIUM_BLOB + * TRILOGY_TYPE_BLOB + * TRILOGY_TYPE_TINY_BLOB + * TRILOGY_TYPE_GEOMETRY + * TRILOGY_TYPE_BIT + * TRILOGY_TYPE_DECIMAL + * TRILOGY_TYPE_NEWDECIMAL + */ struct { const void *data; size_t len; } str; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_DATE + * TRILOGY_TYPE_DATETIME + * TRILOGY_TYPE_TIMESTAMP + */ struct { + /* MySQL types that use this field: + * + * TRILOGY_TYPE_YEAR + */ uint16_t year; + uint8_t month, day; } date; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_TIME + */ struct { bool is_negative; uint32_t days; From c734cdc23690225ef9fa4bfa723fb8de4d046b87 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Wed, 26 Jan 2022 15:28:47 -0800 Subject: [PATCH 16/26] Add some docs for trilogy_stmt_ok_packet_t as well. --- inc/trilogy/protocol.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 0926f8f9..4cce1de3 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -564,6 +564,10 @@ typedef struct { size_t last_gtid_len; } trilogy_ok_packet_t; +/* trilogy_stmt_ok_packet_t - Represents a MySQL binary protocol prepare response packet. + * + * See https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html for more detail. + */ typedef struct { uint32_t id; uint16_t column_count; From 23e07b80c6af4c7a204d95d1c0b34c2d51bccfa8 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 27 Jan 2022 06:34:21 -0800 Subject: [PATCH 17/26] We still need time fields in the date section of trilogy_binary_value_t Otherwise the values from date and time would clobber each other in the union. --- inc/trilogy/protocol.h | 5 +++++ src/protocol.c | 34 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 4cce1de3..4d4b9d61 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -738,6 +738,11 @@ typedef struct { uint16_t year; uint8_t month, day; + + struct { + uint8_t hour, minute, second; + uint32_t micro_seconds; + } datetime; } date; /* MySQL types that use this field: diff --git a/src/protocol.c b/src/protocol.c index 309c260b..d60d22e8 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -856,9 +856,9 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ case TRILOGY_TYPE_TIMESTAMP: { uint8_t field_len = 0; - if (val.as.time.micro_seconds) { + if (val.as.date.datetime.micro_seconds) { field_len = 11; - } else if (val.as.time.hour || val.as.time.minute || val.as.time.second) { + } else if (val.as.date.datetime.hour || val.as.date.datetime.minute || val.as.date.datetime.second) { field_len = 7; } else if (val.as.date.year || val.as.date.month || val.as.date.day) { field_len = 4; @@ -876,14 +876,14 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ CHECKED(trilogy_builder_write_uint8(builder, val.as.date.day)); if (field_len > 4) { - CHECKED(trilogy_builder_write_uint8(builder, val.as.time.hour)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.datetime.hour)); - CHECKED(trilogy_builder_write_uint8(builder, val.as.time.minute)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.datetime.minute)); - CHECKED(trilogy_builder_write_uint8(builder, val.as.time.second)); + CHECKED(trilogy_builder_write_uint8(builder, val.as.date.datetime.second)); if (field_len > 7) { - CHECKED(trilogy_builder_write_uint32(builder, val.as.time.micro_seconds)); + CHECKED(trilogy_builder_write_uint32(builder, val.as.date.datetime.micro_seconds)); } } } @@ -1079,10 +1079,10 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum out_values[i].as.date.year = 0; out_values[i].as.date.month = 0; out_values[i].as.date.day = 0; - out_values[i].as.time.hour = 0; - out_values[i].as.time.minute = 0; - out_values[i].as.time.second = 0; - out_values[i].as.time.micro_seconds = 0; + out_values[i].as.date.datetime.hour = 0; + out_values[i].as.date.datetime.minute = 0; + out_values[i].as.date.datetime.second = 0; + out_values[i].as.date.datetime.micro_seconds = 0; switch (time_len) { case 0: @@ -1097,19 +1097,19 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.month)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.day)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.hour)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.minute)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.second)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.datetime.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.datetime.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.datetime.second)); break; case 11: CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.month)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.day)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.hour)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.minute)); - CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.second)); - CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.time.micro_seconds)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.datetime.hour)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.datetime.minute)); + CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.date.datetime.second)); + CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.date.datetime.micro_seconds)); break; default: From dfb8f52ef7754a909b0c96c4c670a8c25ea066dc Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 27 Jan 2022 07:17:37 -0800 Subject: [PATCH 18/26] Split out TRILOGY_TYPE_YEAR into it's own field in the union Being as though this is its own column type, it feels cleaner. --- inc/trilogy/protocol.h | 12 ++++++------ src/protocol.c | 7 +++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 4d4b9d61..0b30981b 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -724,6 +724,12 @@ typedef struct { size_t len; } str; + /* MySQL types that use this field: + * + * TRILOGY_TYPE_YEAR + */ + uint16_t year; + /* MySQL types that use this field: * * TRILOGY_TYPE_DATE @@ -731,14 +737,8 @@ typedef struct { * TRILOGY_TYPE_TIMESTAMP */ struct { - /* MySQL types that use this field: - * - * TRILOGY_TYPE_YEAR - */ uint16_t year; - uint8_t month, day; - struct { uint8_t hour, minute, second; uint32_t micro_seconds; diff --git a/src/protocol.c b/src/protocol.c index d60d22e8..0cd05528 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -798,7 +798,6 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ CHECKED(trilogy_builder_write_uint8(builder, val.as.uint8)); break; - case TRILOGY_TYPE_YEAR: case TRILOGY_TYPE_SHORT: CHECKED(trilogy_builder_write_uint16(builder, val.as.uint16)); @@ -819,6 +818,10 @@ int trilogy_build_stmt_execute_packet(trilogy_builder_t *builder, uint32_t stmt_ case TRILOGY_TYPE_DOUBLE: CHECKED(trilogy_builder_write_double(builder, val.as.dbl)); + break; + case TRILOGY_TYPE_YEAR: + CHECKED(trilogy_builder_write_uint16(builder, val.as.year)); + break; case TRILOGY_TYPE_TIME: { uint8_t field_len = 0; @@ -1062,7 +1065,7 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum break; case TRILOGY_TYPE_YEAR: - CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.date.year)); + CHECKED(trilogy_reader_get_uint16(&reader, &out_values[i].as.year)); break; case TRILOGY_TYPE_TINY: From 9844b48e665d8d3ec1c27bb069c95efad4e533bd Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 31 Jan 2022 21:25:29 -0800 Subject: [PATCH 19/26] Add a few more binary protocol tests --- test/blocking_test.c | 185 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 2 deletions(-) diff --git a/test/blocking_test.c b/test/blocking_test.c index bb97e0bb..d5369263 100644 --- a/test/blocking_test.c +++ b/test/blocking_test.c @@ -193,7 +193,7 @@ TEST test_blocking_stmt_prepare() PASS(); } -TEST test_blocking_stmt_execute() +TEST test_blocking_stmt_execute_str() { trilogy_conn_t conn; @@ -247,6 +247,184 @@ TEST test_blocking_stmt_execute() PASS(); } +TEST test_blocking_stmt_execute_integer() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + uint32_t unsigned_val = 1234; + trilogy_binary_value_t binds[] = { + {.is_null = false, .is_unsigned = true, .type = TRILOGY_TYPE_LONG, .as.uint32 = unsigned_val}}; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.uint32, unsigned_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + int32_t signed_val = -1234; + + trilogy_binary_value_t signed_binds[] = { + {.is_null = false, .is_unsigned = false, .type = TRILOGY_TYPE_LONG, .as.int32 = signed_val}}; + + err = trilogy_stmt_execute(&conn, &stmt, flags, signed_binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.int32, signed_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_execute_double() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + double dbl_val = 1234.5; + trilogy_binary_value_t binds[] = { + {.is_null = false, .is_unsigned = true, .type = TRILOGY_TYPE_DOUBLE, .as.dbl = dbl_val}}; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.dbl, dbl_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_execute_datetime() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT CAST('2022-01-31 21:15:45' AS DATETIME)"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(0, stmt.parameter_count); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, NULL, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.date.year, 2022); + ASSERT_EQ(values[0].as.date.month, 1); + ASSERT_EQ(values[0].as.date.day, 31); + ASSERT_EQ(values[0].as.date.datetime.hour, 21); + ASSERT_EQ(values[0].as.date.datetime.minute, 15); + ASSERT_EQ(values[0].as.date.datetime.second, 45); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + TEST test_blocking_stmt_reset() { trilogy_conn_t conn; @@ -319,7 +497,10 @@ int blocking_test() RUN_TEST(test_blocking_query_error); RUN_TEST(test_blocking_query_no_rows); RUN_TEST(test_blocking_stmt_prepare); - RUN_TEST(test_blocking_stmt_execute); + RUN_TEST(test_blocking_stmt_execute_str); + RUN_TEST(test_blocking_stmt_execute_integer); + RUN_TEST(test_blocking_stmt_execute_double); + RUN_TEST(test_blocking_stmt_execute_datetime); RUN_TEST(test_blocking_stmt_reset); RUN_TEST(test_blocking_stmt_close); From 42253e8aacc08cb59e03fffb3e54bf5ec755888c Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Thu, 2 Jun 2022 14:40:36 -0700 Subject: [PATCH 20/26] Remove dev references --- inc/trilogy/protocol.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 0b30981b..57a38a03 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -565,8 +565,6 @@ typedef struct { } trilogy_ok_packet_t; /* trilogy_stmt_ok_packet_t - Represents a MySQL binary protocol prepare response packet. - * - * See https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html for more detail. */ typedef struct { uint32_t id; @@ -627,7 +625,6 @@ typedef struct { /* trilogy_binary_value_t - MySQL binary protocol value type * - * See https://dev.mysql.com/doc/internals/en/binary-protocol-value.html for more detail. */ typedef struct { // Flag denoting the value is NULL. From 4baf042f96d8e79dcc9d4b22c398f0eb3af660b8 Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Tue, 6 Jun 2023 08:21:59 -0400 Subject: [PATCH 21/26] Update README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a7b0a93f..d212124c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ It's currently in production use on github.com. * Password authentication * Query, ping, and quit commands +* Support prepared statements (binary protocol) + * Low-level protocol API completely decoupled from IO * Non-blocking client API wrapping the protocol API From 5a89078f769475351a89c83dc73c1b1a3b9083ce Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Wed, 7 Jun 2023 09:20:03 -0400 Subject: [PATCH 22/26] Test prepared statement support against all potential MySQL types --- test/blocking_test.c | 372 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) diff --git a/test/blocking_test.c b/test/blocking_test.c index d5369263..083a1328 100644 --- a/test/blocking_test.c +++ b/test/blocking_test.c @@ -374,6 +374,278 @@ TEST test_blocking_stmt_execute_double() PASS(); } +TEST test_blocking_stmt_execute_float() { + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + float float_val = 1234.5f; + trilogy_binary_value_t binds[] = { + {.is_null = false, .is_unsigned = true, .type = TRILOGY_TYPE_FLOAT, .as.flt = float_val}}; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.flt, float_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_execute_long() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + uint64_t unsigned_val = 1234; + trilogy_binary_value_t binds[] = { + {.is_null = false, .is_unsigned = true, .type = TRILOGY_TYPE_LONGLONG, .as.uint64 = unsigned_val}}; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.uint64, unsigned_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + int64_t signed_val = -1234; + + trilogy_binary_value_t signed_binds[] = { + {.is_null = false, .is_unsigned = false, .type = TRILOGY_TYPE_LONGLONG, .as.int64 = signed_val}}; + + err = trilogy_stmt_execute(&conn, &stmt, flags, signed_binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.int64, signed_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_execute_short() { + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + uint16_t unsigned_val = 1234; + trilogy_binary_value_t binds[] = { + {.is_null = false, .is_unsigned = true, .type = TRILOGY_TYPE_SHORT, .as.uint16 = unsigned_val}}; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.uint16, unsigned_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + int16_t signed_val = -1234; + + trilogy_binary_value_t signed_binds[] = { + {.is_null = false, .is_unsigned = false, .type = TRILOGY_TYPE_SHORT, .as.int16 = signed_val}}; + + err = trilogy_stmt_execute(&conn, &stmt, flags, signed_binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.int16, signed_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_execute_tiny() { + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT ?"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.parameter_count); + + trilogy_column_packet_t param; + err = trilogy_read_full_column(&conn, ¶m); + ASSERT_OK(err); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + uint8_t unsigned_val = 123; + trilogy_binary_value_t binds[] = { + {.is_null = false, .is_unsigned = true, .type = TRILOGY_TYPE_TINY, .as.uint8 = unsigned_val}}; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.uint8, unsigned_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + int8_t signed_val = -123; + + trilogy_binary_value_t signed_binds[] = { + {.is_null = false, .is_unsigned = false, .type = TRILOGY_TYPE_TINY, .as.int8 = signed_val}}; + + err = trilogy_stmt_execute(&conn, &stmt, flags, signed_binds, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.int8, signed_val); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + TEST test_blocking_stmt_execute_datetime() { trilogy_conn_t conn; @@ -425,6 +697,100 @@ TEST test_blocking_stmt_execute_datetime() PASS(); } +TEST test_blocking_stmt_execute_time() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT CAST('21:15:45' AS TIME)"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(0, stmt.parameter_count); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, NULL, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.time.hour, 21); + ASSERT_EQ(values[0].as.time.minute, 15); + ASSERT_EQ(values[0].as.time.second, 45); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_blocking_stmt_execute_year() +{ + trilogy_conn_t conn; + + connect_conn(&conn); + + const char *query = "SELECT CAST('2023' AS YEAR)"; + trilogy_stmt_t stmt; + + int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); + ASSERT_OK(err); + + ASSERT_EQ(0, stmt.parameter_count); + + ASSERT_EQ(1, stmt.column_count); + + trilogy_column_packet_t column_def; + err = trilogy_read_full_column(&conn, &column_def); + ASSERT_OK(err); + + uint8_t flags = 0x00; + + uint64_t column_count; + + err = trilogy_stmt_execute(&conn, &stmt, flags, NULL, &column_count); + ASSERT_OK(err); + + ASSERT_EQ(1, column_count); + + trilogy_column_packet_t columns[1]; + err = trilogy_read_full_column(&conn, &columns[0]); + ASSERT_OK(err); + + trilogy_binary_value_t values[1]; + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_OK(err); + + ASSERT_EQ(values[0].as.year, 2023); + + err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); + ASSERT_EOF(err); + + trilogy_free(&conn); + PASS(); +} + TEST test_blocking_stmt_reset() { trilogy_conn_t conn; @@ -500,7 +866,13 @@ int blocking_test() RUN_TEST(test_blocking_stmt_execute_str); RUN_TEST(test_blocking_stmt_execute_integer); RUN_TEST(test_blocking_stmt_execute_double); + RUN_TEST(test_blocking_stmt_execute_float); + RUN_TEST(test_blocking_stmt_execute_long); + RUN_TEST(test_blocking_stmt_execute_short); + RUN_TEST(test_blocking_stmt_execute_tiny); RUN_TEST(test_blocking_stmt_execute_datetime); + RUN_TEST(test_blocking_stmt_execute_time); + RUN_TEST(test_blocking_stmt_execute_year); RUN_TEST(test_blocking_stmt_reset); RUN_TEST(test_blocking_stmt_close); From 80d608e66c6a5e7a96346280c68591c1cc4efdf9 Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Wed, 7 Jun 2023 12:07:36 -0400 Subject: [PATCH 23/26] Bugfix: parsing TRILOGY_TYPE_TIME packet should not try to read microseconds when len is 8 bytes --- src/protocol.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/protocol.c b/src/protocol.c index 0cd05528..232bb97f 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -1147,7 +1147,6 @@ int trilogy_parse_stmt_row_packet(const uint8_t *buff, size_t len, trilogy_colum CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.hour)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.minute)); CHECKED(trilogy_reader_get_uint8(&reader, &out_values[i].as.time.second)); - CHECKED(trilogy_reader_get_uint32(&reader, &out_values[i].as.time.micro_seconds)); break; } From 2bacaa2dc2d837256358f985fc862ff98219c92e Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Thu, 8 Jun 2023 11:48:41 -0400 Subject: [PATCH 24/26] Try year test with YEAR() instead of CAST() --- test/blocking_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/blocking_test.c b/test/blocking_test.c index 083a1328..51c287b0 100644 --- a/test/blocking_test.c +++ b/test/blocking_test.c @@ -751,7 +751,7 @@ TEST test_blocking_stmt_execute_year() connect_conn(&conn); - const char *query = "SELECT CAST('2023' AS YEAR)"; + const char *query = "SELECT YEAR('2022-01-31')"; trilogy_stmt_t stmt; int err = trilogy_stmt_prepare(&conn, query, strlen(query), &stmt); @@ -782,7 +782,7 @@ TEST test_blocking_stmt_execute_year() err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); ASSERT_OK(err); - ASSERT_EQ(values[0].as.year, 2023); + ASSERT_EQ(values[0].as.year, 2022); err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); ASSERT_EOF(err); From 5ccb8ea10808fcbf10675d88508fcc1514451f8b Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Thu, 8 Jun 2023 11:59:44 -0400 Subject: [PATCH 25/26] Document new builder methods --- inc/trilogy/builder.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/inc/trilogy/builder.h b/inc/trilogy/builder.h index a43cd5e6..9879fdef 100644 --- a/inc/trilogy/builder.h +++ b/inc/trilogy/builder.h @@ -117,8 +117,30 @@ int trilogy_builder_write_uint32(trilogy_builder_t *builder, uint32_t val); */ int trilogy_builder_write_uint64(trilogy_builder_t *builder, uint64_t val); +/* trilogy_builder_write_float - Append a float to the packet buffer. + * + * builder - A pre-initialized trilogy_builder_t pointer + * val - The value to append to the buffer + * + * Return values: + * TRILOGY_OK - The value was appended to the packet buffer. + * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum + * packet size. + */ int trilogy_builder_write_float(trilogy_builder_t *builder, float val); +/* trilogy_builder_write_double - Append a double to the packet buffer. + * + * builder - A pre-initialized trilogy_builder_t pointer + * val - The value to append to the buffer + * + * Return values: + * TRILOGY_OK - The value was appended to the packet buffer. + * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum + * packet size. + */ int trilogy_builder_write_double(trilogy_builder_t *builder, double val); /* trilogy_builder_write_lenenc - Append a length-encoded integer to the packet From 7fe04a499384c1599c68a7899b0d82f78df4bc6a Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Thu, 8 Jun 2023 12:25:20 -0400 Subject: [PATCH 26/26] Handle prepared statements with floats The value returned from the server differs between MySQL 5.7 and MySQL 8. With MySQL 5.7 we get back a float, with MySQL 8 we get back a double. --- test/blocking_test.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/blocking_test.c b/test/blocking_test.c index 51c287b0..cd37a1c3 100644 --- a/test/blocking_test.c +++ b/test/blocking_test.c @@ -417,7 +417,13 @@ TEST test_blocking_stmt_execute_float() { err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); ASSERT_OK(err); - ASSERT_EQ(values[0].as.flt, float_val); + // With MySQL 5.7, the value is returned from the server as a float. + // With MySQL 8, the value is returned from the server as a double. + if (columns[0].type == TRILOGY_TYPE_FLOAT) { + ASSERT_EQ(values[0].as.flt, float_val); + } else if (columns[0].type == TRILOGY_TYPE_DOUBLE) { + ASSERT_EQ(values[0].as.dbl, float_val); + } err = trilogy_stmt_read_full_row(&conn, &stmt, columns, values); ASSERT_EOF(err);