Implement MOVE query for the access entities.

This commit is contained in:
pufit 2023-07-20 22:20:36 -04:00
parent ec27ccc696
commit 2a00e2aa6a
49 changed files with 731 additions and 103 deletions

View File

@ -525,9 +525,9 @@ scope_guard AccessControl::subscribeForChanges(const std::vector<UUID> & ids, co
return changes_notifier->subscribeForChanges(ids, handler);
}
std::optional<UUID> AccessControl::insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists)
std::optional<UUID> AccessControl::insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id)
{
auto id = MultipleAccessStorage::insertImpl(entity, replace_if_exists, throw_if_exists);
auto id = MultipleAccessStorage::insertImpl(entity, replace_if_exists, throw_if_exists, set_id);
if (id)
changes_notifier->sendNotifications();
return id;

View File

@ -229,7 +229,7 @@ private:
class CustomSettingsPrefixes;
class PasswordComplexityRules;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists) override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id) override;
bool removeImpl(const UUID & id, bool throw_if_not_exists) override;
bool updateImpl(const UUID & id, const UpdateFunc & update_func, bool throw_if_not_exists) override;

View File

@ -498,9 +498,9 @@ std::optional<std::pair<String, AccessEntityType>> DiskAccessStorage::readNameWi
}
std::optional<UUID> DiskAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool replace_if_exists, bool throw_if_exists)
std::optional<UUID> DiskAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id)
{
UUID id = generateRandomID();
UUID id = set_id ? *set_id : generateRandomID();
if (insertWithID(id, new_entity, replace_if_exists, throw_if_exists, /* write_on_disk= */ true))
return id;

View File

@ -39,7 +39,7 @@ private:
std::vector<UUID> findAllImpl(AccessEntityType type) const override;
AccessEntityPtr readImpl(const UUID & id, bool throw_if_not_exists) const override;
std::optional<std::pair<String, AccessEntityType>> readNameWithTypeImpl(const UUID & id, bool throw_if_not_exists) const override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists) override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id) override;
bool removeImpl(const UUID & id, bool throw_if_not_exists) override;
bool updateImpl(const UUID & id, const UpdateFunc & update_func, bool throw_if_not_exists) override;

View File

@ -93,6 +93,17 @@ String IAccessStorage::readName(const UUID & id) const
}
bool IAccessStorage::exists(const std::vector<UUID> & ids) const
{
for (const auto & id : ids)
{
if (!exists(id))
return false;
}
return true;
}
std::optional<String> IAccessStorage::readName(const UUID & id, bool throw_if_not_exists) const
{
if (auto name_and_type = readNameWithType(id, throw_if_not_exists))
@ -167,15 +178,26 @@ UUID IAccessStorage::insert(const AccessEntityPtr & entity)
return *insert(entity, /* replace_if_exists = */ false, /* throw_if_exists = */ true);
}
std::optional<UUID> IAccessStorage::insert(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists)
{
return insertImpl(entity, replace_if_exists, throw_if_exists);
return *insert(entity, replace_if_exists, throw_if_exists, /* set_id = */ std::nullopt);
}
std::optional<UUID> IAccessStorage::insert(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id)
{
return insertImpl(entity, replace_if_exists, throw_if_exists, set_id);
}
std::vector<UUID> IAccessStorage::insert(const std::vector<AccessEntityPtr> & multiple_entities, bool replace_if_exists, bool throw_if_exists)
{
return insert(multiple_entities, /* ids = */ {}, replace_if_exists, throw_if_exists);
}
std::vector<UUID> IAccessStorage::insert(const std::vector<AccessEntityPtr> & multiple_entities, const std::vector<UUID> & ids, bool replace_if_exists, bool throw_if_exists)
{
if (!ids.empty())
assert(multiple_entities.size() == ids.size());
if (multiple_entities.empty())
return {};
@ -189,16 +211,24 @@ std::vector<UUID> IAccessStorage::insert(const std::vector<AccessEntityPtr> & mu
std::vector<AccessEntityPtr> successfully_inserted;
try
{
std::vector<UUID> ids;
for (const auto & entity : multiple_entities)
std::vector<UUID> new_ids;
for (size_t i = 0; i < multiple_entities.size(); ++i)
{
if (auto id = insertImpl(entity, replace_if_exists, throw_if_exists))
const auto & entity = multiple_entities[i];
std::optional<UUID> id;
if (!ids.empty())
id = ids[i];
auto new_id = insertImpl(entity, replace_if_exists, throw_if_exists, id);
if (new_id)
{
successfully_inserted.push_back(entity);
ids.push_back(*id);
new_ids.push_back(*new_id);
}
}
return ids;
return new_ids;
}
catch (Exception & e)
{
@ -244,7 +274,7 @@ std::vector<UUID> IAccessStorage::insertOrReplace(const std::vector<AccessEntity
}
std::optional<UUID> IAccessStorage::insertImpl(const AccessEntityPtr & entity, bool, bool)
std::optional<UUID> IAccessStorage::insertImpl(const AccessEntityPtr & entity, bool, bool, std::optional<UUID>)
{
if (isReadOnly())
throwReadonlyCannotInsert(entity->getType(), entity->getName());

View File

@ -92,6 +92,7 @@ public:
/// Returns whether there is an entity with such identifier in the storage.
virtual bool exists(const UUID & id) const = 0;
bool exists(const std::vector<UUID> & ids) const;
/// Reads an entity. Throws an exception if not found.
template <typename EntityClassT = IAccessEntity>
@ -100,6 +101,9 @@ public:
template <typename EntityClassT = IAccessEntity>
std::shared_ptr<const EntityClassT> read(const String & name, bool throw_if_not_exists = true) const;
template <typename EntityClassT = IAccessEntity>
std::vector<AccessEntityPtr> read(const std::vector<UUID> & ids, bool throw_if_not_exists = true) const;
/// Reads an entity. Returns nullptr if not found.
template <typename EntityClassT = IAccessEntity>
std::shared_ptr<const EntityClassT> tryRead(const UUID & id) const;
@ -128,7 +132,9 @@ public:
/// Throws an exception if the specified name already exists.
UUID insert(const AccessEntityPtr & entity);
std::optional<UUID> insert(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists);
std::optional<UUID> insert(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id);
std::vector<UUID> insert(const std::vector<AccessEntityPtr> & multiple_entities, bool replace_if_exists = false, bool throw_if_exists = true);
std::vector<UUID> insert(const std::vector<AccessEntityPtr> & multiple_entities, const std::vector<UUID> & ids, bool replace_if_exists = false, bool throw_if_exists = true);
/// Inserts an entity to the storage. Returns ID of a new entry in the storage.
std::optional<UUID> tryInsert(const AccessEntityPtr & entity);
@ -179,7 +185,7 @@ protected:
virtual std::vector<UUID> findAllImpl(AccessEntityType type) const = 0;
virtual AccessEntityPtr readImpl(const UUID & id, bool throw_if_not_exists) const = 0;
virtual std::optional<std::pair<String, AccessEntityType>> readNameWithTypeImpl(const UUID & id, bool throw_if_not_exists) const;
virtual std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists);
virtual std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id);
virtual bool removeImpl(const UUID & id, bool throw_if_not_exists);
virtual bool updateImpl(const UUID & id, const UpdateFunc & update_func, bool throw_if_not_exists);
virtual std::optional<UUID> authenticateImpl(const Credentials & credentials, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool throw_if_user_not_exists, bool allow_no_password, bool allow_plaintext_password) const;
@ -240,6 +246,19 @@ std::shared_ptr<const EntityClassT> IAccessStorage::read(const String & name, bo
}
template <typename EntityClassT>
std::vector<AccessEntityPtr> IAccessStorage::read(const std::vector<UUID> & ids, bool throw_if_not_exists) const
{
std::vector<AccessEntityPtr> result;
result.reserve(ids.size());
for (const auto & id : ids)
result.push_back(read<EntityClassT>(id, throw_if_not_exists));
return result;
}
template <typename EntityClassT>
std::shared_ptr<const EntityClassT> IAccessStorage::tryRead(const UUID & id) const
{

View File

@ -63,9 +63,9 @@ AccessEntityPtr MemoryAccessStorage::readImpl(const UUID & id, bool throw_if_not
}
std::optional<UUID> MemoryAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool replace_if_exists, bool throw_if_exists)
std::optional<UUID> MemoryAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id)
{
UUID id = generateRandomID();
UUID id = set_id ? *set_id : generateRandomID();
if (insertWithID(id, new_entity, replace_if_exists, throw_if_exists))
return id;

View File

@ -44,7 +44,7 @@ private:
std::optional<UUID> findImpl(AccessEntityType type, const String & name) const override;
std::vector<UUID> findAllImpl(AccessEntityType type) const override;
AccessEntityPtr readImpl(const UUID & id, bool throw_if_not_exists) const override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists) override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id) override;
bool removeImpl(const UUID & id, bool throw_if_not_exists) override;
bool updateImpl(const UUID & id, const UpdateFunc & update_func, bool throw_if_not_exists) override;

