Merge pull request #10109 from vitlibar/RBAC-9

RBAC-9
This commit is contained in:
Vitaly Baranov 2020-04-09 16:26:24 +03:00 committed by GitHub
commit 36af2ab264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 440 additions and 329 deletions

View File

@ -67,16 +67,19 @@ void Suggest::load(const ConnectionParameters & connection_parameters, size_t su
Suggest::Suggest()
{
/// Keywords may be not up to date with ClickHouse parser.
words = {"CREATE", "DATABASE", "IF", "NOT", "EXISTS", "TEMPORARY", "TABLE", "ON", "CLUSTER", "DEFAULT",
"MATERIALIZED", "ALIAS", "ENGINE", "AS", "VIEW", "POPULATE", "SETTINGS", "ATTACH", "DETACH", "DROP",
"RENAME", "TO", "ALTER", "ADD", "MODIFY", "CLEAR", "COLUMN", "AFTER", "COPY", "PROJECT",
"PRIMARY", "KEY", "CHECK", "PARTITION", "PART", "FREEZE", "FETCH", "FROM", "SHOW", "INTO",
"OUTFILE", "FORMAT", "TABLES", "DATABASES", "LIKE", "PROCESSLIST", "CASE", "WHEN", "THEN", "ELSE",
"END", "DESCRIBE", "DESC", "USE", "SET", "OPTIMIZE", "FINAL", "DEDUPLICATE", "INSERT", "VALUES",
"SELECT", "DISTINCT", "SAMPLE", "ARRAY", "JOIN", "GLOBAL", "LOCAL", "ANY", "ALL", "INNER",
"LEFT", "RIGHT", "FULL", "OUTER", "CROSS", "USING", "PREWHERE", "WHERE", "GROUP", "BY",
"WITH", "TOTALS", "HAVING", "ORDER", "COLLATE", "LIMIT", "UNION", "AND", "OR", "ASC",
"IN", "KILL", "QUERY", "SYNC", "ASYNC", "TEST", "BETWEEN", "TRUNCATE"};
words = {"CREATE", "DATABASE", "IF", "NOT", "EXISTS", "TEMPORARY", "TABLE", "ON", "CLUSTER", "DEFAULT",
"MATERIALIZED", "ALIAS", "ENGINE", "AS", "VIEW", "POPULATE", "SETTINGS", "ATTACH", "DETACH", "DROP",
"RENAME", "TO", "ALTER", "ADD", "MODIFY", "CLEAR", "COLUMN", "AFTER", "COPY", "PROJECT",
"PRIMARY", "KEY", "CHECK", "PARTITION", "PART", "FREEZE", "FETCH", "FROM", "SHOW", "INTO",
"OUTFILE", "FORMAT", "TABLES", "DATABASES", "LIKE", "PROCESSLIST", "CASE", "WHEN", "THEN", "ELSE",
"END", "DESCRIBE", "DESC", "USE", "SET", "OPTIMIZE", "FINAL", "DEDUPLICATE", "INSERT", "VALUES",
"SELECT", "DISTINCT", "SAMPLE", "ARRAY", "JOIN", "GLOBAL", "LOCAL", "ANY", "ALL", "INNER",
"LEFT", "RIGHT", "FULL", "OUTER", "CROSS", "USING", "PREWHERE", "WHERE", "GROUP", "BY",
"WITH", "TOTALS", "HAVING", "ORDER", "COLLATE", "LIMIT", "UNION", "AND", "OR", "ASC",
"IN", "KILL", "QUERY", "SYNC", "ASYNC", "TEST", "BETWEEN", "TRUNCATE", "USER", "ROLE",
"PROFILE", "QUOTA", "POLICY", "ROW", "GRANT", "REVOKE", "OPTION", "ADMIN", "EXCEPT", "REPLACE",
"IDENTIFIED", "HOST", "NAME", "READONLY", "WRITABLE", "PERMISSIVE", "FOR", "RESTRICTIVE", "FOR", "RANDOMIZED",
"INTERVAL", "LIMITS", "ONLY", "TRACKING", "IP", "REGEXP"};
}
void Suggest::loadImpl(Connection & connection, const ConnectionTimeouts & timeouts, size_t suggestion_limit)

View File

@ -408,9 +408,10 @@ boost::shared_ptr<const AccessRights> ContextAccess::calculateResultAccess(bool
static const AccessFlags dictionary_ddl = AccessType::CREATE_DICTIONARY | AccessType::DROP_DICTIONARY;
static const AccessFlags table_and_dictionary_ddl = table_ddl | dictionary_ddl;
static const AccessFlags write_table_access = AccessType::INSERT | AccessType::OPTIMIZE;
static const AccessFlags write_dcl_access = AccessType::ACCESS_MANAGEMENT - AccessType::SHOW_ACCESS;
if (readonly_)
merged_access->revoke(write_table_access | table_and_dictionary_ddl | AccessType::SYSTEM | AccessType::KILL_QUERY | AccessType::ACCESS_MANAGEMENT);
merged_access->revoke(write_table_access | table_and_dictionary_ddl | write_dcl_access | AccessType::SYSTEM | AccessType::KILL_QUERY);
if (readonly_ == 1)
{

View File

@ -1,7 +1,5 @@
#include <Access/EnabledRowPolicies.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTExpressionList.h>
#include <boost/smart_ptr/make_shared.hpp>
#include <Parsers/makeASTForLogicalFunction.h>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/copy.hpp>
@ -35,19 +33,17 @@ ASTPtr EnabledRowPolicies::getCondition(const String & database, const String &
ASTPtr EnabledRowPolicies::getCondition(const String & database, const String & table_name, ConditionType type, const ASTPtr & extra_condition) const
{
ASTPtr main_condition = getCondition(database, table_name, type);
if (!main_condition)
return extra_condition;
if (!extra_condition)
return main_condition;
auto function = std::make_shared<ASTFunction>();
auto exp_list = std::make_shared<ASTExpressionList>();
function->name = "and";
function->arguments = exp_list;
function->children.push_back(exp_list);
exp_list->children.push_back(main_condition);
exp_list->children.push_back(extra_condition);
return function;
ASTPtr condition = getCondition(database, table_name, type);
if (condition && extra_condition)
condition = makeASTForLogicalAnd({condition, extra_condition});
else if (!condition)
condition = extra_condition;
bool value;
if (tryGetLiteralBool(condition.get(), value) && value)
condition = nullptr; /// The condition is always true, no need to check it.
return condition;
}

View File

@ -1,97 +1,19 @@
#include <Access/RowPolicyCache.h>
#include <Access/EnabledRowPolicies.h>
#include <Access/AccessControlManager.h>
#include <Parsers/ASTLiteral.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ExpressionListParsers.h>
#include <Parsers/parseQuery.h>
#include <Parsers/makeASTForLogicalFunction.h>
#include <Common/Exception.h>
#include <Common/quoteString.h>
#include <ext/range.h>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
namespace DB
{
namespace
{
bool tryGetLiteralBool(const IAST & ast, bool & value)
{
try
{
if (const ASTLiteral * literal = ast.as<ASTLiteral>())
{
value = !literal->value.isNull() && applyVisitor(FieldVisitorConvertToNumber<bool>(), literal->value);
return true;
}
return false;
}
catch (...)
{
return false;
}
}
ASTPtr applyFunctionAND(ASTs arguments)
{
bool const_arguments = true;
boost::range::remove_erase_if(arguments, [&](const ASTPtr & argument) -> bool
{
bool b;
if (!tryGetLiteralBool(*argument, b))
return false;
const_arguments &= b;
return true;
});
if (!const_arguments)
return std::make_shared<ASTLiteral>(Field{UInt8(0)});
if (arguments.empty())
return std::make_shared<ASTLiteral>(Field{UInt8(1)});
if (arguments.size() == 1)
return arguments[0];
auto function = std::make_shared<ASTFunction>();
auto exp_list = std::make_shared<ASTExpressionList>();
function->name = "and";
function->arguments = exp_list;
function->children.push_back(exp_list);
exp_list->children = std::move(arguments);
return function;
}
ASTPtr applyFunctionOR(ASTs arguments)
{
bool const_arguments = false;
boost::range::remove_erase_if(arguments, [&](const ASTPtr & argument) -> bool
{
bool b;
if (!tryGetLiteralBool(*argument, b))
return false;
const_arguments |= b;
return true;
});
if (const_arguments)
return std::make_shared<ASTLiteral>(Field{UInt8(1)});
if (arguments.empty())
return std::make_shared<ASTLiteral>(Field{UInt8(0)});
if (arguments.size() == 1)
return arguments[0];
auto function = std::make_shared<ASTFunction>();
auto exp_list = std::make_shared<ASTExpressionList>();
function->name = "or";
function->arguments = exp_list;
function->children.push_back(exp_list);
exp_list->children = std::move(arguments);
return function;
}
using ConditionType = RowPolicy::ConditionType;
constexpr size_t MAX_CONDITION_TYPE = RowPolicy::MAX_CONDITION_TYPE;
@ -111,10 +33,16 @@ namespace
ASTPtr getResult() &&
{
/// Process permissive conditions.
restrictions.push_back(applyFunctionOR(std::move(permissions)));
restrictions.push_back(makeASTForLogicalOr(std::move(permissions)));
/// Process restrictive conditions.
return applyFunctionAND(std::move(restrictions));
auto condition = makeASTForLogicalAnd(std::move(restrictions));
bool value;
if (tryGetLiteralBool(condition.get(), value) && value)
condition = nullptr; /// The condition is always true, no need to check it.
return condition;
}
private:

View File

@ -34,7 +34,7 @@ void updateQuotaFromQueryImpl(Quota & quota, const ASTCreateQuotaQuery & query,
auto duration = query_limits.duration;
auto it = boost::range::find_if(quota_all_limits, [&](const Quota::Limits & x) { return x.duration == duration; });
if (query_limits.unset_tracking)
if (query_limits.drop)
{
if (it != quota_all_limits.end())
quota_all_limits.erase(it);
@ -59,6 +59,8 @@ void updateQuotaFromQueryImpl(Quota & quota, const ASTCreateQuotaQuery & query,
{
if (query_limits.max[resource_type])
quota_limits.max[resource_type] = *query_limits.max[resource_type];
else
quota_limits.max[resource_type] = Quota::UNLIMITED;
}
}

View File

@ -7,6 +7,7 @@
#include <Parsers/ASTCreateSettingsProfileQuery.h>
#include <Parsers/ASTShowCreateAccessEntityQuery.h>
#include <Parsers/ASTExtendedRoleSet.h>
#include <Parsers/ASTSettingsProfileElement.h>
#include <Parsers/ExpressionListParsers.h>
#include <Parsers/formatAST.h>
#include <Parsers/parseQuery.h>
@ -101,6 +102,8 @@ namespace
query->settings = profile.elements.toAST();
else
query->settings = profile.elements.toASTWithNames(*manager);
if (query->settings)
query->settings->setUseInheritKeyword(true);
}
if (!profile.to_roles.empty())
@ -133,7 +136,7 @@ namespace
create_query_limits.duration = limits.duration;
create_query_limits.randomize_interval = limits.randomize_interval;
for (auto resource_type : ext::range(Quota::MAX_RESOURCE_TYPE))
if (limits.max[resource_type])
if (limits.max[resource_type] != Quota::UNLIMITED)
create_query_limits.max[resource_type] = limits.max[resource_type];
query->all_limits.push_back(create_query_limits);
}

View File

@ -28,16 +28,17 @@ namespace
}
void formatLimit(ResourceType resource_type, ResourceAmount max, const IAST::FormatSettings & settings)
void formatLimit(ResourceType resource_type, ResourceAmount max, bool first, const IAST::FormatSettings & settings)
{
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " MAX " << Quota::resourceTypeToKeyword(resource_type)
<< (settings.hilite ? IAST::hilite_none : "");
if (first)
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " MAX" << (settings.hilite ? IAST::hilite_none : "");
else
settings.ostr << ",";
settings.ostr << (settings.hilite ? IAST::hilite_operator : "") << " = " << (settings.hilite ? IAST::hilite_none : "");
settings.ostr << " " << (settings.hilite ? IAST::hilite_keyword : "") << Quota::resourceTypeToKeyword(resource_type)
<< (settings.hilite ? IAST::hilite_none : "") << " ";
if (max == Quota::UNLIMITED)
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << "ANY" << (settings.hilite ? IAST::hilite_none : "");
else if (resource_type == Quota::EXECUTION_TIME)
if (resource_type == Quota::EXECUTION_TIME)
settings.ostr << Quota::executionTimeToSeconds(max);
else
settings.ostr << max;
@ -59,9 +60,9 @@ namespace
<< interval_kind.toKeyword()
<< (settings.hilite ? IAST::hilite_none : "");
if (limits.unset_tracking)
if (limits.drop)
{
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " UNSET TRACKING" << (settings.hilite ? IAST::hilite_none : "");
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " NO LIMITS" << (settings.hilite ? IAST::hilite_none : "");
}
else
{
@ -70,14 +71,12 @@ namespace
{
if (limits.max[resource_type])
{
if (limit_found)
settings.ostr << ",";
formatLimit(resource_type, *limits.max[resource_type], !limit_found, settings);
limit_found = true;
formatLimit(resource_type, *limits.max[resource_type], settings);
}
}
if (!limit_found)
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " TRACKING" << (settings.hilite ? IAST::hilite_none : "");
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " TRACKING ONLY" << (settings.hilite ? IAST::hilite_none : "");
}
}

View File

@ -13,17 +13,16 @@ class ASTExtendedRoleSet;
/** CREATE QUOTA [IF NOT EXISTS | OR REPLACE] name
* [KEYED BY {'none' | 'user name' | 'ip address' | 'client key' | 'client key or user name' | 'client key or ip address'}]
* [FOR [RANDOMIZED] INTERVAL number {SECOND | MINUTE | HOUR | DAY}
* {[SET] MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = {number | ANY} } [,...] |
* [SET] TRACKING} [,...]]
* {MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = number} [,...] |
* NO LIMITS | TRACKING ONLY} [,...]]
* [TO {role [,...] | ALL | ALL EXCEPT role [,...]}]
*
* ALTER QUOTA [IF EXISTS] name
* [RENAME TO new_name]
* [KEYED BY {'none' | 'user name' | 'ip address' | 'client key' | 'client key or user name' | 'client key or ip address'}]
* [FOR [RANDOMIZED] INTERVAL number {SECOND | MINUTE | HOUR | DAY}
* {[SET] MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = {number | ANY} } [,...] |
* [SET] TRACKING |
* UNSET TRACKING} [,...]]
* {MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = number} [,...] |
* NO LIMITS | TRACKING ONLY} [,...]]
* [TO {role [,...] | ALL | ALL EXCEPT role [,...]}]
*/
class ASTCreateQuotaQuery : public IAST, public ASTQueryWithOnCluster
@ -48,7 +47,7 @@ public:
struct Limits
{
std::optional<ResourceAmount> max[MAX_RESOURCE_TYPE];
bool unset_tracking = false;
bool drop = false;
std::chrono::seconds duration = std::chrono::seconds::zero();
bool randomize_interval = false;
};

View File

@ -12,10 +12,12 @@ class ASTExtendedRoleSet;
/** CREATE SETTINGS PROFILE [IF NOT EXISTS | OR REPLACE] name
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
* [TO {role [,...] | ALL | ALL EXCEPT role [,...]}]
*
* ALTER SETTINGS PROFILE [IF EXISTS] name
* [RENAME TO new_name]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
* [TO {role [,...] | ALL | ALL EXCEPT role [,...]}]
*/
class ASTCreateSettingsProfileQuery : public IAST, public ASTQueryWithOnCluster
{

View File

@ -109,7 +109,7 @@ namespace
{
if (std::exchange(need_comma, true))
settings.ostr << ", ";
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << "NAME REGEXP " << (settings.hilite ? IAST::hilite_none : "");
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << "REGEXP " << (settings.hilite ? IAST::hilite_none : "");
bool need_comma2 = false;
for (const auto & host_regexp : name_regexps)
{

View File

@ -13,14 +13,14 @@ class ASTSettingsProfileElements;
/** CREATE USER [IF NOT EXISTS | OR REPLACE] name
* [IDENTIFIED [WITH {NO_PASSWORD|PLAINTEXT_PASSWORD|SHA256_PASSWORD|SHA256_HASH|DOUBLE_SHA1_PASSWORD|DOUBLE_SHA1_HASH}] BY {'password'|'hash'}]
* [HOST {LOCAL | NAME 'name' | NAME REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [DEFAULT ROLE role [,...]]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
*
* ALTER USER [IF EXISTS] name
* [RENAME TO new_name]
* [IDENTIFIED [WITH {PLAINTEXT_PASSWORD|SHA256_PASSWORD|DOUBLE_SHA1_PASSWORD}] BY {'password'|'hash'}]
* [[ADD|DROP] HOST {LOCAL | NAME 'name' | NAME REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [[ADD|DROP] HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [DEFAULT ROLE role [,...] | ALL | ALL EXCEPT role [,...] ]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
*/

View File

@ -25,7 +25,8 @@ void ASTSettingsProfileElement::formatImpl(const FormatSettings & settings, Form
{
if (!parent_profile.empty())
{
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << "PROFILE " << (settings.hilite ? IAST::hilite_none : "");
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << (use_inherit_keyword ? "INHERIT" : "PROFILE") << " "
<< (settings.hilite ? IAST::hilite_none : "");
formatProfileNameOrID(parent_profile, id_mode, settings);
return;
}
@ -85,4 +86,11 @@ void ASTSettingsProfileElements::formatImpl(const FormatSettings & settings, For
}
}
void ASTSettingsProfileElements::setUseInheritKeyword(bool use_inherit_keyword_)
{
for (auto & element : elements)
element->use_inherit_keyword = use_inherit_keyword_;
}
}

View File

@ -19,6 +19,7 @@ public:
Field max_value;
std::optional<bool> readonly;
bool id_mode = false; /// If true then `parent_profile` keeps UUID, not a name.
bool use_inherit_keyword = false; /// If true then this element is a part of ASTCreateSettingsProfileQuery.
bool empty() const { return parent_profile.empty() && name.empty(); }
@ -41,5 +42,7 @@ public:
String getID(char) const override { return "SettingsProfileElements"; }
ASTPtr clone() const override { return std::make_shared<ASTSettingsProfileElements>(*this); }
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
void setUseInheritKeyword(bool use_inherit_keyword_);
};
}

View File

@ -126,7 +126,7 @@ public:
return parse(pos, node, expected);
}
virtual ~IParser() {}
virtual ~IParser() = default;
};
using ParserPtr = std::unique_ptr<IParser>;

View File

@ -63,12 +63,22 @@ namespace
});
}
bool parseLimit(IParserBase::Pos & pos, Expected & expected, ResourceType & resource_type, ResourceAmount & max)
bool parseLimit(IParserBase::Pos & pos, Expected & expected, bool first, ResourceType & resource_type, ResourceAmount & max)
{
return IParserBase::wrapParseImpl(pos, [&]
{
if (!ParserKeyword{"MAX"}.ignore(pos, expected))
return false;
if (first)
{
if (!ParserKeyword{"MAX"}.ignore(pos, expected))
return false;
}
else
{
if (!ParserToken{TokenType::Comma}.ignore(pos, expected))
return false;
ParserKeyword{"MAX"}.ignore(pos, expected);
}
bool resource_type_set = false;
for (auto rt : ext::range_with_static_cast<Quota::ResourceType>(Quota::MAX_RESOURCE_TYPE))
@ -83,9 +93,6 @@ namespace
if (!resource_type_set)
return false;
if (!ParserToken{TokenType::Equals}.ignore(pos, expected))
return false;
ASTPtr max_ast;
if (ParserNumber{}.parse(pos, max_ast, expected))
{
@ -95,10 +102,6 @@ namespace
else
max = applyVisitor(FieldVisitorConvertToNumber<ResourceAmount>(), max_field);
}
else if (ParserKeyword{"ANY"}.ignore(pos, expected))
{
max = Quota::UNLIMITED;
}
else
return false;
@ -106,18 +109,7 @@ namespace
});
}
bool parseCommaAndLimit(IParserBase::Pos & pos, Expected & expected, ResourceType & resource_type, ResourceAmount & max)
{
return IParserBase::wrapParseImpl(pos, [&]
{
if (!ParserToken{TokenType::Comma}.ignore(pos, expected))
return false;
return parseLimit(pos, expected, resource_type, max);
});
}
bool parseLimits(IParserBase::Pos & pos, Expected & expected, bool alter, ASTCreateQuotaQuery::Limits & limits)
bool parseLimits(IParserBase::Pos & pos, Expected & expected, ASTCreateQuotaQuery::Limits & limits)
{
return IParserBase::wrapParseImpl(pos, [&]
{
@ -142,23 +134,22 @@ namespace
new_limits.duration = std::chrono::seconds(static_cast<UInt64>(num_intervals * interval_kind.toAvgSeconds()));
if (alter && ParserKeyword{"UNSET TRACKING"}.ignore(pos, expected))
if (ParserKeyword{"NO LIMITS"}.ignore(pos, expected))
{
new_limits.unset_tracking = true;
new_limits.drop = true;
}
else if (ParserKeyword{"SET TRACKING"}.ignore(pos, expected) || ParserKeyword{"TRACKING"}.ignore(pos, expected))
else if (ParserKeyword{"TRACKING ONLY"}.ignore(pos, expected))
{
}
else
{
ParserKeyword{"SET"}.ignore(pos, expected);
ResourceType resource_type;
ResourceAmount max;
if (!parseLimit(pos, expected, resource_type, max))
if (!parseLimit(pos, expected, true, resource_type, max))
return false;
new_limits.max[resource_type] = max;
while (parseCommaAndLimit(pos, expected, resource_type, max))
while (parseLimit(pos, expected, false, resource_type, max))
new_limits.max[resource_type] = max;
}
@ -167,7 +158,7 @@ namespace
});
}
bool parseAllLimits(IParserBase::Pos & pos, Expected & expected, bool alter, std::vector<ASTCreateQuotaQuery::Limits> & all_limits)
bool parseAllLimits(IParserBase::Pos & pos, Expected & expected, std::vector<ASTCreateQuotaQuery::Limits> & all_limits)
{
return IParserBase::wrapParseImpl(pos, [&]
{
@ -175,7 +166,7 @@ namespace
do
{
ASTCreateQuotaQuery::Limits limits;
if (!parseLimits(pos, expected, alter, limits))
if (!parseLimits(pos, expected, limits))
{
all_limits.resize(old_size);
return false;
@ -199,6 +190,14 @@ namespace
return true;
});
}
bool parseOnCluster(IParserBase::Pos & pos, Expected & expected, String & cluster)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return ParserKeyword{"ON"}.ignore(pos, expected) && ASTQueryWithOnCluster::parse(pos, cluster, expected);
});
}
}
@ -238,16 +237,10 @@ bool ParserCreateQuotaQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
if (!parseIdentifierOrStringLiteral(pos, expected, name))
return false;
String cluster;
if (ParserKeyword{"ON"}.ignore(pos, expected))
{
if (!ASTQueryWithOnCluster::parse(pos, cluster, expected))
return false;
}
String new_name;
std::optional<KeyType> key_type;
std::vector<ASTCreateQuotaQuery::Limits> all_limits;
String cluster;
while (true)
{
@ -257,7 +250,10 @@ bool ParserCreateQuotaQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
if (!key_type && parseKeyType(pos, expected, key_type))
continue;
if (parseAllLimits(pos, expected, alter, all_limits))
if (parseAllLimits(pos, expected, all_limits))
continue;
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
break;
@ -266,6 +262,9 @@ bool ParserCreateQuotaQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
std::shared_ptr<ASTExtendedRoleSet> roles;
parseToRoles(pos, expected, attach_mode, roles);
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
auto query = std::make_shared<ASTCreateQuotaQuery>();
node = query;

View File

@ -9,17 +9,16 @@ namespace DB
* CREATE QUOTA [IF NOT EXISTS | OR REPLACE] name
* [KEYED BY {'none' | 'user name' | 'ip address' | 'client key' | 'client key or user name' | 'client key or ip address'}]
* [FOR [RANDOMIZED] INTERVAL number {SECOND | MINUTE | HOUR | DAY}
* {[SET] MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = {number | ANY} } [,...] |
* [SET] TRACKING} [,...]]
* {MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = number} [,...] |
* NO LIMITS | TRACKING ONLY} [,...]]
* [TO {role [,...] | ALL | ALL EXCEPT role [,...]}]
*
* ALTER QUOTA [IF EXISTS] name
* [RENAME TO new_name]
* [KEYED BY {'none' | 'user name' | 'ip address' | 'client key' | 'client key or user name' | 'client key or ip address'}]
* [FOR [RANDOMIZED] INTERVAL number {SECOND | MINUTE | HOUR | DAY}
* {[SET] MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = {number | ANY} } [,...] |
* [SET] TRACKING |
* UNSET TRACKING} [,...]]
* {MAX {{QUERIES | ERRORS | RESULT ROWS | RESULT BYTES | READ ROWS | READ BYTES | EXECUTION TIME} = number} } [,...] |
* NO LIMITS | TRACKING ONLY} [,...]]
* [TO {role [,...] | ALL | ALL EXCEPT role [,...]}]
*/
class ParserCreateQuotaQuery : public IParserBase

