Merge branch '2.3' into develop
This commit is contained in:
@ -1133,6 +1133,7 @@ static bool config_load_and_process(const char* filename, bool (* process_config
|
||||
{
|
||||
bool rval = false;
|
||||
DUPLICATE_CONTEXT dcontext;
|
||||
bool have_persisted_configs = false;
|
||||
|
||||
if (duplicate_context_init(&dcontext))
|
||||
{
|
||||
@ -1166,8 +1167,12 @@ static bool config_load_and_process(const char* filename, bool (* process_config
|
||||
* TODO: Figure out a cleaner way to do this
|
||||
*/
|
||||
is_persisted_config = true;
|
||||
have_persisted_configs = true;
|
||||
|
||||
MXS_NOTICE("Loading generated configuration files from '%s'", persist_cnf);
|
||||
MXS_NOTICE("Runtime configuration changes have been done to MaxScale. Loading persisted "
|
||||
"configuration files and applying them on top of the main configuration file. "
|
||||
"These changes can override the values of the main configuration file: "
|
||||
"To revert them, remove all the files in '%s'.", persist_cnf);
|
||||
DUPLICATE_CONTEXT p_dcontext;
|
||||
/**
|
||||
* We need to initialize a second duplicate context for the
|
||||
@ -1193,12 +1198,12 @@ static bool config_load_and_process(const char* filename, bool (* process_config
|
||||
if (!check_config_objects(config_context.next) || !process_config(config_context.next))
|
||||
{
|
||||
rval = false;
|
||||
if (contains_cnf_files(persist_cnf))
|
||||
if (have_persisted_configs)
|
||||
{
|
||||
MXS_WARNING("One or more generated configurations were found at '%s'. "
|
||||
"If the error relates to any of the files located there, "
|
||||
"remove the offending configurations from this directory.",
|
||||
persist_cnf);
|
||||
MXS_WARNING("Persisted configuration files generated by runtime configuration "
|
||||
"changes were found at '%s' and at least one configuration error was "
|
||||
"encountered. If the errors relate to any of the persisted configuration "
|
||||
"files, remove the offending files and restart MaxScale.", persist_cnf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2632,6 +2637,38 @@ static int handle_global_item(const char* name, const char* value)
|
||||
return processed ? 1 : 0;
|
||||
}
|
||||
|
||||
bool config_can_modify_at_runtime(const char* name)
|
||||
{
|
||||
for (int i = 0; config_pre_parse_global_params[i]; ++i)
|
||||
{
|
||||
if (strcmp(name, config_pre_parse_global_params[i]) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::unordered_set<std::string> static_params
|
||||
{
|
||||
CN_USERS_REFRESH_TIME,
|
||||
CN_LOCAL_ADDRESS,
|
||||
CN_ADMIN_ENABLED,
|
||||
CN_ADMIN_SSL_CA_CERT,
|
||||
CN_ADMIN_SSL_CERT,
|
||||
CN_ADMIN_SSL_KEY,
|
||||
CN_ADMIN_HOST,
|
||||
CN_ADMIN_PORT,
|
||||
CN_LOG_THROTTLING,
|
||||
"sql_mode",
|
||||
CN_QUERY_CLASSIFIER_ARGS,
|
||||
CN_QUERY_CLASSIFIER,
|
||||
CN_POLL_SLEEP,
|
||||
CN_NON_BLOCKING_POLLS,
|
||||
CN_THREAD_STACK_SIZE,
|
||||
CN_THREADS
|
||||
};
|
||||
|
||||
return static_params.count(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free an SSL structure
|
||||
*
|
||||
|
@ -938,6 +938,81 @@ bool runtime_alter_maxscale(const char* name, const char* value)
|
||||
MXS_NOTICE("'%s' set to: %lu", CN_WRITEQ_LOW_WATER, size);
|
||||
}
|
||||
}
|
||||
else if (key == CN_MS_TIMESTAMP)
|
||||
{
|
||||
mxs_log_set_highprecision_enabled(config_truth_value(value));
|
||||
rval = true;
|
||||
}
|
||||
else if (key == CN_SKIP_PERMISSION_CHECKS)
|
||||
{
|
||||
cnf.skip_permission_checks = config_truth_value(value);
|
||||
rval = true;
|
||||
}
|
||||
else if (key == CN_QUERY_RETRIES)
|
||||
{
|
||||
int intval = get_positive_int(value);
|
||||
if (intval)
|
||||
{
|
||||
cnf.query_retries = intval;
|
||||
rval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
config_runtime_error("Invalid timeout value for '%s': %s", CN_QUERY_RETRIES, value);
|
||||
}
|
||||
}
|
||||
else if (key == CN_QUERY_RETRY_TIMEOUT)
|
||||
{
|
||||
int intval = get_positive_int(value);
|
||||
if (intval)
|
||||
{
|
||||
cnf.query_retry_timeout = intval;
|
||||
rval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
config_runtime_error("Invalid timeout value for '%s': %s", CN_QUERY_RETRY_TIMEOUT, value);
|
||||
}
|
||||
}
|
||||
else if (key == CN_RETAIN_LAST_STATEMENTS)
|
||||
{
|
||||
int intval = get_positive_int(value);
|
||||
if (intval)
|
||||
{
|
||||
session_set_retain_last_statements(intval);
|
||||
rval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
config_runtime_error("Invalid value for '%s': %s", CN_RETAIN_LAST_STATEMENTS, value);
|
||||
}
|
||||
}
|
||||
else if (key == CN_DUMP_LAST_STATEMENTS)
|
||||
{
|
||||
rval = true;
|
||||
if (strcmp(value, "on_close") == 0)
|
||||
{
|
||||
session_set_dump_statements(SESSION_DUMP_STATEMENTS_ON_CLOSE);
|
||||
}
|
||||
else if (strcmp(value, "on_error") == 0)
|
||||
{
|
||||
session_set_dump_statements(SESSION_DUMP_STATEMENTS_ON_ERROR);
|
||||
}
|
||||
else if (strcmp(value, "never") == 0)
|
||||
{
|
||||
session_set_dump_statements(SESSION_DUMP_STATEMENTS_NEVER);
|
||||
}
|
||||
else
|
||||
{
|
||||
rval = false;
|
||||
config_runtime_error("%s can have the values 'never', 'on_close' or 'on_error'.",
|
||||
CN_DUMP_LAST_STATEMENTS);
|
||||
}
|
||||
}
|
||||
else if (config_can_modify_at_runtime(key.c_str()))
|
||||
{
|
||||
config_runtime_error("Global parameter '%s' cannot be modified at runtime", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
config_runtime_error("Unknown global parameter: %s=%s", name, value);
|
||||
|
@ -227,3 +227,12 @@ void dump_param_list(int file,
|
||||
const std::unordered_set<std::string>& ignored,
|
||||
const MXS_MODULE_PARAM* common_params,
|
||||
const MXS_MODULE_PARAM* module_params);
|
||||
|
||||
/**
|
||||
* Check whether a parameter can be modified at runtime
|
||||
*
|
||||
* @param name Name of the parameter
|
||||
*
|
||||
* @return True if the parameter can be modified at runtime
|
||||
*/
|
||||
bool config_can_modify_at_runtime(const char* name);
|
||||
|
@ -18,8 +18,11 @@
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <cctype>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/buffer.h>
|
||||
@ -1261,11 +1264,54 @@ static inline bool is_next(mxs::Buffer::iterator it, mxs::Buffer::iterator end,
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Class for fast char type lookups
|
||||
class LUT
|
||||
{
|
||||
public:
|
||||
LUT(const std::string& values)
|
||||
{
|
||||
for (const auto& a : values)
|
||||
{
|
||||
m_table[(uint8_t)a] = true;
|
||||
}
|
||||
}
|
||||
|
||||
LUT(std::function<bool(uint8_t)> is_type)
|
||||
{
|
||||
for (int i = 0; i <= std::numeric_limits<uint8_t>::max(); i++)
|
||||
{
|
||||
m_table[i] = is_type(i);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool operator()(uint8_t c) const
|
||||
{
|
||||
return m_table[c];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<bool, 256> m_table = {};
|
||||
};
|
||||
|
||||
// Optimized versions of standard functions that ignore the locale and use a lookup table
|
||||
static const LUT is_space(::isspace);
|
||||
static const LUT is_digit(::isdigit);
|
||||
static const LUT is_alpha(::isalpha);
|
||||
static const LUT is_alnum(::isalnum);
|
||||
static const LUT is_xdigit(::isxdigit);
|
||||
|
||||
// For detection of characters that need special treatment, helps speed up processing of keywords etc.
|
||||
static const LUT is_special([](uint8_t c) {
|
||||
return isdigit(c) || isspace(c) || std::string("\"'`#-/\\").find(
|
||||
c) != std::string::npos;
|
||||
});
|
||||
|
||||
static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator it,
|
||||
mxs::Buffer::iterator end)
|
||||
{
|
||||
mxb_assert(it != end);
|
||||
mxb_assert(isdigit(*it));
|
||||
mxb_assert(is_digit(*it));
|
||||
std::pair<bool, mxs::Buffer::iterator> rval = std::make_pair(true, it);
|
||||
bool is_hex = *it == '0';
|
||||
bool allow_hex = false;
|
||||
@ -1275,7 +1321,7 @@ static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator
|
||||
|
||||
while (it != end)
|
||||
{
|
||||
if (isdigit(*it) || (allow_hex && isxdigit(*it)))
|
||||
if (is_digit(*it) || (allow_hex && is_xdigit(*it)))
|
||||
{
|
||||
// Digit or hex-digit, skip it
|
||||
}
|
||||
@ -1295,7 +1341,7 @@ static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator
|
||||
// Possible scientific notation number
|
||||
auto next_it = std::next(it);
|
||||
|
||||
if (next_it == end || (!isdigit(*next_it) && *next_it != '-'))
|
||||
if (next_it == end || (!is_digit(*next_it) && *next_it != '-'))
|
||||
{
|
||||
rval.first = false;
|
||||
break;
|
||||
@ -1312,19 +1358,19 @@ static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator
|
||||
// Possible decimal number
|
||||
auto next_it = std::next(it);
|
||||
|
||||
if (next_it != end && !isdigit(*next_it))
|
||||
if (next_it != end && !is_digit(*next_it))
|
||||
{
|
||||
/** No number after the period, not a decimal number.
|
||||
* The fractional part of the number is optional in MariaDB. */
|
||||
rval.first = false;
|
||||
break;
|
||||
}
|
||||
mxb_assert(isdigit(*next_it));
|
||||
mxb_assert(is_digit(*next_it));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have a non-text character, we treat it as a number
|
||||
rval.first = !isalpha(*it);
|
||||
rval.first = !is_alpha(*it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1337,22 +1383,21 @@ static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator
|
||||
return rval;
|
||||
}
|
||||
|
||||
bool is_negation(const std::string& str)
|
||||
static inline bool is_negation(const std::string& str, int i)
|
||||
{
|
||||
bool rval = false;
|
||||
|
||||
if (!str.empty() && str[str.size() - 1] == '-')
|
||||
if (i > 0 && str[i - 1] == '-')
|
||||
{
|
||||
// Possibly a negative number
|
||||
rval = true;
|
||||
|
||||
for (auto it = std::next(str.rbegin()); it != str.rend(); it++)
|
||||
for (int j = i - 1; j >= 0; j--)
|
||||
{
|
||||
if (!isspace(*it))
|
||||
if (!is_space(str[j]))
|
||||
{
|
||||
/** If we find a previously converted value, we know that it
|
||||
* is not a negation but a subtraction. */
|
||||
rval = *it != '?';
|
||||
rval = str[j] != '?';
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1361,154 +1406,150 @@ bool is_negation(const std::string& str)
|
||||
return rval;
|
||||
}
|
||||
|
||||
mxs::Buffer::iterator find_char(mxs::Buffer::iterator it, const mxs::Buffer::iterator& end, char c)
|
||||
{
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
if (*it == '\\')
|
||||
{
|
||||
if (++it == end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (*it == c)
|
||||
{
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
namespace maxscale
|
||||
{
|
||||
|
||||
std::string get_canonical(GWBUF* querybuf)
|
||||
{
|
||||
std::string rval;
|
||||
rval.reserve(gwbuf_length(querybuf) - MYSQL_HEADER_LEN + 1);
|
||||
int i = 0;
|
||||
rval.resize(gwbuf_length(querybuf) - MYSQL_HEADER_LEN + 1);
|
||||
mxs::Buffer buf(querybuf);
|
||||
|
||||
enum state
|
||||
{
|
||||
NONE,
|
||||
SINGLE_QUOTE,
|
||||
DOUBLE_QUOTE,
|
||||
BACKTICK,
|
||||
UNTIL_NEWLINE,
|
||||
INLINE_COMMENT
|
||||
} my_state = NONE;
|
||||
|
||||
for (auto it = std::next(buf.begin(), MYSQL_HEADER_LEN + 1); // Skip packet header and command
|
||||
it != buf.end(); ++it)
|
||||
{
|
||||
if (*it == '\\')
|
||||
if (!is_special(*it))
|
||||
{
|
||||
// Normal character, no special handling required
|
||||
rval[i++] = *it;
|
||||
}
|
||||
else if (*it == '\\')
|
||||
{
|
||||
// Jump over any escaped values
|
||||
rval += *it;
|
||||
it++;
|
||||
rval += *it;
|
||||
continue;
|
||||
rval[i++] += *it++;
|
||||
|
||||
if (it != buf.end())
|
||||
{
|
||||
rval[i++] = *it;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Query that ends with a backslash
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char prev = rval.empty() ? ' ' : rval[rval.size() - 1];
|
||||
|
||||
switch (my_state)
|
||||
else if (is_space(*it) && (i == 0 || is_space(rval[i - 1])))
|
||||
{
|
||||
case BACKTICK:
|
||||
rval += *it;
|
||||
if (*it == '`')
|
||||
// Repeating space, skip it
|
||||
}
|
||||
else if (*it == '/' && is_next(it, buf.end(), "/*"))
|
||||
{
|
||||
auto comment_start = std::next(it, 2);
|
||||
if (comment_start != buf.end() && *comment_start != '!' && *comment_start != 'M')
|
||||
{
|
||||
my_state = NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case SINGLE_QUOTE:
|
||||
if (*it == '\'')
|
||||
{
|
||||
rval += '?';
|
||||
my_state = NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case DOUBLE_QUOTE:
|
||||
if (*it == '"')
|
||||
{
|
||||
rval += '?';
|
||||
my_state = NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case INLINE_COMMENT:
|
||||
if (is_next(it, buf.end(), "*/"))
|
||||
{
|
||||
// Comment end marker, return to normal parsing
|
||||
++it;
|
||||
my_state = NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case UNTIL_NEWLINE:
|
||||
if (is_next(it, buf.end(), "\r\n"))
|
||||
{
|
||||
++it;
|
||||
my_state = NONE;
|
||||
}
|
||||
else if (is_next(it, buf.end(), "\n") || is_next(it, buf.end(), "\r"))
|
||||
{
|
||||
my_state = NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (isspace(*it))
|
||||
{
|
||||
if (isspace(prev))
|
||||
// Non-executable comment
|
||||
while (it != buf.end())
|
||||
{
|
||||
// Repeating space, skip it
|
||||
continue;
|
||||
}
|
||||
*it = ' ';
|
||||
}
|
||||
else if (is_next(it, buf.end(), "/*"))
|
||||
{
|
||||
auto comment_start = std::next(it, 2);
|
||||
if (comment_start != buf.end()
|
||||
&& *comment_start != '!'
|
||||
&& *comment_start != 'M')
|
||||
{
|
||||
// Non-executable comment
|
||||
my_state = INLINE_COMMENT;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (is_next(it, buf.end(), "# ") || is_next(it, buf.end(), "-- "))
|
||||
{
|
||||
// End-of-line comment, jump to the next line if one exists
|
||||
my_state = UNTIL_NEWLINE;
|
||||
continue;
|
||||
}
|
||||
else if (isdigit(*it) && !isalpha(prev) && !isdigit(prev) && prev != '_')
|
||||
{
|
||||
auto num_end = probe_number(it, buf.end());
|
||||
|
||||
if (num_end.first)
|
||||
{
|
||||
if (is_negation(rval))
|
||||
if (is_next(it, buf.end(), "*/"))
|
||||
{
|
||||
// Remove the sign
|
||||
rval.resize(rval.size() - 1);
|
||||
// Comment end marker, return to normal parsing
|
||||
++it;
|
||||
break;
|
||||
}
|
||||
rval += '?';
|
||||
it = num_end.second;
|
||||
continue;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
switch (*it)
|
||||
else
|
||||
{
|
||||
case '\'':
|
||||
my_state = SINGLE_QUOTE;
|
||||
break;
|
||||
// Executable comment, treat it as normal SQL
|
||||
rval[i++] = *it;
|
||||
}
|
||||
}
|
||||
else if ((*it == '#' || *it == '-')
|
||||
&& (is_next(it, buf.end(), "# ") || is_next(it, buf.end(), "-- ")))
|
||||
{
|
||||
// End-of-line comment, jump to the next line if one exists
|
||||
while (it != buf.end())
|
||||
{
|
||||
if (*it == '\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (*it == '\r')
|
||||
{
|
||||
if ((is_next(it, buf.end(), "\r\n")))
|
||||
{
|
||||
++it;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case '"':
|
||||
my_state = DOUBLE_QUOTE;
|
||||
break;
|
||||
|
||||
case '`':
|
||||
my_state = BACKTICK;
|
||||
|
||||
/* falls through */
|
||||
default:
|
||||
rval += *it;
|
||||
++it;
|
||||
}
|
||||
if (it == buf.end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (is_digit(*it) && (i == 0 || (!is_alnum(rval[i - 1]) && rval[i - 1] != '_')))
|
||||
{
|
||||
auto num_end = probe_number(it, buf.end());
|
||||
|
||||
break;
|
||||
if (num_end.first)
|
||||
{
|
||||
if (is_negation(rval, i))
|
||||
{
|
||||
// Remove the sign
|
||||
i--;
|
||||
}
|
||||
rval[i++] = '?';
|
||||
it = num_end.second;
|
||||
}
|
||||
}
|
||||
else if (*it == '\'' || *it == '"')
|
||||
{
|
||||
char c = *it;
|
||||
it = find_char(std::next(it), buf.end(), c);
|
||||
rval[i++] = '?';
|
||||
}
|
||||
else if (*it == '`')
|
||||
{
|
||||
auto start = it;
|
||||
it = find_char(std::next(it), buf.end(), '`');
|
||||
std::copy(start, it, &rval[i]);
|
||||
i += std::distance(start, it);
|
||||
rval[i++] = '`';
|
||||
}
|
||||
else
|
||||
{
|
||||
rval[i++] = *it;
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink the buffer so that the internal bookkeeping of std::string remains up to date
|
||||
rval.resize(i);
|
||||
|
||||
buf.release();
|
||||
|
||||
return rval;
|
||||
|
@ -9,7 +9,7 @@ ALTER EVENT e1 DO SELECT ?;
|
||||
ALTER EVENT e1 ON SCHEDULE AT ? ON COMPLETION PRESERVE DISABLE;
|
||||
ALTER TABLE `@0023sql1` RENAME `#sql-1`;
|
||||
ALTER TABLE t1 ADD INDEX (c13) COMMENT ?;
|
||||
ALTER TABLE t1 ADD PARTITION IF NOT EXISTS(PARTITION `p5` VALUES LESS THAN (?)COMMENT \'?);
|
||||
ALTER TABLE t1 ADD PARTITION IF NOT EXISTS(PARTITION `p5` VALUES LESS THAN (?)COMMENT ?);
|
||||
ALTER TABLE `t1` ADD PRIMARY KEY (`a`);
|
||||
alter table t1 change a a enum(?,?,?,?,?,?,?,?) character set utf16;
|
||||
alter table t1 change a a int `FKEY1`=?;
|
||||
|
@ -35,5 +35,15 @@ describe("Core Parameters", function() {
|
||||
.should.be.fulfilled
|
||||
})
|
||||
|
||||
it("will not modify static parameters", function() {
|
||||
return set_value("threads", "1")
|
||||
.should.be.rejected
|
||||
})
|
||||
|
||||
it("does not accept unknown parameters", function() {
|
||||
return set_value("quantum_compute", "yes, please")
|
||||
.should.be.rejected
|
||||
})
|
||||
|
||||
after(stopMaxScale)
|
||||
});
|
||||
|
Reference in New Issue
Block a user