View File

@ -213,6 +213,42 @@ ConstStoragePtr MultipleAccessStorage::getStorageByName(const DB::String & stora
return const_cast<MultipleAccessStorage *>(this)->getStorageByName(storage_name);
}
StoragePtr MultipleAccessStorage::findExcludingStorage(AccessEntityType type, const DB::String & name, DB::MultipleAccessStorage::StoragePtr exclude) const
{
auto storages = getStoragesInternal();
for (const auto & storage : *storages)
{
if (storage == exclude)
continue;
if (storage->find(type, name))
return storage;
}
return nullptr;
}
void MultipleAccessStorage::moveAccessEntities(const std::vector<UUID> & ids, const String & source_storage_name, const String & destination_storage_name)
{
auto source_storage = findStorageByName(source_storage_name);
auto destination_storage = findStorageByName(destination_storage_name);
auto to_move = source_storage->read(ids);
source_storage->remove(ids);
try
{
destination_storage->insert(to_move, ids);
}
catch (Exception & e)
{
e.addMessage("while moving access entities");
source_storage->insert(to_move, ids);
throw;
}
}
AccessEntityPtr MultipleAccessStorage::readImpl(const UUID & id, bool throw_if_not_exists) const
{
if (auto storage = findStorage(id))
@ -280,7 +316,7 @@ void MultipleAccessStorage::reload(ReloadMode reload_mode)
}
std::optional<UUID> MultipleAccessStorage::insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists)
std::optional<UUID> MultipleAccessStorage::insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id)
{
std::shared_ptr<IAccessStorage> storage_for_insertion;
@ -303,7 +339,7 @@ std::optional<UUID> MultipleAccessStorage::insertImpl(const AccessEntityPtr & en
getStorageName());
}
auto id = storage_for_insertion->insert(entity, replace_if_exists, throw_if_exists);
auto id = storage_for_insertion->insert(entity, replace_if_exists, throw_if_exists, set_id);
if (id)
{
std::lock_guard lock{mutex};

View File

@ -46,6 +46,11 @@ public:
ConstStoragePtr getStorageByName(const String & storage_name) const;
StoragePtr getStorageByName(const String & storage_name);
/// Search for an access entity storage, excluding one. Returns nullptr if not found.
StoragePtr findExcludingStorage(AccessEntityType type, const String & name, StoragePtr exclude) const;
void moveAccessEntities(const std::vector<UUID> & ids, const String & source_storage_name, const String & destination_storage_name);
bool exists(const UUID & id) const override;
bool isBackupAllowed() const override;
@ -58,7 +63,7 @@ protected:
std::vector<UUID> findAllImpl(AccessEntityType type) const override;
AccessEntityPtr readImpl(const UUID & id, bool throw_if_not_exists) const override;
std::optional<std::pair<String, AccessEntityType>> readNameWithTypeImpl(const UUID & id, bool throw_if_not_exists) const override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists) override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id) override;
bool removeImpl(const UUID & id, bool throw_if_not_exists) override;
bool updateImpl(const UUID & id, const UpdateFunc & update_func, bool throw_if_not_exists) override;
std::optional<UUID> authenticateImpl(const Credentials & credentials, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool throw_if_user_not_exists, bool allow_no_password, bool allow_plaintext_password) const override;
@ -70,6 +75,8 @@ private:
std::shared_ptr<const Storages> nested_storages TSA_GUARDED_BY(mutex);
mutable CacheBase<UUID, Storage> ids_cache TSA_GUARDED_BY(mutex);
mutable std::mutex mutex;
mutable std::mutex move_mutex;
};
}

View File

@ -108,9 +108,9 @@ static void retryOnZooKeeperUserError(size_t attempts, Func && function)
}
}
std::optional<UUID> ReplicatedAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool replace_if_exists, bool throw_if_exists)
std::optional<UUID> ReplicatedAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id)
{
const UUID id = generateRandomID();
const UUID id = set_id ? *set_id : generateRandomID();
if (insertWithID(id, new_entity, replace_if_exists, throw_if_exists))
return id;

View File

@ -46,7 +46,7 @@ private:
std::unique_ptr<ThreadFromGlobalPool> watching_thread;
std::shared_ptr<ConcurrentBoundedQueue<UUID>> watched_queue;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists) override;
std::optional<UUID> insertImpl(const AccessEntityPtr & entity, bool replace_if_exists, bool throw_if_exists, std::optional<UUID> set_id) override;
bool removeImpl(const UUID & id, bool throw_if_not_exists) override;
bool updateImpl(const UUID & id, const UpdateFunc & update_func, bool throw_if_not_exists) override;

