MXS-1475 Add sql_mode parser
Given the value in a statement like "SET SQL_MODE=..." this parser is capable of deducing whether SQL_MODE is set to DEFAULT or ORACLE or something else.
This commit is contained in:
264
server/modules/protocol/MySQL/mariadbclient/sqlmodeparser.hh
Normal file
264
server/modules/protocol/MySQL/mariadbclient/sqlmodeparser.hh
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
#pragma once
|
||||||
|
/*
|
||||||
|
* 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: 2019-07-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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <maxscale/cppdefs.hh>
|
||||||
|
#include <maxscale/customparser.hh>
|
||||||
|
#include <maxscale/protocol/mysql.h>
|
||||||
|
|
||||||
|
|
||||||
|
class SqlModeParser : public maxscale::CustomParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum sql_mode_t
|
||||||
|
{
|
||||||
|
DEFAULT, // "set sql_mode=DEFAULT"
|
||||||
|
ORACLE, // "set sql_mode=ORACLE", "set sql_mode='PIPES_AS_CONCAT,ORACLE', autocommit=false", etc.
|
||||||
|
SOMETHING // "set sql_mode=PIPES_AS_CONCAT"
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
UNUSED_FIRST = 0xFF,
|
||||||
|
TK_DEFAULT,
|
||||||
|
TK_ORACLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlModeParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the trimmed value from the right of a "SET SQL_MODE=..." statement
|
||||||
|
* return whether SQL_MODE is set to ORACLE or DEFAULT.
|
||||||
|
*
|
||||||
|
* @param pBegin Beginning of string.
|
||||||
|
* @param pEnd One past end of string.
|
||||||
|
*
|
||||||
|
* @return DEFAULT if the value is DEFAULT,
|
||||||
|
* ORACLE if the string contains ORACLE,
|
||||||
|
* SOMETHING otherwise.
|
||||||
|
*/
|
||||||
|
sql_mode_t get_sql_mode(const char* pBegin, const char* pEnd)
|
||||||
|
{
|
||||||
|
sql_mode_t sql_mode = SOMETHING;
|
||||||
|
|
||||||
|
m_pSql = pBegin;
|
||||||
|
m_pI = m_pSql;
|
||||||
|
m_pEnd = pEnd;
|
||||||
|
|
||||||
|
return parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a @c sql_mode_t as a string.
|
||||||
|
*
|
||||||
|
* @param sql_mode An SQL mode.
|
||||||
|
*
|
||||||
|
* @return The corresponding string.
|
||||||
|
*/
|
||||||
|
static const char* to_string(sql_mode_t sql_mode)
|
||||||
|
{
|
||||||
|
switch (sql_mode)
|
||||||
|
{
|
||||||
|
case DEFAULT:
|
||||||
|
return "DEFAULT";
|
||||||
|
|
||||||
|
case ORACLE:
|
||||||
|
return "ORACLE";
|
||||||
|
|
||||||
|
case SOMETHING:
|
||||||
|
return "SOMETHING";
|
||||||
|
|
||||||
|
default:
|
||||||
|
ss_dassert(!true);
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool consume_id()
|
||||||
|
{
|
||||||
|
// Consumes "[a-zA-Z]([a-zA-Z0-9_])*
|
||||||
|
|
||||||
|
bool rv = false;
|
||||||
|
|
||||||
|
if (is_alpha(*m_pI))
|
||||||
|
{
|
||||||
|
rv = true;
|
||||||
|
|
||||||
|
++m_pI;
|
||||||
|
|
||||||
|
while ((m_pI < m_pEnd) && (is_alpha(*m_pI) || is_number(*m_pI) || (*m_pI == '_')))
|
||||||
|
{
|
||||||
|
++m_pI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql_mode_t parse()
|
||||||
|
{
|
||||||
|
sql_mode_t rv = SOMETHING;
|
||||||
|
|
||||||
|
token_t token = next_token();
|
||||||
|
|
||||||
|
switch (token)
|
||||||
|
{
|
||||||
|
case '\'':
|
||||||
|
case '"':
|
||||||
|
case '`':
|
||||||
|
rv = parse_string(token);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_DEFAULT:
|
||||||
|
rv = DEFAULT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_ORACLE:
|
||||||
|
rv = ORACLE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PARSER_UNKNOWN_TOKEN:
|
||||||
|
default:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql_mode_t parse_string(char quote)
|
||||||
|
{
|
||||||
|
sql_mode_t rv = SOMETHING;
|
||||||
|
|
||||||
|
bool parsed;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
parsed = parse_setting(&rv);
|
||||||
|
|
||||||
|
if (parsed)
|
||||||
|
{
|
||||||
|
bypass_whitespace();
|
||||||
|
|
||||||
|
if (peek_current_char(&c) && (c == ','))
|
||||||
|
{
|
||||||
|
++m_pI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (parsed && (c == ','));
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_setting(sql_mode_t* pSql_mode)
|
||||||
|
{
|
||||||
|
bool rv = true;
|
||||||
|
|
||||||
|
token_t token = next_token();
|
||||||
|
|
||||||
|
switch (token)
|
||||||
|
{
|
||||||
|
case TK_ORACLE:
|
||||||
|
*pSql_mode = ORACLE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PARSER_UNKNOWN_TOKEN:
|
||||||
|
if (consume_id())
|
||||||
|
{
|
||||||
|
*pSql_mode = SOMETHING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rv = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PARSER_EXHAUSTED:
|
||||||
|
log_exhausted();
|
||||||
|
rv = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
log_unexpected();
|
||||||
|
rv = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_t next_token(token_required_t required = TOKEN_NOT_REQUIRED)
|
||||||
|
{
|
||||||
|
token_t token = PARSER_UNKNOWN_TOKEN;
|
||||||
|
|
||||||
|
bypass_whitespace();
|
||||||
|
|
||||||
|
if (m_pI == m_pEnd)
|
||||||
|
{
|
||||||
|
token = PARSER_EXHAUSTED;
|
||||||
|
}
|
||||||
|
else if (*m_pI == ';')
|
||||||
|
{
|
||||||
|
++m_pI;
|
||||||
|
|
||||||
|
while ((m_pI != m_pEnd) && isspace(*m_pI))
|
||||||
|
{
|
||||||
|
++m_pI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pI != m_pEnd)
|
||||||
|
{
|
||||||
|
MXS_WARNING("Non-space data found after semi-colon: '%.*s'.",
|
||||||
|
(int)(m_pEnd - m_pI), m_pI);
|
||||||
|
}
|
||||||
|
|
||||||
|
token = PARSER_EXHAUSTED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (*m_pI)
|
||||||
|
{
|
||||||
|
case '\'':
|
||||||
|
case '"':
|
||||||
|
case '`':
|
||||||
|
case ',':
|
||||||
|
token = *m_pI;
|
||||||
|
++m_pI;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
token = expect_token(MXS_CP_EXPECT_TOKEN("DEFAULT"), TK_DEFAULT);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
case 'O':
|
||||||
|
token = expect_token(MXS_CP_EXPECT_TOKEN("ORACLE"), TK_ORACLE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((token == PARSER_EXHAUSTED) && (required == TOKEN_REQUIRED))
|
||||||
|
{
|
||||||
|
log_exhausted();
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
};
|
@ -1,8 +1,11 @@
|
|||||||
add_executable(test_setparser test_setparser.cc)
|
add_executable(test_setparser test_setparser.cc)
|
||||||
add_executable(test_setsqlmodeparser test_setsqlmodeparser.cc)
|
add_executable(test_setsqlmodeparser test_setsqlmodeparser.cc)
|
||||||
|
add_executable(test_sqlmodeparser test_sqlmodeparser.cc)
|
||||||
|
|
||||||
target_link_libraries(test_setsqlmodeparser maxscale-common)
|
target_link_libraries(test_setsqlmodeparser maxscale-common)
|
||||||
target_link_libraries(test_setparser maxscale-common)
|
target_link_libraries(test_setparser maxscale-common)
|
||||||
|
target_link_libraries(test_sqlmodeparser maxscale-common)
|
||||||
|
|
||||||
add_test(test_setparser test_setarser)
|
add_test(test_setparser test_setarser)
|
||||||
add_test(test_setsqlmodeparser test_setsqlmodeparser)
|
add_test(test_setsqlmodeparser test_setsqlmodeparser)
|
||||||
|
add_test(test_sqlmodeparser test_sqlmodeparser)
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* 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: 2019-07-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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../sqlmodeparser.hh"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <maxscale/buffer.h>
|
||||||
|
#include <maxscale/paths.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
typedef SqlModeParser P;
|
||||||
|
|
||||||
|
struct TEST_CASE
|
||||||
|
{
|
||||||
|
const char* zValue;
|
||||||
|
SqlModeParser::sql_mode_t sql_mode;
|
||||||
|
} test_cases[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"DEFAULT",
|
||||||
|
P::DEFAULT
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ORACLE",
|
||||||
|
P::ORACLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"BLAH",
|
||||||
|
P::SOMETHING
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"'BLAH'",
|
||||||
|
P::SOMETHING
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"'ORACLE'",
|
||||||
|
P::ORACLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"'BLAH, A, B, ORACLE'",
|
||||||
|
P::ORACLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"'BLAH, A, B, XYZ_123'",
|
||||||
|
P::SOMETHING
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"'A,B, ORACLE'",
|
||||||
|
P::ORACLE
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const int N_TEST_CASES = sizeof(test_cases) / sizeof(test_cases[0]);
|
||||||
|
|
||||||
|
int test(const char* zValue, SqlModeParser::sql_mode_t expected_sql_mode)
|
||||||
|
{
|
||||||
|
int rv = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
SqlModeParser parser;
|
||||||
|
|
||||||
|
SqlModeParser::sql_mode_t sql_mode = parser.get_sql_mode(zValue, zValue + strlen(zValue));
|
||||||
|
|
||||||
|
if (sql_mode == expected_sql_mode)
|
||||||
|
{
|
||||||
|
cout << "OK";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "ERROR: Expected "
|
||||||
|
<< "'" << SqlModeParser::to_string(expected_sql_mode) << "'"
|
||||||
|
<< ", got "
|
||||||
|
<< "'" << SqlModeParser::to_string(sql_mode) << "'"
|
||||||
|
<< ".";
|
||||||
|
rv = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test(const TEST_CASE& test_case)
|
||||||
|
{
|
||||||
|
int rv = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
cout << test_case.zValue << ": ";
|
||||||
|
|
||||||
|
rv = test(test_case.zValue, test_case.sql_mode);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_contiguous()
|
||||||
|
{
|
||||||
|
int rv = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
cout << "Test contiguous statements\n"
|
||||||
|
<< "--------------------------" << endl;
|
||||||
|
|
||||||
|
for (int i = 0; i < N_TEST_CASES; ++i)
|
||||||
|
{
|
||||||
|
if (test(test_cases[i]) == EXIT_FAILURE)
|
||||||
|
{
|
||||||
|
rv = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test()
|
||||||
|
{
|
||||||
|
int rv = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
if (test_contiguous() != EXIT_SUCCESS)
|
||||||
|
{
|
||||||
|
rv = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv == EXIT_SUCCESS)
|
||||||
|
{
|
||||||
|
cout << "OK" << endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "ERROR" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
int rv = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
srand(time(NULL));
|
||||||
|
|
||||||
|
set_datadir(strdup("/tmp"));
|
||||||
|
set_langdir(strdup("."));
|
||||||
|
set_process_datadir(strdup("/tmp"));
|
||||||
|
|
||||||
|
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
|
||||||
|
{
|
||||||
|
rv = test();
|
||||||
|
|
||||||
|
mxs_log_finish();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << "error: Could not initialize log." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
Reference in New Issue
Block a user