View File

@ -41,6 +41,14 @@ namespace
return true;
});
}
bool parseOnCluster(IParserBase::Pos & pos, Expected & expected, String & cluster)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return ParserKeyword{"ON"}.ignore(pos, expected) && ASTQueryWithOnCluster::parse(pos, cluster, expected);
});
}
}
@ -80,15 +88,10 @@ bool ParserCreateRoleQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (!parseRoleName(pos, expected, name))
return false;
String cluster;
if (ParserKeyword{"ON"}.ignore(pos, expected))
{
if (!ASTQueryWithOnCluster::parse(pos, cluster, expected))
return false;
}
String new_name;
std::shared_ptr<ASTSettingsProfileElements> settings;
String cluster;
while (true)
{
if (alter && parseRenameTo(pos, expected, new_name))
@ -97,6 +100,9 @@ bool ParserCreateRoleQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (parseSettings(pos, expected, attach_mode, settings))
continue;
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
break;
}

View File

@ -83,14 +83,13 @@ namespace
static constexpr char delete_op[] = "DELETE";
std::vector<const char *> ops;
bool keyword_for = false;
if (ParserKeyword{"FOR"}.ignore(pos, expected))
{
keyword_for = true;
do
{
if (ParserKeyword{"SELECT"}.ignore(pos, expected))
ops.push_back(select_op);
#if 0 /// INSERT, UPDATE, DELETE are not supported yet
else if (ParserKeyword{"INSERT"}.ignore(pos, expected))
ops.push_back(insert_op);
else if (ParserKeyword{"UPDATE"}.ignore(pos, expected))
@ -100,6 +99,7 @@ namespace
else if (ParserKeyword{"ALL"}.ignore(pos, expected))
{
}
#endif
else
return false;
}
@ -109,9 +109,11 @@ namespace
if (ops.empty())
{
ops.push_back(select_op);
#if 0 /// INSERT, UPDATE, DELETE are not supported yet
ops.push_back(insert_op);
ops.push_back(update_op);
ops.push_back(delete_op);
#endif
}
std::optional<ASTPtr> filter;
@ -123,14 +125,15 @@ namespace
if (!parseConditionalExpression(pos, expected, filter))
return false;
}
#if 0 /// INSERT, UPDATE, DELETE are not supported yet
if (ParserKeyword{"WITH CHECK"}.ignore(pos, expected))
{
keyword_with_check = true;
if (!parseConditionalExpression(pos, expected, check))
return false;
}
if (!keyword_for && !keyword_using && !keyword_with_check)
#endif
if (!keyword_using && !keyword_with_check)
return false;
if (filter && !check && !alter)
@ -200,6 +203,14 @@ namespace
return true;
});
}
bool parseOnCluster(IParserBase::Pos & pos, Expected & expected, String & cluster)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return ParserKeyword{"ON"}.ignore(pos, expected) && ASTQueryWithOnCluster::parse(pos, cluster, expected);
});
}
}
@ -243,16 +254,10 @@ bool ParserCreateRowPolicyQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
|| !parseDatabaseAndTableName(pos, expected, database, table_name))
return false;
String cluster;
if (ParserKeyword{"ON"}.ignore(pos, expected))
{
if (!ASTQueryWithOnCluster::parse(pos, cluster, expected))
return false;
}
String new_policy_name;
std::optional<bool> is_restrictive;
std::vector<std::pair<ConditionType, ASTPtr>> conditions;
String cluster;
while (true)
{
@ -265,12 +270,18 @@ bool ParserCreateRowPolicyQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
if (parseMultipleConditions(pos, expected, alter, conditions))
continue;
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
break;
}
std::shared_ptr<ASTExtendedRoleSet> roles;
parseToRoles(pos, expected, attach_mode, roles);
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
auto query = std::make_shared<ASTCreateRowPolicyQuery>();
node = query;

