mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-19 16:20:50 +00:00
Implement MOVE
query for the access entities.
This commit is contained in:
parent
ec27ccc696
commit
2a00e2aa6a
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {};
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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 {};
|
||||
|
@ -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)
|
||||
{
|
||||
|
96
src/Interpreters/Access/InterpreterMoveAccessEntityQuery.cpp
Normal file
96
src/Interpreters/Access/InterpreterMoveAccessEntityQuery.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
24
src/Interpreters/Access/InterpreterMoveAccessEntityQuery.h
Normal file
24
src/Interpreters/Access/InterpreterMoveAccessEntityQuery.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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())
|
||||
|
@ -38,6 +38,7 @@ public:
|
||||
Strings names;
|
||||
String new_name;
|
||||
std::optional<QuotaKeyType> key_type;
|
||||
String storage_name;
|
||||
|
||||
struct Limits
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -45,6 +45,7 @@ public:
|
||||
|
||||
std::shared_ptr<ASTUserNamesWithHost> names;
|
||||
std::optional<String> new_name;
|
||||
String storage_name;
|
||||
|
||||
std::shared_ptr<ASTAuthenticationData> auth_data;
|
||||
|
||||
|
@ -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);
|
||||
|
64
src/Parsers/Access/ASTMoveAccessEntityQuery.cpp
Normal file
64
src/Parsers/Access/ASTMoveAccessEntityQuery.cpp
Normal 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);
|
||||
}
|
||||
}
|
32
src/Parsers/Access/ASTMoveAccessEntityQuery.h
Normal file
32
src/Parsers/Access/ASTMoveAccessEntityQuery.h
Normal 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; }
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
93
src/Parsers/Access/ParserMoveAccessEntityQuery.cpp
Normal file
93
src/Parsers/Access/ParserMoveAccessEntityQuery.cpp
Normal 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;
|
||||
}
|
||||
}
|
17
src/Parsers/Access/ParserMoveAccessEntityQuery.h
Normal file
17
src/Parsers/Access/ParserMoveAccessEntityQuery.h
Normal 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;
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -290,6 +290,7 @@ public:
|
||||
Alter,
|
||||
Grant,
|
||||
Revoke,
|
||||
Move,
|
||||
System,
|
||||
Set,
|
||||
Use,
|
||||
|
@ -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)
|
||||
|
@ -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']",
|
||||
|
@ -0,0 +1,5 @@
|
||||
<clickhouse>
|
||||
<user_directories>
|
||||
<memory/>
|
||||
</user_directories>
|
||||
</clickhouse>
|
@ -1,8 +1,4 @@
|
||||
<clickhouse>
|
||||
<user_directories>
|
||||
<memory/>
|
||||
</user_directories>
|
||||
|
||||
<roles>
|
||||
<default_role>
|
||||
<grants>
|
||||
|
@ -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'"
|
||||
|
@ -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"
|
||||
|
@ -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"]
|
||||
|
@ -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,
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user