View File

@ -14,6 +14,12 @@
namespace DB
{
namespace ErrorCodes
{
extern const int ACCESS_ENTITY_ALREADY_EXISTS;
}
namespace
{
void updateQuotaFromQueryImpl(
@ -90,6 +96,15 @@ BlockIO InterpreterCreateQuotaQuery::execute()
if (query.roles)
roles_from_query = RolesOrUsersSet{*query.roles, access_control, getContext()->getUserID()};
IAccessStorage * storage = &access_control;
MultipleAccessStorage::StoragePtr storage_ptr;
if (!query.storage_name.empty())
{
storage_ptr = access_control.getStorageByName(query.storage_name);
storage = storage_ptr.get();
}
if (query.alter)
{
auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr
@ -100,11 +115,11 @@ BlockIO InterpreterCreateQuotaQuery::execute()
};
if (query.if_exists)
{
auto ids = access_control.find<Quota>(query.names);
access_control.tryUpdate(ids, update_func);
auto ids = storage->find<Quota>(query.names);
storage->tryUpdate(ids, update_func);
}
else
access_control.update(access_control.getIDs<Quota>(query.names), update_func);
storage->update(storage->getIDs<Quota>(query.names), update_func);
}
else
{
@ -116,12 +131,21 @@ BlockIO InterpreterCreateQuotaQuery::execute()
new_quotas.emplace_back(std::move(new_quota));
}
if (!query.storage_name.empty())
{
for (const auto & name : query.names)
{
if (auto another_storage_ptr = access_control.findExcludingStorage(AccessEntityType::QUOTA, name, storage_ptr))
throw Exception(ErrorCodes::ACCESS_ENTITY_ALREADY_EXISTS, "Quota {} already exists in storage {}", name, another_storage_ptr->getStorageName());
}
}
if (query.if_not_exists)
access_control.tryInsert(new_quotas);
storage->tryInsert(new_quotas);
else if (query.or_replace)
access_control.insertOrReplace(new_quotas);
storage->insertOrReplace(new_quotas);
else
access_control.insert(new_quotas);
storage->insert(new_quotas);
}
return {};

View File

@ -97,13 +97,7 @@ BlockIO InterpreterCreateRoleQuery::execute()
{
for (const auto & name : query.names)
{
auto id = access_control.find<Role>(name);
if (!id)
continue;
auto another_storage_ptr = access_control.findStorage(*id);
if (another_storage_ptr != storage_ptr)
if (auto another_storage_ptr = access_control.findExcludingStorage(AccessEntityType::ROLE, name, storage_ptr))
throw Exception(ErrorCodes::ACCESS_ENTITY_ALREADY_EXISTS, "Role {} already exists in storage {}", name, another_storage_ptr->getStorageName());
}
}

View File

@ -14,6 +14,12 @@
namespace DB
{
namespace ErrorCodes
{
extern const int ACCESS_ENTITY_ALREADY_EXISTS;
}
namespace
{
void updateRowPolicyFromQueryImpl(
@ -66,6 +72,16 @@ BlockIO InterpreterCreateRowPolicyQuery::execute()
if (query.roles)
roles_from_query = RolesOrUsersSet{*query.roles, access_control, getContext()->getUserID()};
IAccessStorage * storage = &access_control;
MultipleAccessStorage::StoragePtr storage_ptr;
if (!query.storage_name.empty())
{
storage_ptr = access_control.getStorageByName(query.storage_name);
storage = storage_ptr.get();
}
Strings names = query.names->toStrings();
if (query.alter)
{
auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr
@ -74,14 +90,13 @@ BlockIO InterpreterCreateRowPolicyQuery::execute()
updateRowPolicyFromQueryImpl(*updated_policy, query, {}, roles_from_query);
return updated_policy;
};
Strings names = query.names->toStrings();
if (query.if_exists)
{
auto ids = access_control.find<RowPolicy>(names);
access_control.tryUpdate(ids, update_func);
auto ids = storage->find<RowPolicy>(names);
storage->tryUpdate(ids, update_func);
}
else
access_control.update(access_control.getIDs<RowPolicy>(names), update_func);
storage->update(storage->getIDs<RowPolicy>(names), update_func);
}
else
{
@ -93,12 +108,21 @@ BlockIO InterpreterCreateRowPolicyQuery::execute()
new_policies.emplace_back(std::move(new_policy));
}
if (!query.storage_name.empty())
{
for (const auto & name : names)
{
if (auto another_storage_ptr = access_control.findExcludingStorage(AccessEntityType::ROW_POLICY, name, storage_ptr))
throw Exception(ErrorCodes::ACCESS_ENTITY_ALREADY_EXISTS, "Row policy {} already exists in storage {}", name, another_storage_ptr->getStorageName());
}
}
if (query.if_not_exists)
access_control.tryInsert(new_policies);
storage->tryInsert(new_policies);
else if (query.or_replace)
access_control.insertOrReplace(new_policies);
storage->insertOrReplace(new_policies);
else
access_control.insert(new_policies);
storage->insert(new_policies);
}
return {};

View File

@ -10,6 +10,12 @@
namespace DB
{
namespace ErrorCodes
{
extern const int ACCESS_ENTITY_ALREADY_EXISTS;
}
namespace
{
void updateSettingsProfileFromQueryImpl(
@ -67,6 +73,16 @@ BlockIO InterpreterCreateSettingsProfileQuery::execute()
if (query.to_roles)
roles_from_query = RolesOrUsersSet{*query.to_roles, access_control, getContext()->getUserID()};
IAccessStorage * storage = &access_control;
MultipleAccessStorage::StoragePtr storage_ptr;
if (!query.storage_name.empty())
{
storage_ptr = access_control.getStorageByName(query.storage_name);
storage = storage_ptr.get();
}
if (query.alter)
{
auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr
@ -77,11 +93,11 @@ BlockIO InterpreterCreateSettingsProfileQuery::execute()
};
if (query.if_exists)
{
auto ids = access_control.find<SettingsProfile>(query.names);
access_control.tryUpdate(ids, update_func);
auto ids = storage->find<SettingsProfile>(query.names);
storage->tryUpdate(ids, update_func);
}
else
access_control.update(access_control.getIDs<SettingsProfile>(query.names), update_func);
storage->update(storage->getIDs<SettingsProfile>(query.names), update_func);
}
else
{
@ -93,12 +109,21 @@ BlockIO InterpreterCreateSettingsProfileQuery::execute()
new_profiles.emplace_back(std::move(new_profile));
}
if (!query.storage_name.empty())
{
for (const auto & name : query.names)
{
if (auto another_storage_ptr = access_control.findExcludingStorage(AccessEntityType::SETTINGS_PROFILE, name, storage_ptr))
throw Exception(ErrorCodes::ACCESS_ENTITY_ALREADY_EXISTS, "Settings profile {} already exists in storage {}", name, another_storage_ptr->getStorageName());
}
}
if (query.if_not_exists)
access_control.tryInsert(new_profiles);
storage->tryInsert(new_profiles);
else if (query.or_replace)
access_control.insertOrReplace(new_profiles);
storage->insertOrReplace(new_profiles);
else
access_control.insert(new_profiles);
storage->insert(new_profiles);
}
return {};

View File

@ -17,6 +17,7 @@ namespace DB
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
extern const int ACCESS_ENTITY_ALREADY_EXISTS;
}
namespace
{
@ -139,6 +140,16 @@ BlockIO InterpreterCreateUserQuery::execute()
if (!query.cluster.empty())
return executeDDLQueryOnCluster(query_ptr, getContext());
IAccessStorage * storage = &access_control;
MultipleAccessStorage::StoragePtr storage_ptr;
if (!query.storage_name.empty())
{
storage_ptr = access_control.getStorageByName(query.storage_name);
storage = storage_ptr.get();
}
Strings names = query.names->toStrings();
if (query.alter)
{
std::optional<RolesOrUsersSet> grantees_from_query;
@ -152,14 +163,13 @@ BlockIO InterpreterCreateUserQuery::execute()
return updated_user;
};
Strings names = query.names->toStrings();
if (query.if_exists)
{
auto ids = access_control.find<User>(names);
access_control.tryUpdate(ids, update_func);
auto ids = storage->find<User>(names);
storage->tryUpdate(ids, update_func);
}
else
access_control.update(access_control.getIDs<User>(names), update_func);
storage->update(storage->getIDs<User>(names), update_func);
}
else
{
@ -171,13 +181,22 @@ BlockIO InterpreterCreateUserQuery::execute()
new_users.emplace_back(std::move(new_user));
}
if (!query.storage_name.empty())
{
for (const auto & name : names)
{
if (auto another_storage_ptr = access_control.findExcludingStorage(AccessEntityType::USER, name, storage_ptr))
throw Exception(ErrorCodes::ACCESS_ENTITY_ALREADY_EXISTS, "User {} already exists in storage {}", name, another_storage_ptr->getStorageName());
}
}
std::vector<UUID> ids;
if (query.if_not_exists)
ids = access_control.tryInsert(new_users);
ids = storage->tryInsert(new_users);
else if (query.or_replace)
ids = access_control.insertOrReplace(new_users);
ids = storage->insertOrReplace(new_users);
else
ids = access_control.insert(new_users);
ids = storage->insert(new_users);
if (query.grantees)
{

View File

@ -0,0 +1,96 @@
#include <Interpreters/Access/InterpreterMoveAccessEntityQuery.h>
#include <Parsers/Access/ASTMoveAccessEntityQuery.h>
#include <Parsers/Access/ASTRowPolicyName.h>
#include <Access/AccessControl.h>
#include <Access/Common/AccessRightsElement.h>
#include <Interpreters/executeDDLQueryOnCluster.h>
namespace DB
{
namespace ErrorCodes
{
extern const int NOT_IMPLEMENTED;
extern const int ACCESS_ENTITY_NOT_FOUND;
}
BlockIO InterpreterMoveAccessEntityQuery::execute()
{
auto & query = query_ptr->as<ASTMoveAccessEntityQuery &>();
auto & access_control = getContext()->getAccessControl();
getContext()->checkAccess(getRequiredAccess());
if (!query.cluster.empty())
return executeDDLQueryOnCluster(query_ptr, getContext());
query.replaceEmptyDatabase(getContext()->getCurrentDatabase());
std::vector<UUID> ids;
if (query.type == AccessEntityType::ROW_POLICY)
ids = access_control.find(query.type, query.row_policy_names->toStrings());
else
ids = access_control.find(query.type, query.names);
if (ids.empty())
return {};
/// Validate that all entities are from the same storage.
const auto source_storage = access_control.findStorage(ids.front());
if (!source_storage->exists(ids))
throw Exception(ErrorCodes::ACCESS_ENTITY_NOT_FOUND, "All access entities must be from the same storage in order to be moved");
access_control.moveAccessEntities(ids, source_storage->getStorageName(), query.storage_name);
return {};
}
AccessRightsElements InterpreterMoveAccessEntityQuery::getRequiredAccess() const
{
const auto & query = query_ptr->as<const ASTMoveAccessEntityQuery &>();
AccessRightsElements res;
switch (query.type)
{
case AccessEntityType::USER:
{
res.emplace_back(AccessType::DROP_USER);
res.emplace_back(AccessType::CREATE_USER);
return res;
}
case AccessEntityType::ROLE:
{
res.emplace_back(AccessType::DROP_ROLE);
res.emplace_back(AccessType::CREATE_ROLE);
return res;
}
case AccessEntityType::SETTINGS_PROFILE:
{
res.emplace_back(AccessType::DROP_SETTINGS_PROFILE);
res.emplace_back(AccessType::CREATE_SETTINGS_PROFILE);
return res;
}
case AccessEntityType::ROW_POLICY:
{
if (query.row_policy_names)
{
for (const auto & row_policy_name : query.row_policy_names->full_names)
{
res.emplace_back(AccessType::DROP_ROW_POLICY, row_policy_name.database, row_policy_name.table_name);
res.emplace_back(AccessType::CREATE_ROW_POLICY, row_policy_name.database, row_policy_name.table_name);
}
}
return res;
}
case AccessEntityType::QUOTA:
{
res.emplace_back(AccessType::DROP_QUOTA);
res.emplace_back(AccessType::CREATE_QUOTA);
return res;
}
case AccessEntityType::MAX:
break;
}
throw Exception(ErrorCodes::NOT_IMPLEMENTED, "{}: type is not supported by DROP query", toString(query.type));
}
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <Interpreters/IInterpreter.h>
#include <Parsers/IAST_fwd.h>
namespace DB
{
class AccessRightsElements;
class InterpreterMoveAccessEntityQuery : public IInterpreter, WithMutableContext
{
public:
InterpreterMoveAccessEntityQuery(const ASTPtr & query_ptr_, ContextMutablePtr context_) : WithMutableContext(context_), query_ptr(query_ptr_) {}
BlockIO execute() override;
private:
AccessRightsElements getRequiredAccess() const;
ASTPtr query_ptr;
};
}

View File

@ -39,6 +39,7 @@
#include <Parsers/Access/ASTCreateUserQuery.h>
#include <Parsers/Access/ASTDropAccessEntityQuery.h>
#include <Parsers/Access/ASTGrantQuery.h>
#include <Parsers/Access/ASTMoveAccessEntityQuery.h>
#include <Parsers/Access/ASTSetRoleQuery.h>
#include <Parsers/Access/ASTShowAccessEntitiesQuery.h>
#include <Parsers/Access/ASTShowAccessQuery.h>
@ -96,6 +97,7 @@
#include <Interpreters/Access/InterpreterCreateUserQuery.h>
#include <Interpreters/Access/InterpreterDropAccessEntityQuery.h>
#include <Interpreters/Access/InterpreterGrantQuery.h>
#include <Interpreters/Access/InterpreterMoveAccessEntityQuery.h>
#include <Interpreters/Access/InterpreterSetRoleQuery.h>
#include <Interpreters/Access/InterpreterShowAccessEntitiesQuery.h>
#include <Interpreters/Access/InterpreterShowAccessQuery.h>
@ -314,6 +316,10 @@ std::unique_ptr<IInterpreter> InterpreterFactory::get(ASTPtr & query, ContextMut
{
return std::make_unique<InterpreterDropAccessEntityQuery>(query, context);
}
else if (query->as<ASTMoveAccessEntityQuery>())
{
return std::make_unique<InterpreterMoveAccessEntityQuery>(query, context);
}
else if (query->as<ASTDropNamedCollectionQuery>())
{
return std::make_unique<InterpreterDropNamedCollectionQuery>(query, context);

View File

@ -170,6 +170,12 @@ void ASTCreateQuotaQuery::formatImpl(const FormatSettings & settings, FormatStat
settings.ostr << (settings.hilite ? hilite_keyword : "") << " OR REPLACE" << (settings.hilite ? hilite_none : "");
formatNames(names, settings);
if (!storage_name.empty())
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "")
<< " IN " << (settings.hilite ? IAST::hilite_none : "")
<< backQuoteIfNeed(storage_name);
formatOnCluster(settings);
if (!new_name.empty())

View File

@ -38,6 +38,7 @@ public:
Strings names;
String new_name;
std::optional<QuotaKeyType> key_type;
String storage_name;
struct Limits
{

View File

@ -74,7 +74,7 @@ void ASTCreateRoleQuery::formatImpl(const FormatSettings & format, FormatState &
if (!storage_name.empty())
format.ostr << (format.hilite ? IAST::hilite_keyword : "")
<< " AT " << (format.hilite ? IAST::hilite_none : "")
<< " IN " << (format.hilite ? IAST::hilite_none : "")
<< backQuoteIfNeed(storage_name);
formatOnCluster(format);

View File

@ -168,6 +168,11 @@ void ASTCreateRowPolicyQuery::formatImpl(const FormatSettings & settings, Format
settings.ostr << " ";
names->format(settings);
if (!storage_name.empty())
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "")
<< " IN " << (settings.hilite ? IAST::hilite_none : "")
<< backQuoteIfNeed(storage_name);
formatOnCluster(settings);
assert(names->cluster.empty());

View File

@ -35,6 +35,7 @@ public:
bool if_exists = false;
bool if_not_exists = false;
bool or_replace = false;
String storage_name;
std::shared_ptr<ASTRowPolicyNames> names;
String new_short_name;

View File

@ -81,6 +81,12 @@ void ASTCreateSettingsProfileQuery::formatImpl(const FormatSettings & format, Fo
format.ostr << (format.hilite ? hilite_keyword : "") << " OR REPLACE" << (format.hilite ? hilite_none : "");
formatNames(names, format);
if (!storage_name.empty())
format.ostr << (format.hilite ? IAST::hilite_keyword : "")
<< " IN " << (format.hilite ? IAST::hilite_none : "")
<< backQuoteIfNeed(storage_name);
formatOnCluster(format);
if (!new_name.empty())

View File

@ -28,6 +28,7 @@ public:
bool if_exists = false;
bool if_not_exists = false;
bool or_replace = false;
String storage_name;
Strings names;
String new_name;

View File

@ -208,6 +208,11 @@ void ASTCreateUserQuery::formatImpl(const FormatSettings & format, FormatState &
format.ostr << " ";
names->format(format);
if (!storage_name.empty())
format.ostr << (format.hilite ? IAST::hilite_keyword : "")
<< " IN " << (format.hilite ? IAST::hilite_none : "")
<< backQuoteIfNeed(storage_name);
formatOnCluster(format);
if (new_name)

View File

@ -45,6 +45,7 @@ public:
std::shared_ptr<ASTUserNamesWithHost> names;
std::optional<String> new_name;
String storage_name;
std::shared_ptr<ASTAuthenticationData> auth_data;

View File

@ -54,8 +54,8 @@ void ASTDropAccessEntityQuery::formatImpl(const FormatSettings & settings, Forma
formatNames(names, settings);
if (!storage_name.empty())
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "")
<< " FROM " << (settings.hilite ? IAST::hilite_none : "")
settings.ostr << (settings.hilite ? hilite_keyword : "")
<< " FROM " << (settings.hilite ? hilite_none : "")
<< backQuoteIfNeed(storage_name);
formatOnCluster(settings);

View File

@ -0,0 +1,64 @@
#include <Parsers/Access/ASTMoveAccessEntityQuery.h>
#include <Parsers/Access/ASTRowPolicyName.h>
#include <Common/quoteString.h>
#include <IO/Operators.h>
namespace DB
{
namespace
{
void formatNames(const Strings & names, const IAST::FormatSettings & settings)
{
bool need_comma = false;
for (const auto & name : names)
{
if (std::exchange(need_comma, true))
settings.ostr << ',';
settings.ostr << ' ' << backQuoteIfNeed(name);
}
}
}
String ASTMoveAccessEntityQuery::getID(char) const
{
return String("MOVE ") + toString(type) + " query";
}
ASTPtr ASTMoveAccessEntityQuery::clone() const
{
auto res = std::make_shared<ASTMoveAccessEntityQuery>(*this);
if (row_policy_names)
res->row_policy_names = std::static_pointer_cast<ASTRowPolicyNames>(row_policy_names->clone());
return res;
}
void ASTMoveAccessEntityQuery::formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const
{
settings.ostr << (settings.hilite ? hilite_keyword : "")
<< "MOVE " << AccessEntityTypeInfo::get(type).name
<< (settings.hilite ? hilite_none : "");
if (type == AccessEntityType::ROW_POLICY)
{
settings.ostr << " ";
row_policy_names->format(settings);
}
else
formatNames(names, settings);
settings.ostr << (settings.hilite ? hilite_keyword : "")
<< " TO " << (settings.hilite ? hilite_none : "")
<< backQuoteIfNeed(storage_name);
formatOnCluster(settings);
}
void ASTMoveAccessEntityQuery::replaceEmptyDatabase(const String & current_database) const
{
if (row_policy_names)
row_policy_names->replaceEmptyDatabase(current_database);
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <Parsers/IAST.h>
#include <Parsers/ASTQueryWithOnCluster.h>
#include <Access/Common/AccessEntityType.h>
namespace DB
{
class ASTRowPolicyNames;
/** MOVE {USER | ROLE | QUOTA | [ROW] POLICY | [SETTINGS] PROFILE} [IF EXISTS] name [,...] [ON [database.]table [,...]] TO storage_name
*/
class ASTMoveAccessEntityQuery : public IAST, public ASTQueryWithOnCluster
{
public:
AccessEntityType type;
Strings names;
std::shared_ptr<ASTRowPolicyNames> row_policy_names;
String storage_name;
String getID(char) const override;
ASTPtr clone() const override;
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
ASTPtr getRewrittenASTWithoutOnCluster(const WithoutOnClusterASTRewriteParams &) const override { return removeOnCluster<ASTMoveAccessEntityQuery>(clone()); }
void replaceEmptyDatabase(const String & current_database) const;
QueryKind getQueryKind() const override { return QueryKind::Move; }
};
}

View File

@ -5,6 +5,7 @@
#include <Parsers/Access/ASTRolesOrUsersSet.h>
#include <Parsers/Access/ParserCreateQuotaQuery.h>
#include <Parsers/Access/ParserRolesOrUsersSet.h>
#include <Parsers/Access/parseUserName.h>
#include <Parsers/CommonParsers.h>
#include <Parsers/ExpressionElementParsers.h>
#include <Parsers/ExpressionListParsers.h>
@ -288,6 +289,7 @@ bool ParserCreateQuotaQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
std::optional<QuotaKeyType> key_type;
std::vector<ASTCreateQuotaQuery::Limits> all_limits;
String cluster;
String storage_name;
while (true)
{
@ -310,6 +312,9 @@ bool ParserCreateQuotaQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
if (storage_name.empty() && ParserKeyword{"IN"}.ignore(pos, expected) && parseStorageName(pos, expected, storage_name))
continue;
break;
}
@ -332,6 +337,7 @@ bool ParserCreateQuotaQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
query->key_type = key_type;
query->all_limits = std::move(all_limits);
query->roles = std::move(roles);
query->storage_name = std::move(storage_name);
return true;
}

View File

@ -93,9 +93,6 @@ bool ParserCreateRoleQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
String cluster;
String storage_name;
if (ParserKeyword{"AT"}.ignore(pos, expected))
parseStorageName(pos, expected, storage_name);
while (true)
{
if (alter && new_name.empty() && (names.size() == 1) && parseRenameTo(pos, expected, new_name))
@ -114,6 +111,9 @@ bool ParserCreateRoleQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
if (storage_name.empty() && ParserKeyword{"IN"}.ignore(pos, expected) && parseStorageName(pos, expected, storage_name))
continue;
break;
}

View File

@ -4,6 +4,7 @@
#include <Parsers/Access/ASTRowPolicyName.h>
#include <Parsers/Access/ParserRolesOrUsersSet.h>
#include <Parsers/Access/ParserRowPolicyName.h>
#include <Parsers/Access/parseUserName.h>
#include <Parsers/ExpressionListParsers.h>
#include <Parsers/ExpressionElementParsers.h>
#include <Parsers/parseDatabaseAndTableName.h>
@ -245,6 +246,7 @@ bool ParserCreateRowPolicyQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
String new_short_name;
std::optional<bool> is_restrictive;
std::vector<std::pair<RowPolicyFilterType, ASTPtr>> filters;
String storage_name;
while (true)
{
@ -271,6 +273,9 @@ bool ParserCreateRowPolicyQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
if (storage_name.empty() && ParserKeyword{"IN"}.ignore(pos, expected) && parseStorageName(pos, expected, storage_name))
continue;
break;
}
@ -294,6 +299,7 @@ bool ParserCreateRowPolicyQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
query->is_restrictive = is_restrictive;
query->filters = std::move(filters);
query->roles = std::move(roles);
query->storage_name = std::move(storage_name);
return true;
}

View File

@ -4,6 +4,7 @@
#include <Parsers/Access/ASTSettingsProfileElement.h>
#include <Parsers/Access/ParserSettingsProfileElement.h>
#include <Parsers/Access/ParserRolesOrUsersSet.h>
#include <Parsers/Access/parseUserName.h>
#include <Parsers/ASTLiteral.h>
#include <Parsers/CommonParsers.h>
#include <Parsers/ExpressionElementParsers.h>
@ -111,6 +112,7 @@ bool ParserCreateSettingsProfileQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
String new_name;
std::shared_ptr<ASTSettingsProfileElements> settings;
String cluster;
String storage_name;
while (true)
{
@ -130,6 +132,9 @@ bool ParserCreateSettingsProfileQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
if (cluster.empty() && parseOnCluster(pos, expected, cluster))
continue;
if (storage_name.empty() && ParserKeyword{"IN"}.ignore(pos, expected) && parseStorageName(pos, expected, storage_name))
continue;
break;
}
@ -152,6 +157,7 @@ bool ParserCreateSettingsProfileQuery::parseImpl(Pos & pos, ASTPtr & node, Expec
query->new_name = std::move(new_name);
query->settings = std::move(settings);
query->to_roles = std::move(to_roles);
query->storage_name = std::move(storage_name);
return true;
}

View File

@ -414,6 +414,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
std::shared_ptr<ASTRolesOrUsersSet> grantees;
std::shared_ptr<ASTDatabaseOrNone> default_database;
String cluster;
String storage_name;
while (true)
{
@ -480,6 +481,9 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
}
}
if (storage_name.empty() && ParserKeyword{"IN"}.ignore(pos, expected) && parseStorageName(pos, expected, storage_name))
continue;
break;
}
@ -514,6 +518,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
query->settings = std::move(settings);
query->grantees = std::move(grantees);
query->default_database = std::move(default_database);
query->storage_name = std::move(storage_name);
if (query->auth_data)
query->children.push_back(query->auth_data);

View File

@ -0,0 +1,93 @@
#include <Parsers/Access/ParserMoveAccessEntityQuery.h>
#include <Parsers/Access/ASTMoveAccessEntityQuery.h>
#include <Parsers/Access/ParserRowPolicyName.h>
#include <Parsers/Access/ASTRowPolicyName.h>
#include <Parsers/Access/parseUserName.h>
#include <Parsers/CommonParsers.h>
#include <Parsers/parseIdentifierOrStringLiteral.h>
#include <base/range.h>
namespace DB
{
namespace
{
bool parseEntityType(IParserBase::Pos & pos, Expected & expected, AccessEntityType & type)
{
for (auto i : collections::range(AccessEntityType::MAX))
{
const auto & type_info = AccessEntityTypeInfo::get(i);
if (ParserKeyword{type_info.name}.ignore(pos, expected)
|| (!type_info.alias.empty() && ParserKeyword{type_info.alias}.ignore(pos, expected)))
{
type = i;
return true;
}
}
return false;
}
bool parseOnCluster(IParserBase::Pos & pos, Expected & expected, String & cluster)
{
return IParserBase::wrapParseImpl(pos, [&]
{
return ParserKeyword{"ON"}.ignore(pos, expected) && ASTQueryWithOnCluster::parse(pos, cluster, expected);
});
}
}
bool ParserMoveAccessEntityQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
if (!ParserKeyword{"MOVE"}.ignore(pos, expected))
return false;
AccessEntityType type;
if (!parseEntityType(pos, expected, type))
return false;
Strings names;
std::shared_ptr<ASTRowPolicyNames> row_policy_names;
String storage_name;
String cluster;
if ((type == AccessEntityType::USER) || (type == AccessEntityType::ROLE))
{
if (!parseUserNames(pos, expected, names))
return false;
}
else if (type == AccessEntityType::ROW_POLICY)
{
ParserRowPolicyNames parser;
ASTPtr ast;
parser.allowOnCluster();
if (!parser.parse(pos, ast, expected))
return false;
row_policy_names = typeid_cast<std::shared_ptr<ASTRowPolicyNames>>(ast);
cluster = std::exchange(row_policy_names->cluster, "");
}
else
{
if (!parseIdentifiersOrStringLiterals(pos, expected, names))
return false;
}
if (!ParserKeyword{"TO"}.ignore(pos, expected) || !parseStorageName(pos, expected, storage_name))
return false;
if (cluster.empty())
parseOnCluster(pos, expected, cluster);
auto query = std::make_shared<ASTMoveAccessEntityQuery>();
node = query;
query->type = type;
query->cluster = std::move(cluster);
query->names = std::move(names);
query->row_policy_names = std::move(row_policy_names);
query->storage_name = std::move(storage_name);
return true;
}
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <Parsers/IParserBase.h>
namespace DB
{
/** Parses queries like
* MOVE {USER | ROLE | QUOTA | [ROW] POLICY | [SETTINGS] PROFILE} [IF EXISTS] name [,...] [ON [database.]table [,...]] TO storage_name
*/
class ParserMoveAccessEntityQuery : public IParserBase
{
protected:
const char * getName() const override { return "MOVE access entity query"; }
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
};
}

View File

@ -2,6 +2,7 @@
#include <Core/Types.h>
#include <Parsers/IParser.h>
#include <Parsers/parseIdentifierOrStringLiteral.h>
namespace DB
@ -36,7 +37,7 @@ inline bool parseRoleNames(IParser::Pos & pos, Expected & expected, Strings & ro
inline bool parseStorageName(IParser::Pos & pos, Expected & expected, String & storage_name)
{
return parseUserName(pos, expected, storage_name);
return parseIdentifierOrStringLiteral(pos, expected, storage_name);
}
}

View File

@ -290,6 +290,7 @@ public:
Alter,
Grant,
Revoke,
Move,
System,
Set,
Use,

View File

@ -27,6 +27,7 @@
#include <Parsers/Access/ParserCreateUserQuery.h>
#include <Parsers/Access/ParserDropAccessEntityQuery.h>
#include <Parsers/Access/ParserGrantQuery.h>
#include <Parsers/Access/ParserMoveAccessEntityQuery.h>
#include <Parsers/Access/ParserSetRoleQuery.h>
@ -54,6 +55,7 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
ParserCreateIndexQuery create_index_p;
ParserDropIndexQuery drop_index_p;
ParserDropAccessEntityQuery drop_access_entity_p;
ParserMoveAccessEntityQuery move_access_entity_p;
ParserGrantQuery grant_p;
ParserSetRoleQuery set_role_p;
ParserExternalDDLQuery external_ddl_p;
@ -80,6 +82,7 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|| create_index_p.parse(pos, node, expected)
|| drop_index_p.parse(pos, node, expected)
|| drop_access_entity_p.parse(pos, node, expected)
|| move_access_entity_p.parse(pos, node, expected)
|| grant_p.parse(pos, node, expected)
|| external_ddl_p.parse(pos, node, expected)
|| transaction_control_p.parse(pos, node, expected)

View File

@ -471,7 +471,7 @@ def test_introspection():
[
[
"A",
"local directory",
"local_directory",
"no_password",
"{}",
"['::/0']",
@ -484,7 +484,7 @@ def test_introspection():
],
[
"B",
"local directory",
"local_directory",
"no_password",
"{}",
"['::/0']",

View File

@ -0,0 +1,5 @@
<clickhouse>
<user_directories>
<memory/>
</user_directories>
</clickhouse>

View File

@ -1,8 +1,4 @@
<clickhouse>
<user_directories>
<memory/>
</user_directories>
<roles>
<default_role>
<grants>

View File

@ -8,6 +8,7 @@ cluster = ClickHouseCluster(__file__)
node = cluster.add_instance(
"node",
stay_alive=True,
main_configs=["configs/memory.xml"]
)
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
@ -28,6 +29,68 @@ def started_cluster():
cluster.shutdown()
def execute_test_for_access_type(access_type: str, system_table_name: str):
node.query(f"CREATE {access_type} test1 IN local_directory")
node.query(f"CREATE {access_type} test2 IN local_directory")
node.query(f"CREATE {access_type} test3 IN local_directory")
node.query(f"CREATE {access_type} test4 IN memory")
node.query(f"CREATE {access_type} test5 IN memory")
node.query(f"CREATE {access_type} test6 IN memory")
# Already exists
with pytest.raises(QueryRuntimeException):
node.query(f"CREATE {access_type} test1 IN memory")
node.query(f"MOVE {access_type} test1 TO memory")
assert node.query(f"SELECT storage FROM system.{system_table_name} WHERE name = 'test1'") == TSV(["memory"])
node.query(f"MOVE {access_type} test2 TO local_directory")
assert node.query(f"SELECT storage FROM system.{system_table_name} WHERE name = 'test2'") == TSV(["local_directory"])
node.query(f"MOVE {access_type} test2,test3 TO memory")
assert node.query(f"SELECT storage FROM system.{system_table_name} WHERE name = 'test2'") == TSV(["memory"])
assert node.query(f"SELECT storage FROM system.{system_table_name} WHERE name = 'test3'") == TSV(["memory"])
node.query(f"MOVE {access_type} test4,test5 TO local_directory")
# Different storages
with pytest.raises(QueryRuntimeException):
node.query(f"MOVE {access_type} test4,test1 TO memory")
# Doesn't exist
with pytest.raises(QueryRuntimeException):
node.query(f"MOVE {access_type} test7 TO local_directory")
# Storage doesn't exist
with pytest.raises(QueryRuntimeException):
node.query(f"MOVE {access_type} test6 TO non_existing_storage")
# Unwriteable storage
with pytest.raises(QueryRuntimeException):
node.query(f"MOVE {access_type} test6 TO users_xml")
def test_roles():
execute_test_for_access_type("ROLE", "roles")
def test_users():
execute_test_for_access_type("USER", "users")
def test_settings_profiles():
execute_test_for_access_type("SETTINGS PROFILE", "settings_profiles")
def test_quotas():
execute_test_for_access_type("QUOTA", "quotas")
def test_row_policies():
execute_test_for_access_type("ROW POLICY", "row_policies")
def test_role_from_different_storages():
node.query("CREATE ROLE default_role")
node.query("GRANT SELECT ON system.* TO default_role")
@ -72,9 +135,9 @@ def test_role_from_different_storages():
# Already exists
with pytest.raises(QueryRuntimeException):
node.query("CREATE ROLE default_role AT memory")
node.query("CREATE ROLE default_role IN memory")
node.query("CREATE ROLE other_role AT memory")
node.query("CREATE ROLE other_role IN memory")
assert node.query(
"SELECT storage FROM system.roles WHERE name = 'other_role'"

View File

@ -287,7 +287,7 @@ def test_introspection():
assert instance.query(
"SELECT name, storage from system.roles WHERE name IN ('R1', 'R2') ORDER BY name"
) == TSV([["R1", "local directory"], ["R2", "local directory"]])
) == TSV([["R1", "local_directory"], ["R2", "local_directory"]])
assert instance.query(
"SELECT * from system.grants WHERE user_name IN ('A', 'B') OR role_name IN ('R1', 'R2') ORDER BY user_name, role_name, access_type, database, table, column, is_partial_revoke, grant_option"

View File

@ -88,7 +88,7 @@ def test_smoke():
)
)
assert system_settings_profile("xyz") == [
["xyz", "local directory", 1, 0, "['robin']", "[]"]
["xyz", "local_directory", 1, 0, "['robin']", "[]"]
]
assert system_settings_profile_elements(profile_name="xyz") == [
[
@ -120,7 +120,7 @@ def test_smoke():
instance.query("SET max_memory_usage = 80000000", user="robin")
instance.query("SET max_memory_usage = 120000000", user="robin")
assert system_settings_profile("xyz") == [
["xyz", "local directory", 1, 0, "[]", "[]"]
["xyz", "local_directory", 1, 0, "[]", "[]"]
]
assert system_settings_profile_elements(user_name="robin") == []
@ -201,7 +201,7 @@ def test_settings_from_granted_role():
)
)
assert system_settings_profile("xyz") == [
["xyz", "local directory", 2, 0, "[]", "[]"]
["xyz", "local_directory", 2, 0, "[]", "[]"]
]
assert system_settings_profile_elements(profile_name="xyz") == [
[
@ -276,7 +276,7 @@ def test_settings_from_granted_role():
)
)
assert system_settings_profile("xyz") == [
["xyz", "local directory", 2, 0, "['worker']", "[]"]
["xyz", "local_directory", 2, 0, "['worker']", "[]"]
]
instance.query("ALTER SETTINGS PROFILE xyz TO NONE")
@ -293,7 +293,7 @@ def test_settings_from_granted_role():
)
instance.query("SET max_memory_usage = 120000000", user="robin")
assert system_settings_profile("xyz") == [
["xyz", "local directory", 2, 0, "[]", "[]"]
["xyz", "local_directory", 2, 0, "[]", "[]"]
]
@ -323,7 +323,7 @@ def test_inheritance():
)
assert system_settings_profile("xyz") == [
["xyz", "local directory", 1, 0, "[]", "[]"]
["xyz", "local_directory", 1, 0, "[]", "[]"]
]
assert system_settings_profile_elements(profile_name="xyz") == [
[
@ -340,7 +340,7 @@ def test_inheritance():
]
]
assert system_settings_profile("alpha") == [
["alpha", "local directory", 1, 0, "['robin']", "[]"]
["alpha", "local_directory", 1, 0, "['robin']", "[]"]
]
assert system_settings_profile_elements(profile_name="alpha") == [
["alpha", "\\N", "\\N", 0, "\\N", "\\N", "\\N", "\\N", "\\N", "xyz"]

View File

@ -38,14 +38,14 @@ def test_old_style():
assert node.query("SELECT * FROM system.user_directories") == TSV(
[
[
"users.xml",
"users.xml",
"users_xml",
"users_xml",
'{"path":"\\\\/etc\\\\/clickhouse-server\\\\/users2.xml"}',
1,
],
[
"local directory",
"local directory",
"local_directory",
"local_directory",
'{"path":"\\\\/var\\\\/lib\\\\/clickhouse\\\\/access2\\\\/"}',
2,
],
@ -62,20 +62,20 @@ def test_local_directories():
assert node.query("SELECT * FROM system.user_directories") == TSV(
[
[
"users.xml",
"users.xml",
"users_xml",
"users_xml",
'{"path":"\\\\/etc\\\\/clickhouse-server\\\\/users3.xml"}',
1,
],
[
"local directory",
"local directory",
"local_directory",
"local_directory",
'{"path":"\\\\/var\\\\/lib\\\\/clickhouse\\\\/access3\\\\/"}',
2,
],
[
"local directory (ro)",
"local directory",
"local_directory",
'{"path":"\\\\/var\\\\/lib\\\\/clickhouse\\\\/access3-ro\\\\/","readonly":true}',
3,
],
@ -92,8 +92,8 @@ def test_relative_path():
assert node.query("SELECT * FROM system.user_directories") == TSV(
[
[
"users.xml",
"users.xml",
"users_xml",
"users_xml",
'{"path":"\\\\/etc\\\\/clickhouse-server\\\\/users4.xml"}',
1,
]
@ -110,8 +110,8 @@ def test_memory():
assert node.query("SELECT * FROM system.user_directories") == TSV(
[
[
"users.xml",
"users.xml",
"users_xml",
"users_xml",
'{"path":"\\\\/etc\\\\/clickhouse-server\\\\/users5.xml"}',
1,
],
@ -129,20 +129,20 @@ def test_mixed_style():
assert node.query("SELECT * FROM system.user_directories") == TSV(
[
[
"users.xml",
"users.xml",
"users_xml",
"users_xml",
'{"path":"\\\\/etc\\\\/clickhouse-server\\\\/users6.xml"}',
1,
],
[
"local directory",
"local directory",
"local_directory",
"local_directory",
'{"path":"\\\\/var\\\\/lib\\\\/clickhouse\\\\/access6\\\\/"}',
2,
],
[
"local directory",
"local directory",
"local_directory",
"local_directory",
'{"path":"\\\\/var\\\\/lib\\\\/clickhouse\\\\/access6a\\\\/"}',
3,
],
@ -160,14 +160,14 @@ def test_duplicates():
assert node.query("SELECT * FROM system.user_directories") == TSV(
[
[
"users.xml",
"users.xml",
"users_xml",
"users_xml",
'{"path":"\\\\/etc\\\\/clickhouse-server\\\\/users7.xml"}',
1,
],
[
"local directory",
"local directory",
"local_directory",
"local_directory",
'{"path":"\\\\/var\\\\/lib\\\\/clickhouse\\\\/access7\\\\/"}',
2,
],