View File

@ -33,7 +33,7 @@ namespace
return false;
ASTPtr new_settings_ast;
if (!ParserSettingsProfileElements{}.useIDMode(id_mode).parse(pos, new_settings_ast, expected))
if (!ParserSettingsProfileElements{}.useIDMode(id_mode).enableInheritKeyword(true).parse(pos, new_settings_ast, expected))
return false;
if (!settings)
@ -57,6 +57,14 @@ namespace
return true;
});
}
bool parseOnCluster(IParserBase::Pos & pos, Expected & expected, String & cluster)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return ParserKeyword{"ON"}.ignore(pos, expected) && ASTQueryWithOnCluster::parse(pos, cluster, expected);
});
}
}
@ -96,15 +104,10 @@ bool ParserCreateSettingsProfileQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
if (!parseIdentifierOrStringLiteral(pos, expected, name))
return false;
String cluster;
if (ParserKeyword{"ON"}.ignore(pos, expected))
{
if (!ASTQueryWithOnCluster::parse(pos, cluster, expected))
return false;
}
String new_name;
std::shared_ptr<ASTSettingsProfileElements> settings;
String cluster;
while (true)
{
if (alter && parseRenameTo(pos, expected, new_name))
@ -113,12 +116,18 @@ bool ParserCreateSettingsProfileQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
if (parseSettings(pos, expected, attach_mode, settings))
continue;
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
break;
}
std::shared_ptr<ASTExtendedRoleSet> to_roles;
parseToRoles(pos, expected, attach_mode, to_roles);
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
auto query = std::make_shared<ASTCreateSettingsProfileQuery>();
node = query;

View File

@ -7,11 +7,11 @@ namespace DB
{
/** Parses queries like
* CREATE SETTINGS PROFILE [IF NOT EXISTS | OR REPLACE] name
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | INHERIT 'profile_name'] [,...]
*
* ALTER SETTINGS PROFILE [IF EXISTS] name
* [RENAME TO new_name]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | INHERIT 'profile_name'] [,...]
*/
class ParserCreateSettingsProfileQuery : public IParserBase
{

View File

@ -166,7 +166,7 @@ namespace
{
new_hosts.addLocalHost();
}
else if (ParserKeyword{"NAME REGEXP"}.ignore(pos, expected))
else if (ParserKeyword{"REGEXP"}.ignore(pos, expected))
{
ASTPtr ast;
if (!ParserList{std::make_unique<ParserStringLiteral>(), std::make_unique<ParserToken>(TokenType::Comma), false}.parse(pos, ast, expected))
@ -250,6 +250,14 @@ namespace
return true;
});
}
bool parseOnCluster(IParserBase::Pos & pos, Expected & expected, String & cluster)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return ParserKeyword{"ON"}.ignore(pos, expected) && ASTQueryWithOnCluster::parse(pos, cluster, expected);
});
}
}
@ -290,13 +298,6 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (!parseUserName(pos, expected, name, host_pattern))
return false;
String cluster;
if (ParserKeyword{"ON"}.ignore(pos, expected))
{
if (!ASTQueryWithOnCluster::parse(pos, cluster, expected))
return false;
}
String new_name;
std::optional<String> new_host_pattern;
std::optional<Authentication> authentication;
@ -305,6 +306,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
std::optional<AllowedClientHosts> remove_hosts;
std::shared_ptr<ASTExtendedRoleSet> default_roles;
std::shared_ptr<ASTSettingsProfileElements> settings;
String cluster;
while (true)
{
@ -320,6 +322,9 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (!default_roles && parseDefaultRoles(pos, expected, attach_mode, default_roles))
continue;
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
if (alter)
{
if (new_name.empty() && parseRenameTo(pos, expected, new_name, new_host_pattern))

View File

@ -8,13 +8,13 @@ namespace DB
/** Parses queries like
* CREATE USER [IF NOT EXISTS | OR REPLACE] name
* [IDENTIFIED [WITH {NO_PASSWORD|PLAINTEXT_PASSWORD|SHA256_PASSWORD|SHA256_HASH|DOUBLE_SHA1_PASSWORD|DOUBLE_SHA1_HASH}] BY {'password'|'hash'}]
* [HOST {LOCAL | NAME 'name' | NAME REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
*
* ALTER USER [IF EXISTS] name
* [RENAME TO new_name]
* [IDENTIFIED [WITH {PLAINTEXT_PASSWORD|SHA256_PASSWORD|DOUBLE_SHA1_PASSWORD}] BY {'password'|'hash'}]
* [[ADD|DROP] HOST {LOCAL | NAME 'name' | NAME REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [[ADD|DROP] HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
* [SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE] | PROFILE 'profile_name'] [,...]
*/
class ParserCreateUserQuery : public IParserBase

View File

@ -237,6 +237,14 @@ namespace
return true;
});
}
bool parseOnCluster(IParserBase::Pos & pos, Expected & expected, String & cluster)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return ParserKeyword{"ON"}.ignore(pos, expected) && ASTQueryWithOnCluster::parse(pos, cluster, expected);
});
}
}
@ -260,11 +268,8 @@ bool ParserGrantQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
return false;
String cluster;
if (ParserKeyword{"ON"}.ignore(pos, expected))
{
if (!ASTQueryWithOnCluster::parse(pos, cluster, expected))
return false;
}
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
bool grant_option = false;
bool admin_option = false;
@ -281,10 +286,16 @@ bool ParserGrantQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
if (!parseAccessRightsElements(pos, expected, elements) && !parseRoles(pos, expected, attach, roles))
return false;
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
std::shared_ptr<ASTExtendedRoleSet> to_roles;
if (!parseToRoles(pos, expected, kind, to_roles))
return false;
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
if (kind == Kind::GRANT)
{
if (ParserKeyword{"WITH GRANT OPTION"}.ignore(pos, expected))
@ -293,6 +304,9 @@ bool ParserGrantQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
admin_option = true;
}
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
if (grant_option && roles)
throw Exception("GRANT OPTION should be specified for access types", ErrorCodes::SYNTAX_ERROR);
if (admin_option && !elements.empty())

