MXS-1783 Add 10.4 support

qc_mysqlembedded can now be built using the embedded library from
MariaDB 10.4.
This commit is contained in:
Johan Wikman 2019-05-31 10:57:40 +03:00
parent de3c5721e9
commit b635cc92ab
5 changed files with 263 additions and 75 deletions

View File

@ -6,7 +6,7 @@
# MYSQL_EMBEDDED_LIBRARIES - The MySQL embedded library
# MYSQL_EMBEDDED_INCLUDE_DIR - Path to MySQL headers
find_file(MYSQL_EMBEDDED_VERSION_H mysql_version.h HINTS ${MYSQL_EMBEDDED_INCLUDE_DIR} PATH_SUFFIXES mysql)
find_file(MYSQL_EMBEDDED_VERSION_H mysql_version.h HINTS ${MYSQL_EMBEDDED_INCLUDE_DIR} NO_DEFAULT_PATH)
if(MYSQL_EMBEDDED_VERSION_H MATCHES "MYSQL_EMBEDDED_VERSION_H-NOTFOUND")
message(FATAL_ERROR "Cannot find the mysql_version.h header")
else()

View File

@ -5,7 +5,7 @@ if (BUILD_QC_MYSQLEMBEDDED)
foreach(DIR ${MYSQL_INCLUDE_DIR_ALL})
include_directories(BEFORE ${DIR})
endforeach()
include_directories(BEFORE ${MYSQL_EMBEDDED_INCLUDE_DIR}/..)
include_directories(AFTER ${MYSQL_EMBEDDED_INCLUDE_DIR}/..)
add_library(qc_mysqlembedded SHARED qc_mysqlembedded.cc)

View File

