MXS-1203: Improve resultset processing functions

The functions used to track the resultset EOF packets now expose the
position of the end of the result set. This allows the modules that use
them to check if more results exist in the same buffer.

Added the status bits for OK and EOF packets to the mysql.h protocol
header. This can be used to check for various state changes that happen in
the session. Currently the status bits are only used to detect if more
results are expected.
This commit is contained in:
Markus Mäkelä 2017-04-03 14:40:06 +03:00
parent a1c7ee438d
commit d7258fffd0
5 changed files with 131 additions and 75 deletions

View File

@ -30,8 +30,6 @@ MXS_BEGIN_DECLS
#define PTR_IS_ERR(b) (b[4] == 0xff)
#define PTR_IS_LOCAL_INFILE(b) (b[4] == 0xfb)
#define IS_FULL_RESPONSE(buf) (modutil_count_signal_packets(buf,0,0) == 2)
#define PTR_EOF_MORE_RESULTS(b) ((PTR_IS_EOF(b) && ptr[7] & 0x08))
extern int modutil_is_SQL(GWBUF *);
extern int modutil_is_SQL_prepare(GWBUF *);
@ -54,7 +52,23 @@ GWBUF* modutil_create_mysql_err_msg(int packet_number,
const char *statemsg,
const char *msg);
int modutil_count_signal_packets(GWBUF*, int, int, int*);
/**
* @brief Count the number of EOF and ERR packets in the buffer.
*
* Only complete packets are inspected and the buffer is assumed to only contain
* whole packets. If partial packets are in the buffer, they are ignored.
* The caller must handle the detection of partial packets in buffers.
*
* @param reply Buffer to use
* @param n_found Number of previous found packets
* @param more Set to true of more results exist
* @param offset_out Initial offset into the buffer. This offset is set to point
* to the first byte after the last packet in the buffer.
*
* @return Total number of EOF and ERR packets including the ones already found
*/
int modutil_count_signal_packets(GWBUF *reply, int n_found, bool* more, size_t* offset_out);
mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string);
/**

View File

@ -114,6 +114,22 @@ MXS_BEGIN_DECLS
#define MYSQL_DATABASE_MAXLEN 128
#define MYSQL_TABLE_MAXLEN 64
/** Response packet status bits for OK and EOF packets */
#define MXS_MYSQL_STATUS_IN_TRANS 0x0001
#define MXS_MYSQL_STATUS_AUTOCOMMIT 0x0002
#define MXS_MYSQL_MORE_RESULTS_EXISTS 0x0008
#define MXS_MYSQL_STATUS_NO_GOOD_INDEX_USED 0x0010
#define MXS_MYSQL_STATUS_NO_INDEX_USED 0x0020
#define MXS_MYSQL_STATUS_CURSOR_EXISTS 0x0040
#define MXS_MYSQL_STATUS_LAST_ROW_SENT 0x0080
#define MXS_MYSQL_STATUS_DB_DROPPED 0x0100
#define MXS_MYSQL_STATUS_NO_BACKSLASH_ESCAPES 0x0200
#define MXS_MYSQL_STATUS_METADATA_CHANGED 0x0400
#define MXS_MYSQL_QUERY_WAS_SLOW 0x0800
#define MXS_MYSQL_PS_OUT_PARAMS 0x1000
#define MXS_MYSQL_STATUS_IN_TRANS_READONLY 0x2000
#define MXS_MYSQL_SESSION_STATE_CHANGED 0x4000
#define GW_NOINTR_CALL(A) do { errno = 0; A; } while (errno == EINTR)
#define SMALL_CHUNK 1024
#define MAX_CHUNK SMALL_CHUNK * 8 * 4
@ -427,8 +443,25 @@ int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char*
/** Check for OK packet */
bool mxs_mysql_is_ok_packet(GWBUF *buffer);
/** Check for result set */
bool mxs_mysql_is_result_set(GWBUF *buffer);
/**
* @brief Check if a buffer contains a result set
*
* @param buffer Buffer to check
* @param offset Offset into the buffer
*
* @return True if the @c buffer at @c offset contains the start of a result set
*/
bool mxs_mysql_is_result_set(GWBUF *buffer, size_t offset);
/**
* @brief Check if the OK packet is followed by another result
*
* @param buffer Buffer to check
* @param offset Offset into the buffer
*
* @return True if more results are expected
*/
bool mxs_mysql_more_results_after_ok(GWBUF *buffer, size_t extra_offset);
/** Get current command for a session */
mysql_server_cmd_t mxs_mysql_current_command(MXS_SESSION* session);

View File