View File

@ -108,7 +108,8 @@ bool ParserSettingsProfileElement::parseImpl(Pos & pos, ASTPtr & node, Expected
Field max_value;
std::optional<bool> readonly;
if (ParserKeyword{"PROFILE"}.ignore(pos, expected))
if (ParserKeyword{"PROFILE"}.ignore(pos, expected) ||
(enable_inherit_keyword && ParserKeyword{"INHERIT"}.ignore(pos, expected)))
{
if (!parseProfileNameOrID(pos, expected, id_mode, parent_profile))
return false;
@ -120,9 +121,15 @@ bool ParserSettingsProfileElement::parseImpl(Pos & pos, ASTPtr & node, Expected
return false;
name = getIdentifierName(name_ast);
bool has_value_or_constraint = false;
while (parseValue(pos, expected, value) || parseMinMaxValue(pos, expected, min_value, max_value)
|| parseReadonlyOrWritableKeyword(pos, expected, readonly))
;
{
has_value_or_constraint = true;
}
if (!has_value_or_constraint)
return false;
}
auto result = std::make_shared<ASTSettingsProfileElement>();
@ -133,6 +140,7 @@ bool ParserSettingsProfileElement::parseImpl(Pos & pos, ASTPtr & node, Expected
result->max_value = std::move(max_value);
result->readonly = readonly;
result->id_mode = id_mode;
result->use_inherit_keyword = enable_inherit_keyword;
node = result;
return true;
}
@ -142,12 +150,15 @@ bool ParserSettingsProfileElements::parseImpl(Pos & pos, ASTPtr & node, Expected
{
std::vector<std::shared_ptr<ASTSettingsProfileElement>> elements;
if (!ParserKeyword{"NONE"}.ignore(pos, expected))
if (ParserKeyword{"NONE"}.ignore(pos, expected))
{
}
else
{
do
{
ASTPtr ast;
if (!ParserSettingsProfileElement{}.useIDMode(id_mode).parse(pos, ast, expected))
if (!ParserSettingsProfileElement{}.useIDMode(id_mode).enableInheritKeyword(enable_inherit_keyword).parse(pos, ast, expected))
return false;
auto element = typeid_cast<std::shared_ptr<ASTSettingsProfileElement>>(ast);
elements.push_back(std::move(element));

View File

@ -12,6 +12,7 @@ class ParserSettingsProfileElement : public IParserBase
{
public:
ParserSettingsProfileElement & useIDMode(bool enable_) { id_mode = enable_; return *this; }
ParserSettingsProfileElement & enableInheritKeyword(bool enable_) { enable_inherit_keyword = enable_; return *this; }
protected:
const char * getName() const override { return "SettingsProfileElement"; }
@ -19,6 +20,7 @@ protected:
private:
bool id_mode = false;
bool enable_inherit_keyword = false;
};
@ -26,6 +28,7 @@ class ParserSettingsProfileElements : public IParserBase
{
public:
ParserSettingsProfileElements & useIDMode(bool enable_) { id_mode = enable_; return *this; }
ParserSettingsProfileElements & enableInheritKeyword(bool enable_) { enable_inherit_keyword = enable_; return *this; }
protected:
const char * getName() const override { return "SettingsProfileElements"; }
@ -33,4 +36,7 @@ protected:
private:
bool id_mode = false;
};}
bool enable_inherit_keyword = false;
};
}

View File

@ -0,0 +1,103 @@
#include <Parsers/makeASTForLogicalFunction.h>
#include <Parsers/ASTLiteral.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTExpressionList.h>
#include <boost/range/algorithm_ext/erase.hpp>
namespace DB
{
ASTPtr makeASTForLogicalNot(ASTPtr argument)
{
bool b;
if (tryGetLiteralBool(argument.get(), b))
return std::make_shared<ASTLiteral>(Field{UInt8(!b)});
auto function = std::make_shared<ASTFunction>();
auto exp_list = std::make_shared<ASTExpressionList>();
function->name = "not";
function->arguments = exp_list;
function->children.push_back(exp_list);
exp_list->children.push_back(argument);
return function;
}
ASTPtr makeASTForLogicalAnd(ASTs && arguments)
{
bool partial_result = true;
boost::range::remove_erase_if(arguments, [&](const ASTPtr & argument) -> bool
{
bool b;
if (!tryGetLiteralBool(argument.get(), b))
return false;
partial_result &= b;
return true;
});
if (!partial_result)
return std::make_shared<ASTLiteral>(Field{UInt8(0)});
if (arguments.empty())
return std::make_shared<ASTLiteral>(Field{UInt8(1)});
if (arguments.size() == 1)
return arguments[0];
auto function = std::make_shared<ASTFunction>();
auto exp_list = std::make_shared<ASTExpressionList>();
function->name = "and";
function->arguments = exp_list;
function->children.push_back(exp_list);
exp_list->children = std::move(arguments);
return function;
}
ASTPtr makeASTForLogicalOr(ASTs && arguments)
{
bool partial_result = false;
boost::range::remove_erase_if(arguments, [&](const ASTPtr & argument) -> bool
{
bool b;
if (!tryGetLiteralBool(argument.get(), b))
return false;
partial_result |= b;
return true;
});
if (partial_result)
return std::make_shared<ASTLiteral>(Field{UInt8(1)});
if (arguments.empty())
return std::make_shared<ASTLiteral>(Field{UInt8(0)});
if (arguments.size() == 1)
return arguments[0];
auto function = std::make_shared<ASTFunction>();
auto exp_list = std::make_shared<ASTExpressionList>();
function->name = "or";
function->arguments = exp_list;
function->children.push_back(exp_list);
exp_list->children = std::move(arguments);
return function;
}
bool tryGetLiteralBool(const IAST * ast, bool & value)
{
if (!ast)
return false;
try
{
if (const ASTLiteral * literal = ast->as<ASTLiteral>())
{
value = !literal->value.isNull() && applyVisitor(FieldVisitorConvertToNumber<bool>(), literal->value);
return true;
}
return false;
}
catch (...)
{
return false;
}
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <Parsers/IAST_fwd.h>
namespace DB
{
/// Makes an AST calculating NOT argument.
ASTPtr makeASTForLogicalNot(ASTPtr argument);
/// Makes an AST calculating argument1 AND argument2 AND ... AND argumentN.
ASTPtr makeASTForLogicalAnd(ASTs && arguments);
/// Makes an AST calculating argument1 OR argument2 OR ... OR argumentN.
ASTPtr makeASTForLogicalOr(ASTs && arguments);
/// Tries to extract a literal bool from AST.
bool tryGetLiteralBool(const IAST * ast, bool & value);
}

View File

@ -923,6 +923,7 @@ class ClickHouseInstance:
# The file is named with 0_ prefix to be processed before other configuration overloads.
shutil.copy(p.join(HELPERS_DIR, '0_common_instance_config.xml'), self.config_d_dir)
shutil.copy(p.join(HELPERS_DIR, '0_common_instance_users.xml'), users_d_dir)
# Generate and write macros file
macros = self.macros.copy()

View File

@ -1,13 +0,0 @@
<?xml version="1.0"?>
<yandex>
<profiles>
<default>
</default>
</profiles>
<users>
<default>
<profile>default</profile>
<password></password>
</default>
</users>
</yandex>

View File

@ -1,7 +0,0 @@
<yandex>
<users>
<default>
<access_management>1</access_management>
</default>
</users>
</yandex>

View File

@ -2,7 +2,7 @@ import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance('instance', config_dir="configs")
instance = cluster.add_instance('instance')
@pytest.fixture(scope="module", autouse=True)

View File

@ -1,7 +0,0 @@
<yandex>
<users>
<default>
<access_management>1</access_management>
</default>
</users>
</yandex>

View File

@ -2,7 +2,7 @@ import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance('instance', config_dir='configs', stay_alive=True)
instance = cluster.add_instance('instance', stay_alive=True)
@pytest.fixture(scope="module", autouse=True)
@ -22,7 +22,7 @@ def create_entities():
instance.query("CREATE USER u2 IDENTIFIED BY 'qwerty' HOST LOCAL DEFAULT ROLE rx")
instance.query("CREATE SETTINGS PROFILE s2 SETTINGS PROFILE s1 TO u2")
instance.query("CREATE ROW POLICY p ON mydb.mytable FOR SELECT USING a<1000 TO u1, u2")
instance.query("CREATE QUOTA q FOR INTERVAL 1 HOUR SET MAX QUERIES = 100 TO ALL EXCEPT rx")
instance.query("CREATE QUOTA q FOR INTERVAL 1 HOUR MAX QUERIES 100 TO ALL EXCEPT rx")
@pytest.fixture(autouse=True)
@ -41,13 +41,13 @@ def test_create():
assert instance.query("SHOW CREATE USER u1") == "CREATE USER u1 SETTINGS PROFILE s1\n"
assert instance.query("SHOW CREATE USER u2") == "CREATE USER u2 HOST LOCAL DEFAULT ROLE rx\n"
assert instance.query("SHOW CREATE ROW POLICY p ON mydb.mytable") == "CREATE ROW POLICY p ON mydb.mytable FOR SELECT USING a < 1000 TO u1, u2\n"
assert instance.query("SHOW CREATE QUOTA q") == "CREATE QUOTA q KEYED BY \\'none\\' FOR INTERVAL 1 HOUR MAX QUERIES = 100 TO ALL EXCEPT rx\n"
assert instance.query("SHOW CREATE QUOTA q") == "CREATE QUOTA q KEYED BY \\'none\\' FOR INTERVAL 1 HOUR MAX QUERIES 100 TO ALL EXCEPT rx\n"
assert instance.query("SHOW GRANTS FOR u1") == ""
assert instance.query("SHOW GRANTS FOR u2") == "GRANT rx TO u2\n"
assert instance.query("SHOW CREATE ROLE rx") == "CREATE ROLE rx SETTINGS PROFILE s1\n"
assert instance.query("SHOW GRANTS FOR rx") == ""
assert instance.query("SHOW CREATE SETTINGS PROFILE s1") == "CREATE SETTINGS PROFILE s1 SETTINGS max_memory_usage = 123456789 MIN 100000000 MAX 200000000\n"
assert instance.query("SHOW CREATE SETTINGS PROFILE s2") == "CREATE SETTINGS PROFILE s2 SETTINGS PROFILE s1 TO u2\n"
assert instance.query("SHOW CREATE SETTINGS PROFILE s2") == "CREATE SETTINGS PROFILE s2 SETTINGS INHERIT s1 TO u2\n"
check()
instance.restart_clickhouse() # Check persistency
@ -77,7 +77,7 @@ def test_alter():
assert instance.query("SHOW GRANTS FOR rx") == "GRANT SELECT ON mydb.* TO rx WITH GRANT OPTION\n"
assert instance.query("SHOW GRANTS FOR ry") == "GRANT rx TO ry WITH ADMIN OPTION\n"
assert instance.query("SHOW CREATE SETTINGS PROFILE s1") == "CREATE SETTINGS PROFILE s1 SETTINGS max_memory_usage = 987654321 READONLY\n"
assert instance.query("SHOW CREATE SETTINGS PROFILE s2") == "CREATE SETTINGS PROFILE s2 SETTINGS PROFILE s1 TO u2\n"
assert instance.query("SHOW CREATE SETTINGS PROFILE s2") == "CREATE SETTINGS PROFILE s2 SETTINGS INHERIT s1 TO u2\n"
check()
instance.restart_clickhouse() # Check persistency

View File

@ -0,0 +1,13 @@
<yandex>
<users>
<readonly>
<password></password>
<profile>readonly</profile>
<access_management>1</access_management>
</readonly>
<xyz>
<password></password>
<profile>default</profile>
</xyz>
</users>
</yandex>

View File

@ -0,0 +1,24 @@
import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance('instance', config_dir="configs")
@pytest.fixture(scope="module", autouse=True)
def started_cluster():
try:
cluster.start()
yield cluster
finally:
cluster.shutdown()
def test_enabling_access_management():
instance.query("CREATE USER Alex", user='default')
assert instance.query("SHOW CREATE USER Alex", user='default') == "CREATE USER Alex\n"
assert instance.query("SHOW CREATE USER Alex", user='readonly') == "CREATE USER Alex\n"
assert "Not enough privileges" in instance.query_and_get_error("SHOW CREATE USER Alex", user='xyz')
assert "Cannot execute query in readonly mode" in instance.query_and_get_error("CREATE USER Robin", user='readonly')
assert "Not enough privileges" in instance.query_and_get_error("CREATE USER Robin", user='xyz')

View File

@ -1,7 +0,0 @@
<yandex>
<users>
<default>
<access_management>1</access_management>
</default>
</users>
</yandex>

View File

@ -3,7 +3,7 @@ from helpers.cluster import ClickHouseCluster
import re
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance('instance', config_dir="configs")
instance = cluster.add_instance('instance')
@pytest.fixture(scope="module", autouse=True)

View File

@ -1,7 +1,7 @@
<yandex>
<users>
<default>
<access_management>1</access_management>
<quota>myQuota</quota>
</default>
</users>
</yandex>

View File

@ -0,0 +1,5 @@
<yandex>
<quotas>
<default remove="1"/>
</quotas>
</yandex>

View File

@ -1,17 +0,0 @@
<?xml version="1.0"?>
<yandex>
<profiles>
<default>
</default>
</profiles>
<users>
<default>
<password></password>
<networks incl="networks" replace="replace">
<ip>::/0</ip>
</networks>
<profile>default</profile>
<quota>myQuota</quota>
</default>
</users>
</yandex>

View File

@ -180,7 +180,7 @@ def test_reload_users_xml_by_timer():
def test_dcl_introspection():
assert instance.query("SHOW QUOTAS") == "myQuota\n"
assert instance.query("SHOW CREATE QUOTA myQuota") == "CREATE QUOTA myQuota KEYED BY \\'user name\\' FOR INTERVAL 1 YEAR MAX QUERIES = 1000, MAX READ ROWS = 1000 TO default\n"
assert instance.query("SHOW CREATE QUOTA myQuota") == "CREATE QUOTA myQuota KEYED BY \\'user name\\' FOR INTERVAL 1 YEAR MAX QUERIES 1000, READ ROWS 1000 TO default\n"
expected_usage = "myQuota key=\\\\'default\\\\' interval=\[.*\] queries=0/1000 errors=0 result_rows=0 result_bytes=0 read_rows=0/1000 read_bytes=0 execution_time=0"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE CURRENT"))
@ -193,7 +193,7 @@ def test_dcl_introspection():
# Add interval.
copy_quota_xml('two_intervals.xml')
assert instance.query("SHOW QUOTAS") == "myQuota\n"
assert instance.query("SHOW CREATE QUOTA myQuota") == "CREATE QUOTA myQuota KEYED BY \\'user name\\' FOR INTERVAL 1 YEAR MAX QUERIES = 1000, MAX READ ROWS = 1000, FOR RANDOMIZED INTERVAL 2 YEAR MAX RESULT BYTES = 30000, MAX READ BYTES = 20000, MAX EXECUTION TIME = 120 TO default\n"
assert instance.query("SHOW CREATE QUOTA myQuota") == "CREATE QUOTA myQuota KEYED BY \\'user name\\' FOR INTERVAL 1 YEAR MAX QUERIES 1000, READ ROWS 1000, FOR RANDOMIZED INTERVAL 2 YEAR MAX RESULT BYTES 30000, READ BYTES 20000, EXECUTION TIME 120 TO default\n"
expected_usage = "myQuota key=\\\\'default\\\\' interval=\[.*\] queries=1/1000 errors=0 result_rows=50 result_bytes=200 read_rows=50/1000 read_bytes=200 execution_time=.*\n"\
"myQuota key=\\\\'default\\\\' interval=\[.*\] queries=0 errors=0 result_rows=0 result_bytes=0/30000 read_rows=0 read_bytes=0/20000 execution_time=0/120"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
@ -201,8 +201,8 @@ def test_dcl_introspection():
# Drop interval, add quota.
copy_quota_xml('two_quotas.xml')
assert instance.query("SHOW QUOTAS") == "myQuota\nmyQuota2\n"
assert instance.query("SHOW CREATE QUOTA myQuota") == "CREATE QUOTA myQuota KEYED BY \\'user name\\' FOR INTERVAL 1 YEAR MAX QUERIES = 1000, MAX READ ROWS = 1000 TO default\n"
assert instance.query("SHOW CREATE QUOTA myQuota2") == "CREATE QUOTA myQuota2 KEYED BY \\'client key or user name\\' FOR RANDOMIZED INTERVAL 1 HOUR MAX RESULT ROWS = 4000, MAX RESULT BYTES = 400000, MAX READ ROWS = 4000, MAX READ BYTES = 400000, MAX EXECUTION TIME = 60, FOR INTERVAL 1 MONTH MAX EXECUTION TIME = 1800\n"
assert instance.query("SHOW CREATE QUOTA myQuota") == "CREATE QUOTA myQuota KEYED BY \\'user name\\' FOR INTERVAL 1 YEAR MAX QUERIES 1000, READ ROWS 1000 TO default\n"
assert instance.query("SHOW CREATE QUOTA myQuota2") == "CREATE QUOTA myQuota2 KEYED BY \\'client key or user name\\' FOR RANDOMIZED INTERVAL 1 HOUR MAX RESULT ROWS 4000, RESULT BYTES 400000, READ ROWS 4000, READ BYTES 400000, EXECUTION TIME 60, FOR INTERVAL 1 MONTH MAX EXECUTION TIME 1800\n"
expected_usage = "myQuota key=\\\\'default\\\\' interval=\[.*\] queries=1/1000 errors=0 result_rows=50 result_bytes=200 read_rows=50/1000 read_bytes=200 execution_time=.*"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
@ -212,9 +212,9 @@ def test_dcl_management():
assert instance.query("SHOW QUOTAS") == ""
assert instance.query("SHOW QUOTA USAGE") == ""
instance.query("CREATE QUOTA qA FOR INTERVAL 15 MONTH SET MAX QUERIES = 123 TO CURRENT_USER")
instance.query("CREATE QUOTA qA FOR INTERVAL 15 MONTH MAX QUERIES 123 TO CURRENT_USER")
assert instance.query("SHOW QUOTAS") == "qA\n"
assert instance.query("SHOW CREATE QUOTA qA") == "CREATE QUOTA qA KEYED BY \\'none\\' FOR INTERVAL 5 QUARTER MAX QUERIES = 123 TO default\n"
assert instance.query("SHOW CREATE QUOTA qA") == "CREATE QUOTA qA KEYED BY \\'none\\' FOR INTERVAL 5 QUARTER MAX QUERIES 123 TO default\n"
expected_usage = "qA key=\\\\'\\\\' interval=\[.*\] queries=0/123 errors=0 result_rows=0 result_bytes=0 read_rows=0 read_bytes=0 execution_time=.*"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
@ -222,14 +222,14 @@ def test_dcl_management():
expected_usage = "qA key=\\\\'\\\\' interval=\[.*\] queries=1/123 errors=0 result_rows=50 result_bytes=200 read_rows=50 read_bytes=200 execution_time=.*"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
instance.query("ALTER QUOTA qA FOR INTERVAL 15 MONTH MAX QUERIES = 321, MAX ERRORS = 10, FOR INTERVAL 0.5 HOUR MAX EXECUTION TIME = 0.5")
assert instance.query("SHOW CREATE QUOTA qA") == "CREATE QUOTA qA KEYED BY \\'none\\' FOR INTERVAL 30 MINUTE MAX EXECUTION TIME = 0.5, FOR INTERVAL 5 QUARTER MAX QUERIES = 321, MAX ERRORS = 10 TO default\n"
instance.query("ALTER QUOTA qA FOR INTERVAL 15 MONTH MAX QUERIES 321, MAX ERRORS 10, FOR INTERVAL 0.5 HOUR MAX EXECUTION TIME 0.5")
assert instance.query("SHOW CREATE QUOTA qA") == "CREATE QUOTA qA KEYED BY \\'none\\' FOR INTERVAL 30 MINUTE MAX EXECUTION TIME 0.5, FOR INTERVAL 5 QUARTER MAX QUERIES 321, ERRORS 10 TO default\n"
expected_usage = "qA key=\\\\'\\\\' interval=\[.*\] queries=0 errors=0 result_rows=0 result_bytes=0 read_rows=0 read_bytes=0 execution_time=.*/0.5\n"\
"qA key=\\\\'\\\\' interval=\[.*\] queries=1/321 errors=0/10 result_rows=50 result_bytes=200 read_rows=50 read_bytes=200 execution_time=.*"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
instance.query("ALTER QUOTA qA FOR INTERVAL 15 MONTH UNSET TRACKING, FOR RANDOMIZED INTERVAL 16 MONTH SET TRACKING, FOR INTERVAL 1800 SECOND UNSET TRACKING")
assert instance.query("SHOW CREATE QUOTA qA") == "CREATE QUOTA qA KEYED BY \\'none\\' FOR RANDOMIZED INTERVAL 16 MONTH TRACKING TO default\n"
instance.query("ALTER QUOTA qA FOR INTERVAL 15 MONTH NO LIMITS, FOR RANDOMIZED INTERVAL 16 MONTH TRACKING ONLY, FOR INTERVAL 1800 SECOND NO LIMITS")
assert instance.query("SHOW CREATE QUOTA qA") == "CREATE QUOTA qA KEYED BY \\'none\\' FOR RANDOMIZED INTERVAL 16 MONTH TRACKING ONLY TO default\n"
expected_usage = "qA key=\\\\'\\\\' interval=\[.*\] queries=0 errors=0 result_rows=0 result_bytes=0 read_rows=0 read_bytes=0 execution_time=.*"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
@ -238,7 +238,7 @@ def test_dcl_management():
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))
instance.query("ALTER QUOTA qA RENAME TO qB")
assert instance.query("SHOW CREATE QUOTA qB") == "CREATE QUOTA qB KEYED BY \\'none\\' FOR RANDOMIZED INTERVAL 16 MONTH TRACKING TO default\n"
assert instance.query("SHOW CREATE QUOTA qB") == "CREATE QUOTA qB KEYED BY \\'none\\' FOR RANDOMIZED INTERVAL 16 MONTH TRACKING ONLY TO default\n"
expected_usage = "qB key=\\\\'\\\\' interval=\[.*\] queries=1 errors=0 result_rows=50 result_bytes=200 read_rows=50 read_bytes=200 execution_time=.*"
assert re.match(expected_usage, instance.query("SHOW QUOTA USAGE"))

View File

@ -1,7 +0,0 @@
<yandex>
<users>
<default>
<access_management>1</access_management>
</default>
</users>
</yandex>

View File

@ -113,6 +113,9 @@ def test_prewhere_not_supported():
assert expected_error in instance.query_and_get_error("SELECT * FROM mydb.filtered_table2 PREWHERE 1")
assert expected_error in instance.query_and_get_error("SELECT * FROM mydb.filtered_table3 PREWHERE 1")
# However PREWHERE should still work for user without filtering.
assert instance.query("SELECT * FROM mydb.filtered_table1 PREWHERE 1", user="another") == "0\t0\n0\t1\n1\t0\n1\t1\n"
def test_single_table_name():
copy_policy_xml('tag_with_table_name.xml')

View File

@ -1,7 +0,0 @@
<yandex>
<users>
<default>
<access_management>1</access_management>
</default>
</users>
</yandex>

View File

@ -8,9 +8,9 @@ from helpers.test_tools import assert_eq_with_retry
cluster = ClickHouseCluster(__file__)
node1 = cluster.add_instance('node1', config_dir="configs")
node2 = cluster.add_instance('node2', config_dir="configs")
distributed = cluster.add_instance('distributed', config_dir="configs")
node1 = cluster.add_instance('node1')
node2 = cluster.add_instance('node2')
distributed = cluster.add_instance('distributed', main_configs=["configs/remote_servers.xml"])
@pytest.fixture(scope="module")

View File

@ -1,7 +0,0 @@
<yandex>
<users>
<default>
<access_management>1</access_management>
</default>
</users>
</yandex>

View File

@ -2,7 +2,7 @@ import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance('instance', config_dir="configs")
instance = cluster.add_instance('instance')
@pytest.fixture(scope="module", autouse=True)
@ -31,22 +31,26 @@ def reset_after_test():
def test_settings_profile():
# Set settings and constraints via CREATE SETTINGS PROFILE ... TO user
instance.query("CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000001 MIN 90000000 MAX 110000000 TO robin")
assert instance.query("SHOW CREATE SETTINGS PROFILE xyz") == "CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000001 MIN 90000000 MAX 110000000 TO robin\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "100000001\n"
assert "Setting max_memory_usage shouldn't be less than 90000000" in instance.query_and_get_error("SET max_memory_usage = 80000000", user="robin")
assert "Setting max_memory_usage shouldn't be greater than 110000000" in instance.query_and_get_error("SET max_memory_usage = 120000000", user="robin")
instance.query("ALTER SETTINGS PROFILE xyz TO NONE")
assert instance.query("SHOW CREATE SETTINGS PROFILE xyz") == "CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000001 MIN 90000000 MAX 110000000\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "10000000000\n"
instance.query("SET max_memory_usage = 80000000", user="robin")
instance.query("SET max_memory_usage = 120000000", user="robin")
# Set settings and constraints via CREATE USER ... SETTINGS PROFILE
instance.query("ALTER USER robin SETTINGS PROFILE xyz")
assert instance.query("SHOW CREATE USER robin") == "CREATE USER robin SETTINGS PROFILE xyz\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "100000001\n"
assert "Setting max_memory_usage shouldn't be less than 90000000" in instance.query_and_get_error("SET max_memory_usage = 80000000", user="robin")
assert "Setting max_memory_usage shouldn't be greater than 110000000" in instance.query_and_get_error("SET max_memory_usage = 120000000", user="robin")
instance.query("ALTER USER robin SETTINGS NONE")
assert instance.query("SHOW CREATE USER robin") == "CREATE USER robin\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "10000000000\n"
instance.query("SET max_memory_usage = 80000000", user="robin")
instance.query("SET max_memory_usage = 120000000", user="robin")
@ -57,6 +61,8 @@ def test_settings_profile_from_granted_role():
instance.query("CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000001 MIN 90000000 MAX 110000000")
instance.query("CREATE ROLE worker SETTINGS PROFILE xyz")
instance.query("GRANT worker TO robin")
assert instance.query("SHOW CREATE SETTINGS PROFILE xyz") == "CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000001 MIN 90000000 MAX 110000000\n"
assert instance.query("SHOW CREATE ROLE worker") == "CREATE ROLE worker SETTINGS PROFILE xyz\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "100000001\n"
assert "Setting max_memory_usage shouldn't be less than 90000000" in instance.query_and_get_error("SET max_memory_usage = 80000000", user="robin")
assert "Setting max_memory_usage shouldn't be greater than 110000000" in instance.query_and_get_error("SET max_memory_usage = 120000000", user="robin")
@ -68,17 +74,20 @@ def test_settings_profile_from_granted_role():
instance.query("ALTER ROLE worker SETTINGS NONE")
instance.query("GRANT worker TO robin")
assert instance.query("SHOW CREATE ROLE worker") == "CREATE ROLE worker\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "10000000000\n"
instance.query("SET max_memory_usage = 80000000", user="robin")
instance.query("SET max_memory_usage = 120000000", user="robin")
# Set settings and constraints via CREATE SETTINGS PROFILE ... TO granted role
instance.query("ALTER SETTINGS PROFILE xyz TO worker")
assert instance.query("SHOW CREATE SETTINGS PROFILE xyz") == "CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000001 MIN 90000000 MAX 110000000 TO worker\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "100000001\n"
assert "Setting max_memory_usage shouldn't be less than 90000000" in instance.query_and_get_error("SET max_memory_usage = 80000000", user="robin")
assert "Setting max_memory_usage shouldn't be greater than 110000000" in instance.query_and_get_error("SET max_memory_usage = 120000000", user="robin")
instance.query("ALTER SETTINGS PROFILE xyz TO NONE")
assert instance.query("SHOW CREATE SETTINGS PROFILE xyz") == "CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000001 MIN 90000000 MAX 110000000\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "10000000000\n"
instance.query("SET max_memory_usage = 80000000", user="robin")
instance.query("SET max_memory_usage = 120000000", user="robin")
@ -87,6 +96,8 @@ def test_settings_profile_from_granted_role():
def test_inheritance_of_settings_profile():
instance.query("CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000002 READONLY")
instance.query("CREATE SETTINGS PROFILE alpha SETTINGS PROFILE xyz TO robin")
assert instance.query("SHOW CREATE SETTINGS PROFILE xyz") == "CREATE SETTINGS PROFILE xyz SETTINGS max_memory_usage = 100000002 READONLY\n"
assert instance.query("SHOW CREATE SETTINGS PROFILE alpha") == "CREATE SETTINGS PROFILE alpha SETTINGS INHERIT xyz TO robin\n"
assert instance.query("SELECT value FROM system.settings WHERE name = 'max_memory_usage'", user="robin") == "100000002\n"
assert "Setting max_memory_usage should not be changed" in instance.query_and_get_error("SET max_memory_usage = 80000000", user="robin")

View File

@ -1,2 +1,2 @@
default
CREATE QUOTA default KEYED BY \'user name\' FOR INTERVAL 1 HOUR TRACKING TO default, readonly
CREATE QUOTA default KEYED BY \'user name\' FOR INTERVAL 1 HOUR TRACKING ONLY TO default, readonly

View File

@ -8,10 +8,10 @@ CREATE USER test_user_01075 HOST LOCAL, IP \'2001:db8:11a3:9d7:1f34:8a2e:7a0:765
CREATE USER test_user_01075 HOST LOCAL
CREATE USER test_user_01075 HOST NONE
CREATE USER test_user_01075 HOST LIKE \'@.somesite.com\'
CREATE USER test_user_01075 HOST NAME REGEXP \'.*.anothersite.com\'
CREATE USER test_user_01075 HOST NAME REGEXP \'.*.anothersite.com\', \'.*.anothersite.org\'
CREATE USER test_user_01075 HOST NAME REGEXP \'.*.anothersite2.com\', \'.*.anothersite2.org\'
CREATE USER test_user_01075 HOST NAME REGEXP \'.*.anothersite3.com\', \'.*.anothersite3.org\'
CREATE USER test_user_01075 HOST REGEXP \'.*.anothersite.com\'
CREATE USER test_user_01075 HOST REGEXP \'.*.anothersite.com\', \'.*.anothersite.org\'
CREATE USER test_user_01075 HOST REGEXP \'.*.anothersite2.com\', \'.*.anothersite2.org\'
CREATE USER test_user_01075 HOST REGEXP \'.*.anothersite3.com\', \'.*.anothersite3.org\'
CREATE USER `test_user_01075_x@localhost` HOST LOCAL
CREATE USER test_user_01075_x
CREATE USER `test_user_01075_x@192.168.23.15` HOST LIKE \'192.168.23.15\'

View File

@ -30,16 +30,16 @@ SHOW CREATE USER test_user_01075;
ALTER USER test_user_01075 HOST LIKE '@.somesite.com';
SHOW CREATE USER test_user_01075;
ALTER USER test_user_01075 HOST NAME REGEXP '.*\.anothersite\.com';
ALTER USER test_user_01075 HOST REGEXP '.*\.anothersite\.com';
SHOW CREATE USER test_user_01075;
ALTER USER test_user_01075 HOST NAME REGEXP '.*\.anothersite\.com', '.*\.anothersite\.org';
ALTER USER test_user_01075 HOST REGEXP '.*\.anothersite\.com', '.*\.anothersite\.org';
SHOW CREATE USER test_user_01075;
ALTER USER test_user_01075 HOST NAME REGEXP '.*\.anothersite2\.com', NAME REGEXP '.*\.anothersite2\.org';
ALTER USER test_user_01075 HOST REGEXP '.*\.anothersite2\.com', REGEXP '.*\.anothersite2\.org';
SHOW CREATE USER test_user_01075;
ALTER USER test_user_01075 HOST NAME REGEXP '.*\.anothersite3\.com' HOST NAME REGEXP '.*\.anothersite3\.org';
ALTER USER test_user_01075 HOST REGEXP '.*\.anothersite3\.com' HOST REGEXP '.*\.anothersite3\.org';
SHOW CREATE USER test_user_01075;
DROP USER test_user_01075;