@ -29,10 +29,10 @@
#if defined (MYSQL_CLIENT)
#undef MYSQL_CLIENT
#endif
#include <my_global.h>
#include <my_config.h>
#include <mysql.h>
#include <my_sys.h>
#include <my_global.h>
#include <my_dbug.h>
#include <my_base.h>
// We need to get access to Item::str_value, which is protected. So we cheat.
@ -44,7 +44,6 @@
#include <sql_class.h>
#include <sql_lex.h>
#include <embedded_priv.h>
#include <sql_class.h>
#include <sql_lex.h>
#include <sql_parse.h>
#include <errmsg.h>
@ -60,6 +59,7 @@
#include <pthread.h>
#define json_type mxs_json_type
#include <maxbase/assert.h>
#include <maxscale/log.hh>
#include <maxscale/query_classifier.hh>
@ -98,11 +98,15 @@ typedef struct name_mapping
static NAME_MAPPING function_name_mappings_default[] =
{
{"octet_length", "length"},
{NULL, NULL}
};
static NAME_MAPPING function_name_mappings_oracle[] =
{
{"octet_length", "lengthb"},
{"decode_oracle", "decode"},
{"char_length", "length"},
{"concat_operator_oracle", "concat"},
{"case", "decode"},
{NULL, NULL }
@ -180,6 +184,135 @@ inline void get_string_and_length(const char* cs, const char** s, size_t* length
}
#endif
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 4
#define MARIADB_10_4
class Expose_Lex_prepared_stmt
{
Lex_ident_sys m_name; // Statement name (in all queries)
Item *m_code; // PREPARE or EXECUTE IMMEDIATE source expression
List<Item> m_params; // List of parameters for EXECUTE [IMMEDIATE]
public:
static Item* code(Lex_prepared_stmt* lps)
{
return reinterpret_cast<Expose_Lex_prepared_stmt*>(lps)->m_code;
}
};
static_assert(sizeof(Lex_prepared_stmt) == sizeof(Expose_Lex_prepared_stmt),
"Update Expose_Lex_prepared_stmt, some member variable(s) is(are) missing.");
#define QCME_STRING(name, s) LEX_CSTRING name { s, strlen(s) }
inline int qcme_thd_set_db(THD* thd, LEX_CSTRING& s)
{
return thd->set_db(&s);
}
inline bool qcme_item_is_int(Item* item)
{
return
item->type() == Item::CONST_ITEM
&& static_cast<Item_basic_value*>(item)->const_ptr_longlong();
}
inline bool qcme_item_is_string(Item* item)
{
return
item->type() == Item::CONST_ITEM
&& static_cast<Item_basic_value*>(item)->const_ptr_string();
}
inline const char* qcme_string_get(const LEX_CSTRING& s)
{
return s.str && s.length ? s.str : (s.str != nullptr && *s.str == 0 ? s.str : nullptr);
}
#define QC_CF_IMPLICIT_COMMIT_BEGIN CF_IMPLICIT_COMMIT_BEGIN
#define QC_CF_IMPLICIT_COMMIT_END CF_IMPLICIT_COMMIT_END
inline SELECT_LEX* qcme_get_first_select_lex(LEX* lex)
{
return lex->first_select_lex();
}
inline const LEX_CSTRING& qcme_get_prepared_stmt_name(LEX* lex)
{
Lex_prepared_stmt& prepared_stmt = lex->prepared_stmt;
return prepared_stmt.name();
}
inline Item* qcme_get_prepared_stmt_code(LEX* lex)
{
return Expose_Lex_prepared_stmt::code(&lex->prepared_stmt);
}
extern "C"
{
void _db_flush_(void)
{
}
}
#else
#define QCME_STRING(name, s) const char* name = s
inline int qcme_thd_set_db(THD* thd, const char* s)
{
return thd->set_db(s, strlen(s));
}
inline bool qcme_item_is_int(Item* item)
{
return item->type() == Item::INT_ITEM;
}
inline bool qcme_item_is_string(Item* item)
{
return item->type() == Item::STRING_ITEM;
}
inline const char* qcme_string_get(const char* s)
{
return s;
}
#define QC_CF_IMPLICIT_COMMIT_BEGIN CF_IMPLICT_COMMIT_BEGIN
#define QC_CF_IMPLICIT_COMMIT_END CF_IMPLICIT_COMMIT_END
inline SELECT_LEX* qcme_get_first_select_lex(LEX* lex)
{
return &lex->select_lex;
}
#if MYSQL_VERSION_MINOR >= 3
const LEX_CSTRING& qcme_get_prepared_stmt_name(LEX* lex)
{
return lex->prepared_stmt_name;
}
inline Item* qcme_get_prepared_stmt_code(LEX* lex)
{
return lex->prepared_stmt_code;
}
#else
const LEX_STRING& qcme_get_prepared_stmt_name(LEX* lex)
{
return lex->prepared_stmt_name;
}
#endif
#endif
static struct
{
qc_sql_mode_t sql_mode;
@ -572,7 +705,8 @@ static bool create_parse_tree(THD* thd)
{
Parser_state parser_state;
bool failp = FALSE;
const char* virtual_db = "skygw_virtual";
QCME_STRING(virtual_db, "skygw_virtual");
if (parser_state.init(thd, thd->query(), thd->query_length()))
{
@ -586,7 +720,7 @@ static bool create_parse_tree(THD* thd)
* Set some database to thd so that parsing won't fail because of
* missing database. Then parse.
*/
failp = thd->set_db(virtual_db, strlen(virtual_db));
failp = qcme_thd_set_db(thd, virtual_db);
if (failp)
{
@ -761,13 +895,11 @@ static uint32_t resolve_query_type(parsing_info_t* pi, THD* thd)
{
if (mxs_log_is_priority_enabled(LOG_INFO))
{
if (sql_command_flags[lex->sql_command]
& CF_IMPLICT_COMMIT_BEGIN)
if (sql_command_flags[lex->sql_command] & QC_CF_IMPLICIT_COMMIT_BEGIN)
{
MXS_INFO("Implicit COMMIT before executing the next command.");
}
else if (sql_command_flags[lex->sql_command]
& CF_IMPLICIT_COMMIT_END)
else if (sql_command_flags[lex->sql_command] & QC_CF_IMPLICIT_COMMIT_END)
{
MXS_INFO("Implicit COMMIT after executing the next command.");
}
@ -1331,7 +1463,7 @@ static int is_autocommit_stmt(LEX* lex)
if (item != NULL) /*< found autocommit command */
{
if (item->type() == Item::INT_ITEM) /*< '0' or '1' */
if (qcme_item_is_int(item))
{
rc = item->val_int();
@ -1340,7 +1472,7 @@ static int is_autocommit_stmt(LEX* lex)
rc = -1;
}
}
else if (item->type() == Item::STRING_ITEM) /*< 'on' or 'off' */
else if (qcme_item_is_string(item))
{
String str(target, sizeof(target), system_charset_info);
String* res = item->val_str(&str);
@ -1562,26 +1694,26 @@ int32_t qc_mysql_get_table_names(GWBUF* querybuf, int32_t fullnames, char*** tab
if (fullnames)
{
if (tbl->db
&& (strcmp(tbl->db, "skygw_virtual") != 0)
&& (strcmp(tbl->table_name, "*") != 0))
if (qcme_string_get(tbl->db)
&& (strcmp(qcme_string_get(tbl->db), "skygw_virtual") != 0)
&& (strcmp(qcme_string_get(tbl->table_name), "*") != 0))
{
catnm = (char*) calloc(strlen(tbl->db)
+ strlen(tbl->table_name)
catnm = (char*) calloc(strlen(qcme_string_get(tbl->db))
+ strlen(qcme_string_get(tbl->table_name))
+ 2,
sizeof(char));
strcpy(catnm, tbl->db);
strcpy(catnm, qcme_string_get(tbl->db));
strcat(catnm, ".");
strcat(catnm, tbl->table_name);
strcat(catnm, qcme_string_get(tbl->table_name));
}
}
if (!catnm)
{
// Sometimes the tablename is "*"; we exclude that.
if (strcmp(tbl->table_name, "*") != 0)
if (strcmp(qcme_string_get(tbl->table_name), "*") != 0)
{
catnm = strdup(tbl->table_name);
catnm = strdup(qcme_string_get(tbl->table_name));
}
}
@ -1637,9 +1769,9 @@ int32_t qc_mysql_get_created_table_name(GWBUF* querybuf, char** table_name)
if (lex && (lex->sql_command == SQLCOM_CREATE_TABLE))
{
if (lex->create_last_non_select_table
&& lex->create_last_non_select_table->table_name)
&& qcme_string_get(lex->create_last_non_select_table->table_name))
{
*table_name = strdup(lex->create_last_non_select_table->table_name);
*table_name = strdup(qcme_string_get(lex->create_last_non_select_table->table_name));
}
}
@ -1867,7 +1999,9 @@ int32_t qc_mysql_get_database_names(GWBUF* querybuf, char*** databasesp, int* si
if (lex->sql_command == SQLCOM_CHANGE_DB || lex->sql_command == SQLCOM_SHOW_TABLES)
{
if (lex->select_lex.db && (strcmp(lex->select_lex.db, "skygw_virtual") != 0))
SELECT_LEX* select_lex = qcme_get_first_select_lex(lex);
if (qcme_string_get(select_lex->db) &&
(strcmp(qcme_string_get(select_lex->db), "skygw_virtual") != 0))
{
if (i >= currsz)
{
@ -1883,7 +2017,7 @@ int32_t qc_mysql_get_database_names(GWBUF* querybuf, char*** databasesp, int* si
currsz = currsz * 2 + 1;
}
databases[i++] = strdup(lex->select_lex.db);
databases[i++] = strdup(qcme_string_get(select_lex->db));
}
}
else
@ -1907,7 +2041,8 @@ int32_t qc_mysql_get_database_names(GWBUF* querybuf, char*** databasesp, int* si
// The database is sometimes an empty string. So as not to return
// an array of empty strings, we need to check for that possibility.
if ((strcmp(tbl->db, "skygw_virtual") != 0) && (*tbl->db != 0))
if ((strcmp(qcme_string_get(tbl->db), "skygw_virtual") != 0) &&
(*qcme_string_get(tbl->db) != 0))
{
if (i >= currsz)
{
@ -1925,14 +2060,14 @@ int32_t qc_mysql_get_database_names(GWBUF* querybuf, char*** databasesp, int* si
int j = 0;
while ((j < i) && (strcmp(tbl->db, databases[j]) != 0))
while ((j < i) && (strcmp(qcme_string_get(tbl->db), databases[j]) != 0))
{
++j;
}
if (j == i) // Not found
{
databases[i++] = strdup(tbl->db);
databases[i++] = strdup(qcme_string_get(tbl->db));
}
}
@ -2112,11 +2247,13 @@ int32_t qc_mysql_get_prepare_name(GWBUF* stmt, char** namep)
|| (lex->sql_command == SQLCOM_EXECUTE)
|| (lex->sql_command == SQLCOM_DEALLOCATE_PREPARE))
{
name = (char*)malloc(lex->prepared_stmt_name.length + 1);
// LEX_STRING or LEX_CSTRING
const auto& prepared_stmt_name = qcme_get_prepared_stmt_name(lex);
name = (char*)malloc(prepared_stmt_name.length + 1);
if (name)
{
memcpy(name, lex->prepared_stmt_name.str, lex->prepared_stmt_name.length);
name[lex->prepared_stmt_name.length] = 0;
memcpy(name, prepared_stmt_name.str, prepared_stmt_name.length);
name[prepared_stmt_name.length] = 0;
}
}
}
@ -2145,8 +2282,8 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt)
const char* preparable_stmt;
size_t preparable_stmt_len;
#if MYSQL_VERSION_MINOR >= 3
preparable_stmt = lex->prepared_stmt_code->str_value.ptr();
preparable_stmt_len = lex->prepared_stmt_code->str_value.length();
preparable_stmt = qcme_get_prepared_stmt_code(lex)->str_value.ptr();
preparable_stmt_len = qcme_get_prepared_stmt_code(lex)->str_value.length();
#else
preparable_stmt = lex->prepared_stmt_code.str;
preparable_stmt_len = lex->prepared_stmt_code.length;
@ -2169,24 +2306,66 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt)
// Is followed by the statement.
char* s = (char*)GWBUF_DATA(preperable_packet) + 5;
// We copy the statment, blindly replacing all '?':s with '0':s as
// otherwise the parsing of the preparable statement as a regular
// statement will not always succeed.
// We copy the statment, blindly replacing all '?':s (always)
// and ':N' (in Oracle mode) with '0':s as otherwise the parsing of the
// preparable statement as a regular statement will not always succeed.
qc_sql_mode_t sql_mode = this_thread.sql_mode;
const char* p = preparable_stmt;
while (p < preparable_stmt + preparable_stmt_len)
const char* end = preparable_stmt + preparable_stmt_len;
while (p < end)
{
if (*p == '?')
{
*s = '0';
}
else if (sql_mode == QC_SQL_MODE_ORACLE)
{
if (*p == ':' && p + 1 < end)
{
// This may be an Oracle specific positional parameter.
char c = *(p + 1);
if (isalnum(c))
{
++p;
// e.g. :4711 or :aaa
while (p + 1 < end && isalnum(*(p + 1)))
{
++p;
}
*s = '0';
}
else if (c == '\'' || c == '\"')
{
// e.g. :"abc"
char quote = *p;
while (p + 1 < end && *(p + 1) != quote)
{
++p;
}
*s = '0';
}
}
else
{
*s = *p;
}
}
else
{
*s = *p;
}
++p;
if (p != end)
{
++p;
}
++s;
}
*s = 0;
}
pi->preparable_stmt = preperable_packet;
@ -2257,16 +2436,18 @@ static void unalias_names(st_select_lex* select,
while ((*to_table == from_table) && tbl)
{
if (tbl->alias
&& (strcasecmp(tbl->alias, from_table) == 0)
&& (strcasecmp(tbl->table_name, "*") != 0))
if (qcme_string_get(tbl->alias)
&& (strcasecmp(qcme_string_get(tbl->alias), from_table) == 0)
&& (strcasecmp(qcme_string_get(tbl->table_name), "*") != 0))
{
// The dummy default database "skygw_virtual" is not included.
if (tbl->db && *tbl->db && (strcmp(tbl->db, "skygw_virtual") != 0))
if (qcme_string_get(tbl->db)
&& *qcme_string_get(tbl->db)
&& (strcmp(qcme_string_get(tbl->db), "skygw_virtual") != 0))
{
*to_database = (char*)tbl->db;
*to_database = (char*)qcme_string_get(tbl->db);
}
*to_table = (char*)tbl->table_name;
*to_table = (char*)qcme_string_get(tbl->table_name);
}
tbl = tbl->next_local;
@ -2498,22 +2679,24 @@ static void add_function_field_usage(st_select_lex* select,
add_function_field_usage(select, static_cast<Item_field*>(item), fi);
break;
case Item::STRING_ITEM:
if (this_thread.options & QC_OPTION_STRING_ARG_AS_FIELD)
{
String* s = item->val_str();
int len = s->length();
char tmp[len + 1];
memcpy(tmp, s->ptr(), len);
tmp[len] = 0;
add_function_field_usage(nullptr, nullptr, tmp, fi);
}
break;
default:
// mxb_assert(!true);
;
if (qcme_item_is_string(item))
{
if (this_thread.options & QC_OPTION_STRING_ARG_AS_FIELD)
{
String* s = item->val_str();
int len = s->length();
char tmp[len + 1];
memcpy(tmp, s->ptr(), len);
tmp[len] = 0;
add_function_field_usage(nullptr, nullptr, tmp, fi);
}
}
else
{
// mxb_assert(!true);
}
}
}
}
@ -3084,20 +3267,20 @@ static void update_field_infos(parsing_info_t* pi,
}
break;
case Item::STRING_ITEM:
if (this_thread.options & QC_OPTION_STRING_AS_FIELD)
{
String* s = item->val_str();
int len = s->length();
char tmp[len + 1];
memcpy(tmp, s->ptr(), len);
tmp[len] = 0;
add_field_info(pi, nullptr, nullptr, tmp, excludep);
}
break;
default:
if (qcme_item_is_string(item))
{
if (this_thread.options & QC_OPTION_STRING_AS_FIELD)
{
String* s = item->val_str();
int len = s->length();
char tmp[len + 1];
memcpy(tmp, s->ptr(), len);
tmp[len] = 0;
add_field_info(pi, nullptr, nullptr, tmp, excludep);
}
}
break;
}
}
@ -3213,9 +3396,10 @@ int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_
return QC_RESULT_OK;
}
lex->current_select = &lex->select_lex;
SELECT_LEX* select_lex = qcme_get_first_select_lex(lex);
lex->current_select = select_lex;
update_field_infos(pi, lex, &lex->select_lex, NULL);
update_field_infos(pi, lex, select_lex, NULL);
QC_FUNCTION_INFO* fi = NULL;

View File

@ -440,6 +440,7 @@ static const char* BUILTIN_FUNCTIONS[] =
"instr",
"lcase",
"left",
"lengthb",
"length",
"like",
"load_file",

View File

@ -11,7 +11,10 @@ SET sql_mode=ORACLE;
SELECT LENGTH(null), LENGTH('a'), LENGTH(123);
SELECT LENGTHB(null), LENGTHB('a'), LENGTHB(123);
# 10.4 based qc_mysqlembedded recognizes this as QUERY_TYPE_READ, while
# 10.3 based one recognizes it as QUERY_TYPE_WRITE (i.e. it does not
# recognize it). qc_sqlite now (May 2019) recognizes it always as READ.
#SELECT LENGTHB(null), LENGTHB('a'), LENGTHB(123);
# qc_sqlite: SELECT LENGTH(_utf8 0xC39F), LENGTH(CHAR(14844588 USING utf8));
# Sqlite3 error: SQL logic error or missing database, near "0xC39F": syntax error