提交 a321aa81 编写于 作者: A Alexey Milovidov

dbms: added value 2 for setting 'readonly'; ignoring settings from remote...

dbms: added value 2 for setting 'readonly'; ignoring settings from remote client in readonly mode [#METR-15168].
上级 8cf8d2d2
......@@ -22,6 +22,20 @@ public:
Context & target = ast.global ? context.getGlobalContext() : context.getSessionContext();
/** Значение readonly понимается следующим образом:
* 0 - можно всё.
* 1 - можно делать только запросы на чтение; в том числе, нельзя менять настройки.
* 2 - можно делать только запросы на чтение и можно менять настройки, кроме настройки readonly.
*/
if (context.getSettingsRef().limits.readonly == 1)
throw Exception("Cannot execute SET query in readonly mode", ErrorCodes::READONLY);
if (context.getSettingsRef().limits.readonly > 1)
for (ASTSetQuery::Changes::const_iterator it = ast.changes.begin(); it != ast.changes.end(); ++it)
if (it->name == "readonly")
throw Exception("Cannot modify 'readonly' setting in readonly mode", ErrorCodes::READONLY);
for (ASTSetQuery::Changes::const_iterator it = ast.changes.begin(); it != ast.changes.end(); ++it)
target.setSetting(it->name, it->value);
}
......
......@@ -63,7 +63,8 @@ struct Limits
M(SettingUInt64, max_ast_depth, 1000) /** Проверяются не во время парсинга, */ \
M(SettingUInt64, max_ast_elements, 10000) /** а уже после парсинга запроса. */ \
\
M(SettingBool, readonly, false) \
/** 0 - можно всё. 1 - только запросы на чтение. 2 - только запросы на чтение, а также изменение настроек, кроме настройки readonly. */ \
M(SettingUInt64, readonly, 0) \
\
/** Ограничения для максимального размера множества, получающегося при выполнении секции IN. */ \
M(SettingUInt64, max_rows_in_set, 0) \
......@@ -130,6 +131,22 @@ struct Limits
#undef TRY_SET
}
/// Пропустить сериализованное в бинарном виде значение из буфера.
bool tryIgnore(const String & name, ReadBuffer & buf)
{
#define TRY_IGNORE(TYPE, NAME, DEFAULT) \
else if (name == #NAME) decltype(NAME)(DEFAULT).set(buf);
if (false) {}
APPLY_FOR_LIMITS(TRY_IGNORE)
else
return false;
return true;
#undef TRY_IGNORE
}
/** Установить настройку по имени. Прочитать значение в текстовом виде из строки (например, из конфига, или из параметра URL).
*/
bool trySet(const String & name, const String & value)
......
......@@ -126,6 +126,9 @@ struct Settings
/// Установить настройку по имени. Прочитать сериализованное в бинарном виде значение из буфера (для межсерверного взаимодействия).
void set(const String & name, ReadBuffer & buf);
/// Пропустить сериализованное в бинарном виде значение из буфера.
void ignore(const String & name, ReadBuffer & buf);
/** Установить настройку по имени. Прочитать значение в текстовом виде из строки (например, из конфига, или из параметра URL).
*/
void set(const String & name, const String & value);
......@@ -136,8 +139,8 @@ struct Settings
void setProfile(const String & profile_name, Poco::Util::AbstractConfiguration & config);
/// Прочитать настройки из буфера. Они записаны как набор name-value пар, идущих подряд, заканчивающихся пустым name.
/// Если выставлен флаг check_readonly, в настройках выставлено readonly, но пришли какие-то изменения кинуть исключение.
void deserialize(ReadBuffer & buf, bool check_readonly = false);
/// Если в настройках выставлено readonly=1, то игнорировать настройки.
void deserialize(ReadBuffer & buf);
/// Записать изменённые настройки в буфер. (Например, для отправки на удалённый сервер.)
void serialize(WriteBuffer & buf) const;
......
......@@ -123,9 +123,12 @@ private:
/// Распарсенный запрос. Оттуда берутся некоторые настройки (формат).
ASTPtr parsed_query;
/// Последнее полученное от сервера исключение.
/// Последнее полученное от сервера исключение. Для кода возврата в неинтерактивном режиме.
ExceptionPtr last_exception;
/// Было ли в последнем запросе исключение.
bool got_exception = false;
Stopwatch watch;
/// С сервера периодически приходит информация, о том, сколько прочитано данных за прошедшее время.
......@@ -479,6 +482,7 @@ private:
return false;
resetOutput();
got_exception = false;
watch.restart();
......@@ -512,25 +516,29 @@ private:
else
processOrdinaryQuery();
if (set_query)
/// В случае исключения, не будем менять контекст (текущая БД, настройки) на клиенте.
if (!got_exception)
{
/// Запоминаем все изменения в настройках, чтобы не потерять их при разрыве соединения.
for (ASTSetQuery::Changes::const_iterator it = set_query->changes.begin(); it != set_query->changes.end(); ++it)
if (set_query)
{
if (it->name == "profile")
current_profile = it->value.safeGet<String>();
else
context.setSetting(it->name, it->value);
/// Запоминаем все изменения в настройках, чтобы не потерять их при разрыве соединения.
for (ASTSetQuery::Changes::const_iterator it = set_query->changes.begin(); it != set_query->changes.end(); ++it)
{
if (it->name == "profile")
current_profile = it->value.safeGet<String>();
else
context.setSetting(it->name, it->value);
}
}
}
if (use_query)
{
const String & new_database = use_query->database;
/// Если клиент инициирует пересоединение, он берет настройки из конфига
config().setString("database", new_database);
/// Если connection инициирует пересоединение, он использует свою переменную
connection->setDefaultDatabase(new_database);
if (use_query)
{
const String & new_database = use_query->database;
/// Если клиент инициирует пересоединение, он берет настройки из конфига
config().setString("database", new_database);
/// Если connection инициирует пересоединение, он использует свою переменную
connection->setDefaultDatabase(new_database);
}
}
if (is_interactive)
......@@ -959,6 +967,7 @@ private:
void onException(const Exception & e)
{
resetOutput();
got_exception = true;
std::cerr << "Received exception from server:" << std::endl
<< "Code: " << e.code() << ". " << e.displayText();
......
......@@ -82,7 +82,7 @@ void InterpreterQuery::execute(WriteBuffer & ostr, ReadBuffer * remaining_data_i
}
else if (typeid_cast<ASTSetQuery *>(&*query_ptr))
{
throwIfReadOnly();
/// readonly проверяется внутри InterpreterSetQuery
InterpreterSetQuery interpreter(query_ptr, context);
interpreter.execute();
}
......@@ -174,7 +174,7 @@ BlockIO InterpreterQuery::execute()
}
else if (typeid_cast<ASTSetQuery *>(&*query_ptr))
{
throwIfReadOnly();
/// readonly проверяется внутри InterpreterSetQuery
InterpreterSetQuery interpreter(query_ptr, context);
interpreter.execute();
}
......
......@@ -32,6 +32,20 @@ void Settings::set(const String & name, ReadBuffer & buf)
#undef TRY_SET
}
/// Пропустить сериализованное в бинарном виде значение из буфера.
void Settings::ignore(const String & name, ReadBuffer & buf)
{
#define TRY_IGNORE(TYPE, NAME, DEFAULT) \
else if (name == #NAME) decltype(NAME)(DEFAULT).set(buf);
if (false) {}
APPLY_FOR_SETTINGS(TRY_IGNORE)
else if (!limits.tryIgnore(name, buf))
throw Exception("Unknown setting " + name, ErrorCodes::UNKNOWN_SETTING);
#undef TRY_IGNORE
}
/** Установить настройку по имени. Прочитать значение в текстовом виде из строки (например, из конфига, или из параметра URL).
*/
void Settings::set(const String & name, const String & value)
......@@ -71,9 +85,9 @@ void Settings::setProfile(const String & profile_name, Poco::Util::AbstractConfi
/// Прочитать настройки из буфера. Они записаны как набор name-value пар, идущих подряд, заканчивающихся пустым name.
/// Если выставлен флаг check_readonly, в настройках выставлено readonly, но пришли какие-то изменения кинуть исключение.
void Settings::deserialize(ReadBuffer & buf, bool check_readonly)
void Settings::deserialize(ReadBuffer & buf)
{
bool readonly = limits.readonly;
bool readonly = limits.readonly == 1; /// Если readonly = 2, то можно менять настройки.
while (true)
{
......@@ -84,10 +98,10 @@ void Settings::deserialize(ReadBuffer & buf, bool check_readonly)
if (name.empty())
break;
if (check_readonly && readonly)
throw Exception("Can't set setting " + name + ". Settings are readonly.", ErrorCodes::READONLY);
set(name, buf);
if (!readonly)
set(name, buf);
else
ignore(name, buf);
}
}
......
......@@ -539,7 +539,7 @@ void TCPHandler::receiveQuery()
/// Настройки на отдельный запрос.
if (client_revision >= DBMS_MIN_REVISION_WITH_PER_QUERY_SETTINGS)
{
query_context.getSettingsRef().deserialize(*in, true);
query_context.getSettingsRef().deserialize(*in);
}
readVarUInt(stage, *in);
......
......@@ -12,7 +12,7 @@ TableFunctionPtr TableFunctionFactory::get(
const String & name,
const Context & context) const
{
if (context.getSettings().limits.readonly)
if (context.getSettings().limits.readonly == 1) /** Например, для readonly = 2 - разрешено. */
throw Exception("Table functions are forbidden in readonly mode", ErrorCodes::READONLY);
if (name == "merge") return new TableFunctionMerge;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册