@ -633,83 +633,45 @@ GWBUF* modutil_get_complete_packets(GWBUF **p_readbuf)
return complete;
}
/**
* Count the number of EOF, OK or ERR packets in the buffer. Only complete
* packets are inspected and the buffer is assumed to only contain whole packets.
* If partial packets are in the buffer, they are ignored. The caller must handle the
* detection of partial packets in buffers.
* @param reply Buffer to use
* @param use_ok Whether the DEPRECATE_EOF flag is set
* @param n_found If there were previous packets found
* @return Number of EOF packets
*/
int
modutil_count_signal_packets(GWBUF *reply, int use_ok, int n_found, int* more)
int modutil_count_signal_packets(GWBUF *reply, int n_found, bool* more, size_t* offset)
{
unsigned char* ptr = (unsigned char*) reply->start;
unsigned char* end = (unsigned char*) reply->end;
unsigned char* prev = ptr;
int pktlen, eof = 0, err = 0;
int errlen = 0, eoflen = 0;
int iserr = 0, iseof = 0;
bool moreresults = false;
while (ptr < end)
{
pktlen = MYSQL_GET_PAYLOAD_LEN(ptr) + 4;
unsigned int len = gwbuf_length(reply);
int eof = 0;
int err = 0;
if ((iserr = PTR_IS_ERR(ptr)) || (iseof = PTR_IS_EOF(ptr)))
while (*offset < len)
{
uint8_t header[MYSQL_HEADER_LEN + 5]; // Maximum size of an EOF packet
gwbuf_copy_data(reply, *offset, MYSQL_HEADER_LEN + 1, header);
unsigned int pktlen = MYSQL_GET_PAYLOAD_LEN(header) + MYSQL_HEADER_LEN;
if (MYSQL_GET_COMMAND(header) == MYSQL_REPLY_ERR)
{
if (iserr)
{
err++;
errlen = pktlen;
}
else if (iseof)
{
eof++;
eoflen = pktlen;
}
err++;
}
else if (MYSQL_GET_COMMAND(header) == MYSQL_REPLY_EOF &&
pktlen == 5 + MYSQL_HEADER_LEN)
{
eof++;
}
if ((ptr + pktlen) > end || (eof + n_found) >= 2)
if (*offset + pktlen >= len || (eof + err + n_found) >= 2)
{
moreresults = PTR_EOF_MORE_RESULTS(ptr);
ptr = prev;
gwbuf_copy_data(reply, *offset, sizeof(header), header);
uint16_t* status = (uint16_t*)(header + MYSQL_HEADER_LEN + 1 + 2); // Skip command and warning count
*more = ((*status) & MXS_MYSQL_MORE_RESULTS_EXISTS);
*offset += pktlen;
break;
}
prev = ptr;
ptr += pktlen;
*offset += pktlen;
}
int total = err + eof + n_found;
/*
* If there were new EOF/ERR packets found, make sure that they are the last
* packet in the buffer.
*/
if ((eof || err) && n_found)
{
if (err)
{
ptr -= errlen;
if (!PTR_IS_ERR(ptr))
{
err = 0;
}
}
else
{
ptr -= eoflen;
if (!PTR_IS_EOF(ptr))
{
eof = 0;
}
}
}
*more = moreresults;
return (eof + err);
return total;
}
/**

View File

@ -700,8 +700,9 @@ gw_read_and_write(DCB *dcb)
expecting_resultset(proto) &&
mxs_mysql_is_result_set(read_buffer))
{
int more = 0;
if (modutil_count_signal_packets(read_buffer, 0, 0, &more) != 2)
bool more = false;
size_t offset = 0;
if (modutil_count_signal_packets(read_buffer, 0, &more, &offset) != 2)
{
dcb->dcb_readqueue = read_buffer;
return 0;

View File

@ -48,6 +48,7 @@
#include <maxscale/log_manager.h>
#include <netinet/tcp.h>
#include <maxscale/modutil.h>
#include <maxscale/mysql_utils.h>
uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN] = "";
@ -966,7 +967,7 @@ char *create_auth_fail_str(char *username,
if (db_len > 0)
{
sprintf(errstr, ferrstr, username, hostaddr, password ? "YES": "NO", db);
sprintf(errstr, ferrstr, username, hostaddr, password ? "YES" : "NO", db);
}
else if (errcode == MXS_AUTH_FAILED_SSL)
{
@ -1537,12 +1538,12 @@ bool mxs_mysql_is_ok_packet(GWBUF *buffer)
return rval;
}
bool mxs_mysql_is_result_set(GWBUF *buffer)
bool mxs_mysql_is_result_set(GWBUF *buffer, size_t offset)
{
bool rval = false;
uint8_t cmd;
if (gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, 1, &cmd))
if (gwbuf_copy_data(buffer, offset + MYSQL_HEADER_LEN, 1, &cmd))
{
switch (cmd)
{
@ -1563,6 +1564,51 @@ bool mxs_mysql_is_result_set(GWBUF *buffer)
return rval;
}
bool mxs_mysql_more_results_after_ok(GWBUF *buffer, size_t extra_offset)
{
bool rval = false;
size_t buflen = gwbuf_length(buffer);
size_t offset = extra_offset;
while (offset < buflen)
{
// Copy the header
uint8_t header[MYSQL_HEADER_LEN + 1];
if (gwbuf_copy_data(buffer, offset, sizeof(header), header) != sizeof(header))
{
break;
}
size_t len = gw_mysql_get_byte3(header);
if (header[4] == MYSQL_REPLY_OK)
{
// Copy the payload without the command byte
uint8_t data[len - 1];
gwbuf_copy_data(buffer, offset + MYSQL_HEADER_LEN + 1, sizeof(data), data);
uint8_t* ptr = data;
ptr += mxs_leint_bytes(ptr);
ptr += mxs_leint_bytes(ptr);
uint16_t* status = (uint16_t*)ptr;
rval = (*status) & MXS_MYSQL_MORE_RESULTS_EXISTS;
}
else
{
break;
}
offset += len + MYSQL_HEADER_LEN;
if (offset < buflen)
{
MXS_DEBUG("More data after an OK packet, expecting results: %s", rval ? "YES" : "NO");
}
}
return rval;
}
mysql_server_cmd_t mxs_mysql_current_command(MXS_SESSION* session)
{
MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol;