MaxScale/server/core/mysql_utils.cc
Esa Korhonen b4d91d4b9a Move query result helper class to maxsql
Added some asserts to ensure the class is used correctly.
2019-01-14 10:43:17 +02:00

383 lines
9.1 KiB
C++

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2022-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
/**
* @file mysql_utils.c - Binary MySQL data processing utilities
*
* This file contains functions that are used when processing binary format
* information. The MySQL protocol uses the binary format in result sets and
* row based replication.
*/
#include <maxscale/mysql_utils.hh>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <errmsg.h>
#include <maxbase/atomic.hh>
#include <maxbase/format.hh>
#include <maxsql/mariadb.hh>
#include <maxscale/alloc.h>
#include <maxscale/config.hh>
/**
* @brief Calculate the length of a length-encoded integer in bytes
*
* @param ptr Start of the length encoded value
* @return Number of bytes before the actual value
*/
size_t mxs_leint_bytes(const uint8_t* ptr)
{
uint8_t val = *ptr;
if (val < 0xfb)
{
return 1;
}
else if (val == 0xfc)
{
return 3;
}
else if (val == 0xfd)
{
return 4;
}
else
{
return 9;
}
}
/**
* @brief Converts a length-encoded integer to @c uint64_t
*
* @see https://dev.mysql.com/doc/internals/en/integer.html
* @param c Pointer to the first byte of a length-encoded integer
* @return The value converted to a standard unsigned integer
*/
uint64_t mxs_leint_value(const uint8_t* c)
{
uint64_t sz = 0;
if (*c < 0xfb)
{
sz = *c;
}
else if (*c == 0xfc)
{
memcpy(&sz, c + 1, 2);
}
else if (*c == 0xfd)
{
memcpy(&sz, c + 1, 3);
}
else if (*c == 0xfe)
{
memcpy(&sz, c + 1, 8);
}
else
{
mxb_assert(*c == 0xff);
MXS_ERROR("Unexpected length encoding '%x' encountered when reading "
"length-encoded integer.",
*c);
}
return sz;
}
/**
* Converts a length-encoded integer into a standard unsigned integer
* and advances the pointer to the next unrelated byte.
*
* @param c Pointer to the first byte of a length-encoded integer
*/
uint64_t mxs_leint_consume(uint8_t** c)
{
uint64_t rval = mxs_leint_value(*c);
*c += mxs_leint_bytes(*c);
return rval;
}
/**
* @brief Consume and duplicate a length-encoded string
*
* Converts a length-encoded string to a C string and advances the pointer to
* the first byte after the string. The caller is responsible for freeing
* the returned string.
* @param c Pointer to the first byte of a valid packet.
* @return The newly allocated string or NULL if memory allocation failed
*/
char* mxs_lestr_consume_dup(uint8_t** c)
{
uint64_t slen = mxs_leint_consume(c);
char* str = (char*)MXS_MALLOC((slen + 1) * sizeof(char));
if (str)
{
memcpy(str, *c, slen);
str[slen] = '\0';
*c += slen;
}
return str;
}
/**
* @brief Consume a length-encoded string
*
* Converts length-encoded strings to character strings and advanced
* the pointer to the next unrelated byte.
* @param c Pointer to the start of the length-encoded string
* @param size Pointer to a variable where the size of the string is stored
* @return Pointer to the start of the string
*/
char* mxs_lestr_consume(uint8_t** c, size_t* size)
{
uint64_t slen = mxs_leint_consume(c);
*size = slen;
char* start = (char*) *c;
*c += slen;
return start;
}
MYSQL* mxs_mysql_real_connect(MYSQL* con, SERVER* server, const char* user, const char* passwd)
{
SSL_LISTENER* listener = server->server_ssl;
if (listener)
{
mysql_ssl_set(con, listener->ssl_key, listener->ssl_cert, listener->ssl_ca_cert, NULL, NULL);
}
char yes = 1;
mysql_optionsv(con, MYSQL_OPT_RECONNECT, &yes);
mysql_optionsv(con, MYSQL_INIT_COMMAND, "SET SQL_MODE=''");
MXS_CONFIG* config = config_get_global_options();
if (config->local_address)
{
if (mysql_optionsv(con, MYSQL_OPT_BIND, config->local_address) != 0)
{
MXS_ERROR("'local_address' specified in configuration file, but could not "
"configure MYSQL handle. MaxScale will try to connect using default "
"address.");
}
}
MYSQL* mysql = mysql_real_connect(con, server->address, user, passwd, NULL, server->port, NULL, 0);
auto extra_port = mxb::atomic::load(&server->extra_port, mxb::atomic::RELAXED);
if (!mysql && extra_port > 0)
{
mysql = mysql_real_connect(con, server->address, user, passwd, NULL, extra_port, NULL, 0);
MXS_WARNING("Could not connect with normal port to server '%s', using extra_port", server->name());
}
if (mysql)
{
/** Copy the server charset */
MY_CHARSET_INFO cs_info;
mysql_get_character_set_info(mysql, &cs_info);
server->charset = cs_info.number;
if (listener && mysql_get_ssl_cipher(con) == NULL)
{
if (server->warn_ssl_not_enabled)
{
server->warn_ssl_not_enabled = false;
MXS_ERROR("An encrypted connection to '%s' could not be created, "
"ensure that TLS is enabled on the target server.",
server->name());
}
// Don't close the connection as it is closed elsewhere, just set to NULL
mysql = NULL;
}
}
return mysql;
}
int mxs_mysql_query(MYSQL* conn, const char* query)
{
MXS_CONFIG* cnf = config_get_global_options();
return maxsql::mysql_query_ex(conn, query, cnf->query_retries, cnf->query_retry_timeout);
}
const char* mxs_mysql_get_value(MYSQL_RES* result, MYSQL_ROW row, const char* key)
{
MYSQL_FIELD* f = mysql_fetch_fields(result);
int nfields = mysql_num_fields(result);
for (int i = 0; i < nfields; i++)
{
if (strcasecmp(f[i].name, key) == 0)
{
return row[i];
}
}
return NULL;
}
bool mxs_mysql_trim_quotes(char* s)
{
bool dequoted = true;
char* i = s;
char* end = s + strlen(s);
// Remove space from the beginning
while (*i && isspace(*i))
{
++i;
}
if (*i)
{
// Remove space from the end
while (isspace(*(end - 1)))
{
*(end - 1) = 0;
--end;
}
mxb_assert(end > i);
char quote;
switch (*i)
{
case '\'':
case '"':
case '`':
quote = *i;
++i;
break;
default:
quote = 0;
}
if (quote)
{
--end;
if (*end == quote)
{
*end = 0;
memmove(s, i, end - i + 1);
}
else
{
dequoted = false;
}
}
else if (i != s)
{
memmove(s, i, end - i + 1);
}
}
else
{
*s = 0;
}
return dequoted;
}
mxs_mysql_name_kind_t mxs_mysql_name_to_pcre(char* pcre,
const char* mysql,
mxs_pcre_quote_approach_t approach)
{
mxs_mysql_name_kind_t rv = MXS_MYSQL_NAME_WITHOUT_WILDCARD;
while (*mysql)
{
switch (*mysql)
{
case '%':
if (approach == MXS_PCRE_QUOTE_WILDCARD)
{
*pcre = '.';
pcre++;
*pcre = '*';
}
rv = MXS_MYSQL_NAME_WITH_WILDCARD;
break;
case '\'':
case '^':
case '.':
case '$':
case '|':
case '(':
case ')':
case '[':
case ']':
case '*':
case '+':
case '?':
case '{':
case '}':
*pcre++ = '\\';
// Flowthrough
default:
*pcre = *mysql;
}
++pcre;
++mysql;
}
*pcre = 0;
return rv;
}
void mxs_mysql_update_server_version(SERVER* dest, MYSQL* source)
{
// This function should only be called for a live connection.
const char* version_string = mysql_get_server_info(source);
unsigned long version_num = mysql_get_server_version(source);
mxb_assert(version_string != NULL && version_num != 0);
dest->set_version(version_num, version_string);
}
namespace maxscale
{
std::unique_ptr<mxq::QueryResult> execute_query(MYSQL* conn, const std::string& query,
std::string* errmsg_out)
{
using mxq::QueryResult;
std::unique_ptr<QueryResult> rval;
MYSQL_RES* result = NULL;
if (mxs_mysql_query(conn, query.c_str()) == 0 && (result = mysql_store_result(conn)) != NULL)
{
rval = std::unique_ptr<QueryResult>(new QueryResult(result));
}
else if (errmsg_out)
{
*errmsg_out = mxb::string_printf("Query '%s' failed: '%s'.", query.c_str(), mysql_error(conn));
}
return rval;
}
}