Merge pull request #66590 from vitlibar/move-view-targets-to-separate-ast

Move view targets to separate AST class ASTViewTargets
This commit is contained in:
Vitaly Baranov 2024-07-22 17:20:09 +00:00 committed by GitHub
commit 4f02ded96e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1142 additions and 243 deletions

View File

@ -105,7 +105,7 @@ bool compareRestoredTableDef(const IAST & restored_table_create_query, const IAS
auto new_query = query.clone();
adjustCreateQueryForBackup(new_query, global_context);
ASTCreateQuery & create = typeid_cast<ASTCreateQuery &>(*new_query);
create.setUUID({});
create.resetUUIDs();
create.if_not_exists = false;
return new_query;
};

View File

@ -1,4 +1,5 @@
#include <Backups/RestoreCoordinationLocal.h>
#include <Parsers/ASTCreateQuery.h>
#include <Parsers/formatAST.h>
#include <Common/logger_useful.h>
@ -67,7 +68,7 @@ void RestoreCoordinationLocal::generateUUIDForTable(ASTCreateQuery & create_quer
auto it = create_query_uuids.find(query_str);
if (it != create_query_uuids.end())
{
create_query.setUUID(it->second);
it->second.copyToQuery(create_query);
return true;
}
return false;
@ -79,7 +80,8 @@ void RestoreCoordinationLocal::generateUUIDForTable(ASTCreateQuery & create_quer
return;
}
auto new_uuids = create_query.generateRandomUUID(/* always_generate_new_uuid= */ true);
CreateQueryUUIDs new_uuids{create_query, /* generate_random= */ true, /* force_random= */ true};
new_uuids.copyToQuery(create_query);
{
std::lock_guard lock{mutex};

View File

@ -1,16 +1,17 @@
#pragma once
#include <Backups/IRestoreCoordination.h>
#include <Parsers/ASTCreateQuery.h>
#include <Parsers/CreateQueryUUIDs.h>
#include <Common/Logger.h>
#include <mutex>
#include <set>
#include <unordered_set>
namespace Poco { class Logger; }
namespace DB
{
class ASTCreateQuery;
/// Implementation of the IRestoreCoordination interface performing coordination in memory.
class RestoreCoordinationLocal : public IRestoreCoordination
@ -55,7 +56,7 @@ private:
std::set<std::pair<String /* database_zk_path */, String /* table_name */>> acquired_tables_in_replicated_databases;
std::unordered_set<String /* table_zk_path */> acquired_data_in_replicated_tables;
std::unordered_map<String, ASTCreateQuery::UUIDs> create_query_uuids;
std::unordered_map<String, CreateQueryUUIDs> create_query_uuids;
std::unordered_set<String /* root_zk_path */> acquired_data_in_keeper_map_tables;
mutable std::mutex mutex;

View File

@ -3,6 +3,7 @@
#include <Backups/RestoreCoordinationRemote.h>
#include <Backups/BackupCoordinationStageSync.h>
#include <Parsers/ASTCreateQuery.h>
#include <Parsers/CreateQueryUUIDs.h>
#include <Parsers/formatAST.h>
#include <Functions/UserDefined/UserDefinedSQLObjectType.h>
#include <Common/ZooKeeper/KeeperException.h>
@ -269,7 +270,8 @@ bool RestoreCoordinationRemote::acquireInsertingDataForKeeperMap(const String &
void RestoreCoordinationRemote::generateUUIDForTable(ASTCreateQuery & create_query)
{
String query_str = serializeAST(create_query);
String new_uuids_str = create_query.generateRandomUUID(/* always_generate_new_uuid= */ true).toString();
CreateQueryUUIDs new_uuids{create_query, /* generate_random= */ true, /* force_random= */ true};
String new_uuids_str = new_uuids.toString();
auto holder = with_retries.createRetriesControlHolder("generateUUIDForTable");
holder.retries_ctl.retryLoop(
@ -281,11 +283,14 @@ void RestoreCoordinationRemote::generateUUIDForTable(ASTCreateQuery & create_que
Coordination::Error res = zk->tryCreate(path, new_uuids_str, zkutil::CreateMode::Persistent);
if (res == Coordination::Error::ZOK)
{
new_uuids.copyToQuery(create_query);
return;
}
if (res == Coordination::Error::ZNODEEXISTS)
{
create_query.setUUID(ASTCreateQuery::UUIDs::fromString(zk->get(path)));
CreateQueryUUIDs::fromString(zk->get(path)).copyToQuery(create_query);
return;
}

View File

@ -80,13 +80,20 @@ namespace
/// CREATE TABLE or CREATE DICTIONARY or CREATE VIEW or CREATE TEMPORARY TABLE or CREATE DATABASE query.
void visitCreateQuery(const ASTCreateQuery & create)
{
QualifiedTableName to_table{create.to_table_id.database_name, create.to_table_id.table_name};
if (!to_table.table.empty())
if (create.targets)
{
/// TO target_table (for materialized views)
if (to_table.database.empty())
to_table.database = current_database;
dependencies.emplace(to_table);
for (const auto & target : create.targets->targets)
{
const auto & table_id = target.table_id;
if (!table_id.table_name.empty())
{
/// TO target_table (for materialized views)
QualifiedTableName target_name{table_id.database_name, table_id.table_name};
if (target_name.database.empty())
target_name.database = current_database;
dependencies.emplace(target_name);
}
}
}
QualifiedTableName as_table{create.as_database, create.as_table};

View File

@ -86,12 +86,19 @@ namespace
create.as_table = as_table_new.table;
}
QualifiedTableName to_table{create.to_table_id.database_name, create.to_table_id.table_name};
if (!to_table.table.empty() && !to_table.database.empty())
if (create.targets)
{
auto to_table_new = data.renaming_map.getNewTableName(to_table);
if (to_table_new != to_table)
create.to_table_id = StorageID{to_table_new.database, to_table_new.table};
for (auto & target : create.targets->targets)
{
auto & table_id = target.table_id;
if (!table_id.database_name.empty() && !table_id.table_name.empty())
{
QualifiedTableName target_name{table_id.database_name, table_id.table_name};
auto new_target_name = data.renaming_map.getNewTableName(target_name);
if (new_target_name != target_name)
table_id = StorageID{new_target_name.database, new_target_name.table};
}
}
}
}

View File

@ -729,81 +729,14 @@ void DatabaseReplicated::checkQueryValid(const ASTPtr & query, ContextPtr query_
if (auto * create = query->as<ASTCreateQuery>())
{
bool replicated_table = create->storage && create->storage->engine &&
(startsWith(create->storage->engine->name, "Replicated") || startsWith(create->storage->engine->name, "Shared"));
if (!replicated_table || !create->storage->engine->arguments)
return;
if (create->storage)
checkTableEngine(*create, *create->storage, query_context);
ASTs & args_ref = create->storage->engine->arguments->children;
ASTs args = args_ref;
if (args.size() < 2)
return;
/// It can be a constant expression. Try to evaluate it, ignore exception if we cannot.
bool has_expression_argument = args_ref[0]->as<ASTFunction>() || args_ref[1]->as<ASTFunction>();
if (has_expression_argument)
if (create->targets)
{
try
{
args[0] = evaluateConstantExpressionAsLiteral(args_ref[0]->clone(), query_context);
args[1] = evaluateConstantExpressionAsLiteral(args_ref[1]->clone(), query_context);
}
catch (...) // NOLINT(bugprone-empty-catch)
{
}
for (const auto & inner_table_engine : create->targets->getInnerEngines())
checkTableEngine(*create, *inner_table_engine, query_context);
}
ASTLiteral * arg1 = args[0]->as<ASTLiteral>();
ASTLiteral * arg2 = args[1]->as<ASTLiteral>();
if (!arg1 || !arg2 || arg1->value.getType() != Field::Types::String || arg2->value.getType() != Field::Types::String)
return;
String maybe_path = arg1->value.get<String>();
String maybe_replica = arg2->value.get<String>();
/// Looks like it's ReplicatedMergeTree with explicit zookeeper_path and replica_name arguments.
/// Let's ensure that some macros are used.
/// NOTE: we cannot check here that substituted values will be actually different on shards and replicas.
Macros::MacroExpansionInfo info;
info.table_id = {getDatabaseName(), create->getTable(), create->uuid};
info.shard = getShardName();
info.replica = getReplicaName();
query_context->getMacros()->expand(maybe_path, info);
bool maybe_shard_macros = info.expanded_other;
info.expanded_other = false;
query_context->getMacros()->expand(maybe_replica, info);
bool maybe_replica_macros = info.expanded_other;
bool enable_functional_tests_helper = getContext()->getConfigRef().has("_functional_tests_helper_database_replicated_replace_args_macros");
if (!enable_functional_tests_helper)
{
if (query_context->getSettingsRef().database_replicated_allow_replicated_engine_arguments)
LOG_WARNING(log, "It's not recommended to explicitly specify zookeeper_path and replica_name in ReplicatedMergeTree arguments");
else
throw Exception(ErrorCodes::INCORRECT_QUERY,
"It's not allowed to specify explicit zookeeper_path and replica_name "
"for ReplicatedMergeTree arguments in Replicated database. If you really want to "
"specify them explicitly, enable setting "
"database_replicated_allow_replicated_engine_arguments.");
}
if (maybe_shard_macros && maybe_replica_macros)
return;
if (enable_functional_tests_helper && !has_expression_argument)
{
if (maybe_path.empty() || maybe_path.back() != '/')
maybe_path += '/';
args_ref[0]->as<ASTLiteral>()->value = maybe_path + "auto_{shard}";
args_ref[1]->as<ASTLiteral>()->value = maybe_replica + "auto_{replica}";
return;
}
throw Exception(ErrorCodes::INCORRECT_QUERY,
"Explicit zookeeper_path and replica_name are specified in ReplicatedMergeTree arguments. "
"If you really want to specify it explicitly, then you should use some macros "
"to distinguish different shards and replicas");
}
}
@ -827,6 +760,85 @@ void DatabaseReplicated::checkQueryValid(const ASTPtr & query, ContextPtr query_
}
}
void DatabaseReplicated::checkTableEngine(const ASTCreateQuery & query, ASTStorage & storage, ContextPtr query_context) const
{
bool replicated_table = storage.engine &&
(startsWith(storage.engine->name, "Replicated") || startsWith(storage.engine->name, "Shared"));
if (!replicated_table || !storage.engine->arguments)
return;
ASTs & args_ref = storage.engine->arguments->children;
ASTs args = args_ref;
if (args.size() < 2)
return;
/// It can be a constant expression. Try to evaluate it, ignore exception if we cannot.
bool has_expression_argument = args_ref[0]->as<ASTFunction>() || args_ref[1]->as<ASTFunction>();
if (has_expression_argument)
{
try
{
args[0] = evaluateConstantExpressionAsLiteral(args_ref[0]->clone(), query_context);
args[1] = evaluateConstantExpressionAsLiteral(args_ref[1]->clone(), query_context);
}
catch (...) // NOLINT(bugprone-empty-catch)
{
}
}
ASTLiteral * arg1 = args[0]->as<ASTLiteral>();
ASTLiteral * arg2 = args[1]->as<ASTLiteral>();
if (!arg1 || !arg2 || arg1->value.getType() != Field::Types::String || arg2->value.getType() != Field::Types::String)
return;
String maybe_path = arg1->value.get<String>();
String maybe_replica = arg2->value.get<String>();
/// Looks like it's ReplicatedMergeTree with explicit zookeeper_path and replica_name arguments.
/// Let's ensure that some macros are used.
/// NOTE: we cannot check here that substituted values will be actually different on shards and replicas.
Macros::MacroExpansionInfo info;
info.table_id = {getDatabaseName(), query.getTable(), query.uuid};
info.shard = getShardName();
info.replica = getReplicaName();
query_context->getMacros()->expand(maybe_path, info);
bool maybe_shard_macros = info.expanded_other;
info.expanded_other = false;
query_context->getMacros()->expand(maybe_replica, info);
bool maybe_replica_macros = info.expanded_other;
bool enable_functional_tests_helper = getContext()->getConfigRef().has("_functional_tests_helper_database_replicated_replace_args_macros");
if (!enable_functional_tests_helper)
{
if (query_context->getSettingsRef().database_replicated_allow_replicated_engine_arguments)
LOG_WARNING(log, "It's not recommended to explicitly specify zookeeper_path and replica_name in ReplicatedMergeTree arguments");
else
throw Exception(ErrorCodes::INCORRECT_QUERY,
"It's not allowed to specify explicit zookeeper_path and replica_name "
"for ReplicatedMergeTree arguments in Replicated database. If you really want to "
"specify them explicitly, enable setting "
"database_replicated_allow_replicated_engine_arguments.");
}
if (maybe_shard_macros && maybe_replica_macros)
return;
if (enable_functional_tests_helper && !has_expression_argument)
{
if (maybe_path.empty() || maybe_path.back() != '/')
maybe_path += '/';
args_ref[0]->as<ASTLiteral>()->value = maybe_path + "auto_{shard}";
args_ref[1]->as<ASTLiteral>()->value = maybe_replica + "auto_{replica}";
return;
}
throw Exception(ErrorCodes::INCORRECT_QUERY,
"Explicit zookeeper_path and replica_name are specified in ReplicatedMergeTree arguments. "
"If you really want to specify it explicitly, then you should use some macros "
"to distinguish different shards and replicas");
}
BlockIO DatabaseReplicated::tryEnqueueReplicatedDDL(const ASTPtr & query, ContextPtr query_context, QueryFlags flags)
{
waitDatabaseStarted();
@ -1312,11 +1324,9 @@ ASTPtr DatabaseReplicated::parseQueryFromMetadataInZooKeeper(const String & node
if (create.uuid == UUIDHelpers::Nil || create.getTable() != TABLE_WITH_UUID_NAME_PLACEHOLDER || create.database)
throw Exception(ErrorCodes::LOGICAL_ERROR, "Got unexpected query from {}: {}", node_name, query);
bool is_materialized_view_with_inner_table = create.is_materialized_view && create.to_table_id.empty();
create.setDatabase(getDatabaseName());
create.setTable(unescapeForFileName(node_name));
create.attach = is_materialized_view_with_inner_table;
create.attach = create.is_materialized_view_with_inner_table();
return ast;
}

View File

@ -107,6 +107,7 @@ private:
void fillClusterAuthInfo(String collection_name, const Poco::Util::AbstractConfiguration & config);
void checkQueryValid(const ASTPtr & query, ContextPtr query_context) const;
void checkTableEngine(const ASTCreateQuery & query, ASTStorage & storage, ContextPtr query_context) const;
void recoverLostReplica(const ZooKeeperPtr & current_zookeeper, UInt32 our_log_ptr, UInt32 & max_log_ptr);

View File

@ -949,7 +949,7 @@ namespace
throw Exception(ErrorCodes::INCORRECT_QUERY, "Temporary tables cannot be created with Replicated, Shared or KeeperMap table engines");
}
void setDefaultTableEngine(ASTStorage &storage, DefaultTableEngine engine)
void setDefaultTableEngine(ASTStorage & storage, DefaultTableEngine engine)
{
if (engine == DefaultTableEngine::None)
throw Exception(ErrorCodes::ENGINE_REQUIRED, "Table engine is not specified in CREATE query");
@ -969,9 +969,6 @@ void InterpreterCreateQuery::setEngine(ASTCreateQuery & create) const
if (create.is_dictionary || create.is_ordinary_view || create.is_live_view || create.is_window_view)
return;
if (create.is_materialized_view && create.to_table_id)
return;
if (create.temporary)
{
/// Some part of storage definition is specified, but ENGINE is not: just set the one from default_temporary_table_engine setting.
@ -986,22 +983,44 @@ void InterpreterCreateQuery::setEngine(ASTCreateQuery & create) const
}
if (!create.storage->engine)
{
setDefaultTableEngine(*create.storage, getContext()->getSettingsRef().default_temporary_table_engine.value);
}
checkTemporaryTableEngineName(create.storage->engine->name);
return;
}
if (create.is_materialized_view)
{
/// A materialized view with an external target doesn't need a table engine.
if (create.is_materialized_view_with_external_target())
return;
if (auto to_engine = create.getTargetInnerEngine(ViewTarget::To))
{
/// This materialized view already has a storage definition.
if (!to_engine->engine)
{
/// Some part of storage definition (such as PARTITION BY) is specified, but ENGINE is not: just set default one.
setDefaultTableEngine(*to_engine, getContext()->getSettingsRef().default_table_engine.value);
}
return;
}
}
if (create.storage)
{
/// Some part of storage definition (such as PARTITION BY) is specified, but ENGINE is not: just set default one.
/// This table already has a storage definition.
if (!create.storage->engine)
{
/// Some part of storage definition (such as PARTITION BY) is specified, but ENGINE is not: just set default one.
setDefaultTableEngine(*create.storage, getContext()->getSettingsRef().default_table_engine.value);
}
return;
}
/// We'll try to extract a storage definition from clause `AS`:
/// CREATE TABLE table_name AS other_table_name
std::shared_ptr<ASTStorage> storage_def;
if (!create.as_table.empty())
{
/// NOTE Getting the structure from the table specified in the AS is done not atomically with the creation of the table.
@ -1017,12 +1036,14 @@ void InterpreterCreateQuery::setEngine(ASTCreateQuery & create) const
if (as_create.is_ordinary_view)
throw Exception(ErrorCodes::INCORRECT_QUERY, "Cannot CREATE a table AS {}, it is a View", qualified_name);
if (as_create.is_materialized_view && as_create.to_table_id)
if (as_create.is_materialized_view_with_external_target())
{
throw Exception(
ErrorCodes::INCORRECT_QUERY,
"Cannot CREATE a table AS {}, it is a Materialized View without storage. Use \"AS `{}`\" instead",
"Cannot CREATE a table AS {}, it is a Materialized View without storage. Use \"AS {}\" instead",
qualified_name,
as_create.to_table_id.getQualifiedName());
as_create.getTargetTableID(ViewTarget::To).getFullTableName());
}
if (as_create.is_live_view)
throw Exception(ErrorCodes::INCORRECT_QUERY, "Cannot CREATE a table AS {}, it is a Live View", qualified_name);
@ -1033,18 +1054,37 @@ void InterpreterCreateQuery::setEngine(ASTCreateQuery & create) const
if (as_create.is_dictionary)
throw Exception(ErrorCodes::INCORRECT_QUERY, "Cannot CREATE a table AS {}, it is a Dictionary", qualified_name);
if (as_create.storage)
create.set(create.storage, as_create.storage->ptr());
if (as_create.is_materialized_view)
{
storage_def = as_create.getTargetInnerEngine(ViewTarget::To);
}
else if (as_create.as_table_function)
{
create.set(create.as_table_function, as_create.as_table_function->ptr());
return;
}
else if (as_create.storage)
{
storage_def = typeid_cast<std::shared_ptr<ASTStorage>>(as_create.storage->ptr());
}
else
{
throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot set engine, it's a bug.");
return;
}
}
create.set(create.storage, std::make_shared<ASTStorage>());
setDefaultTableEngine(*create.storage, getContext()->getSettingsRef().default_table_engine.value);
if (!storage_def)
{
/// Set ENGINE by default.
storage_def = std::make_shared<ASTStorage>();
setDefaultTableEngine(*storage_def, getContext()->getSettingsRef().default_table_engine.value);
}
/// Use the found table engine to modify the create query.
if (create.is_materialized_view)
create.setTargetInnerEngine(ViewTarget::To, storage_def);
else
create.set(create.storage, storage_def);
}
void InterpreterCreateQuery::assertOrSetUUID(ASTCreateQuery & create, const DatabasePtr & database) const
@ -1086,11 +1126,11 @@ void InterpreterCreateQuery::assertOrSetUUID(ASTCreateQuery & create, const Data
kind_upper, create.table);
}
create.generateRandomUUID();
create.generateRandomUUIDs();
}
else
{
bool has_uuid = create.uuid != UUIDHelpers::Nil || create.to_inner_uuid != UUIDHelpers::Nil;
bool has_uuid = (create.uuid != UUIDHelpers::Nil) || create.hasInnerUUIDs();
if (has_uuid && !is_on_cluster && !internal)
{
/// We don't show the following error message either
@ -1105,8 +1145,7 @@ void InterpreterCreateQuery::assertOrSetUUID(ASTCreateQuery & create, const Data
/// The database doesn't support UUID so we'll ignore it. The UUID could be set here because of either
/// a) the initiator of `ON CLUSTER` query generated it to ensure the same UUIDs are used on different hosts; or
/// b) `RESTORE from backup` query generated it to ensure the same UUIDs are used on different hosts.
create.uuid = UUIDHelpers::Nil;
create.to_inner_uuid = UUIDHelpers::Nil;
create.resetUUIDs();
}
}
@ -1130,6 +1169,14 @@ void checkTableCanBeAddedWithNoCyclicDependencies(const ASTCreateQuery & create,
DatabaseCatalog::instance().checkTableCanBeAddedWithNoCyclicDependencies(qualified_name, ref_dependencies, loading_dependencies);
}
bool isReplicated(const ASTStorage & storage)
{
if (!storage.engine)
return false;
const auto & storage_name = storage.engine->name;
return storage_name.starts_with("Replicated") || storage_name.starts_with("Shared");
}
}
BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create)
@ -1246,8 +1293,9 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create)
if (!create.temporary && !create.database)
create.setDatabase(current_database);
if (create.to_table_id && create.to_table_id.database_name.empty())
create.to_table_id.database_name = current_database;
if (create.targets)
create.targets->setCurrentDatabase(current_database);
if (create.select && create.isView())
{
@ -1281,12 +1329,9 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create)
TableProperties properties = getTablePropertiesAndNormalizeCreateQuery(create, mode);
/// Check type compatible for materialized dest table and select columns
if (create.select && create.is_materialized_view && create.to_table_id && mode <= LoadingStrictnessLevel::CREATE)
if (create.is_materialized_view_with_external_target() && create.select && mode <= LoadingStrictnessLevel::CREATE)
{
if (StoragePtr to_table = DatabaseCatalog::instance().tryGetTable(
{create.to_table_id.database_name, create.to_table_id.table_name, create.to_table_id.uuid},
getContext()
))
if (StoragePtr to_table = DatabaseCatalog::instance().tryGetTable(create.getTargetTableID(ViewTarget::To), getContext()))
{
Block input_block;
@ -1332,11 +1377,17 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create)
if (!allow_heavy_create && database && database->getEngineName() == "Replicated" && (create.select || create.is_populate))
{
bool is_storage_replicated = false;
if (create.storage && create.storage->engine)
if (create.storage && isReplicated(*create.storage))
is_storage_replicated = true;
if (create.targets)
{
const auto & storage_name = create.storage->engine->name;
if (storage_name.starts_with("Replicated") || storage_name.starts_with("Shared"))
is_storage_replicated = true;
for (const auto & inner_table_engine : create.targets->getInnerEngines())
{
if (isReplicated(*inner_table_engine))
is_storage_replicated = true;
}
}
const bool allow_create_select_for_replicated = (create.isView() && !create.is_populate) || create.is_create_empty || !is_storage_replicated;
@ -1795,7 +1846,7 @@ void InterpreterCreateQuery::prepareOnClusterQuery(ASTCreateQuery & create, Cont
/// For CREATE query generate UUID on initiator, so it will be the same on all hosts.
/// It will be ignored if database does not support UUIDs.
create.generateRandomUUID();
create.generateRandomUUIDs();
/// For cross-replication cluster we cannot use UUID in replica path.
String cluster_name_expanded = local_context->getMacros()->expand(cluster_name);
@ -1917,8 +1968,15 @@ AccessRightsElements InterpreterCreateQuery::getRequiredAccess() const
}
}
if (create.to_table_id)
required_access.emplace_back(AccessType::SELECT | AccessType::INSERT, create.to_table_id.database_name, create.to_table_id.table_name);
if (create.targets)
{
for (const auto & target : create.targets->targets)
{
const auto & target_id = target.table_id;
if (target_id)
required_access.emplace_back(AccessType::SELECT | AccessType::INSERT, target_id.database_name, target_id.table_name);
}
}
if (create.storage && create.storage->engine)
required_access.emplace_back(AccessType::TABLE_ENGINE, create.storage->engine->name);

View File

@ -94,7 +94,8 @@ QueryPipeline InterpreterShowCreateQuery::executeImpl()
{
auto & create = create_query->as<ASTCreateQuery &>();
create.uuid = UUIDHelpers::Nil;
create.to_inner_uuid = UUIDHelpers::Nil;
if (create.targets)
create.targets->resetInnerUUIDs();
}
MutableColumnPtr column = ColumnString::create();

View File

@ -2,6 +2,8 @@
#include <Parsers/ASTExpressionList.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTSelectWithUnionQuery.h>
#include <Parsers/CommonParsers.h>
#include <Parsers/CreateQueryUUIDs.h>
#include <Common/quoteString.h>
#include <Interpreters/StorageID.h>
#include <IO/Operators.h>
@ -240,12 +242,12 @@ ASTPtr ASTCreateQuery::clone() const
res->set(res->columns_list, columns_list->clone());
if (storage)
res->set(res->storage, storage->clone());
if (inner_storage)
res->set(res->inner_storage, inner_storage->clone());
if (select)
res->set(res->select, select->clone());
if (table_overrides)
res->set(res->table_overrides, table_overrides->clone());
if (targets)
res->set(res->targets, targets->clone());
if (dictionary)
{
@ -398,20 +400,18 @@ void ASTCreateQuery::formatQueryImpl(const FormatSettings & settings, FormatStat
refresh_strategy->formatImpl(settings, state, frame);
}
if (to_table_id)
if (auto to_table_id = getTargetTableID(ViewTarget::To))
{
assert((is_materialized_view || is_window_view) && to_inner_uuid == UUIDHelpers::Nil);
settings.ostr
<< (settings.hilite ? hilite_keyword : "") << " TO " << (settings.hilite ? hilite_none : "")
<< (!to_table_id.database_name.empty() ? backQuoteIfNeed(to_table_id.database_name) + "." : "")
<< backQuoteIfNeed(to_table_id.table_name);
settings.ostr << " " << (settings.hilite ? hilite_keyword : "") << toStringView(Keyword::TO)
<< (settings.hilite ? hilite_none : "") << " "
<< (!to_table_id.database_name.empty() ? backQuoteIfNeed(to_table_id.database_name) + "." : "")
<< backQuoteIfNeed(to_table_id.table_name);
}
if (to_inner_uuid != UUIDHelpers::Nil)
if (auto to_inner_uuid = getTargetInnerUUID(ViewTarget::To); to_inner_uuid != UUIDHelpers::Nil)
{
assert(is_materialized_view && !to_table_id);
settings.ostr << (settings.hilite ? hilite_keyword : "") << " TO INNER UUID " << (settings.hilite ? hilite_none : "")
<< quoteString(toString(to_inner_uuid));
settings.ostr << " " << (settings.hilite ? hilite_keyword : "") << toStringView(Keyword::TO_INNER_UUID)
<< (settings.hilite ? hilite_none : "") << " " << quoteString(toString(to_inner_uuid));
}
bool should_add_empty = is_create_empty;
@ -471,14 +471,17 @@ void ASTCreateQuery::formatQueryImpl(const FormatSettings & settings, FormatStat
frame.expression_list_always_start_on_new_line = false;
if (inner_storage)
if (storage)
storage->formatImpl(settings, state, frame);
if (auto inner_storage = getTargetInnerEngine(ViewTarget::Inner))
{
settings.ostr << (settings.hilite ? hilite_keyword : "") << " INNER" << (settings.hilite ? hilite_none : "");
settings.ostr << " " << (settings.hilite ? hilite_keyword : "") << toStringView(Keyword::INNER) << (settings.hilite ? hilite_none : "");
inner_storage->formatImpl(settings, state, frame);
}
if (storage)
storage->formatImpl(settings, state, frame);
if (auto to_storage = getTargetInnerEngine(ViewTarget::To))
to_storage->formatImpl(settings, state, frame);
if (dictionary)
dictionary->formatImpl(settings, state, frame);
@ -538,48 +541,57 @@ bool ASTCreateQuery::isParameterizedView() const
}
ASTCreateQuery::UUIDs::UUIDs(const ASTCreateQuery & query)
: uuid(query.uuid)
, to_inner_uuid(query.to_inner_uuid)
void ASTCreateQuery::generateRandomUUIDs()
{
CreateQueryUUIDs{*this, /* generate_random= */ true}.copyToQuery(*this);
}
String ASTCreateQuery::UUIDs::toString() const
void ASTCreateQuery::resetUUIDs()
{
WriteBufferFromOwnString out;
out << "{" << uuid << "," << to_inner_uuid << "}";
return out.str();
CreateQueryUUIDs{}.copyToQuery(*this);
}
ASTCreateQuery::UUIDs ASTCreateQuery::UUIDs::fromString(const String & str)
StorageID ASTCreateQuery::getTargetTableID(ViewTarget::Kind target_kind) const
{
ReadBufferFromString in{str};
ASTCreateQuery::UUIDs res;
in >> "{" >> res.uuid >> "," >> res.to_inner_uuid >> "}";
return res;
if (targets)
return targets->getTableID(target_kind);
return StorageID::createEmpty();
}
ASTCreateQuery::UUIDs ASTCreateQuery::generateRandomUUID(bool always_generate_new_uuid)
bool ASTCreateQuery::hasTargetTableID(ViewTarget::Kind target_kind) const
{
if (always_generate_new_uuid)
setUUID({});
if (uuid == UUIDHelpers::Nil)
uuid = UUIDHelpers::generateV4();
/// If destination table (to_table_id) is not specified for materialized view,
/// then MV will create inner table. We should generate UUID of inner table here.
bool need_uuid_for_inner_table = !attach && is_materialized_view && !to_table_id;
if (need_uuid_for_inner_table && (to_inner_uuid == UUIDHelpers::Nil))
to_inner_uuid = UUIDHelpers::generateV4();
return UUIDs{*this};
if (targets)
return targets->hasTableID(target_kind);
return false;
}
void ASTCreateQuery::setUUID(const UUIDs & uuids)
UUID ASTCreateQuery::getTargetInnerUUID(ViewTarget::Kind target_kind) const
{
uuid = uuids.uuid;
to_inner_uuid = uuids.to_inner_uuid;
if (targets)
return targets->getInnerUUID(target_kind);
return UUIDHelpers::Nil;
}
bool ASTCreateQuery::hasInnerUUIDs() const
{
if (targets)
return targets->hasInnerUUIDs();
return false;
}
std::shared_ptr<ASTStorage> ASTCreateQuery::getTargetInnerEngine(ViewTarget::Kind target_kind) const
{
if (targets)
return targets->getInnerEngine(target_kind);
return nullptr;
}
void ASTCreateQuery::setTargetInnerEngine(ViewTarget::Kind target_kind, ASTPtr storage_def)
{
if (!targets)
set(targets, std::make_shared<ASTViewTargets>());
targets->setInnerEngine(target_kind, storage_def);
}
}

View File

@ -5,6 +5,7 @@
#include <Parsers/ASTDictionary.h>
#include <Parsers/ASTDictionaryAttributeDeclaration.h>
#include <Parsers/ASTTableOverrides.h>
#include <Parsers/ASTViewTargets.h>
#include <Parsers/ASTSQLSecurity.h>
#include <Parsers/ASTRefreshStrategy.h>
#include <Interpreters/StorageID.h>
@ -15,6 +16,7 @@ namespace DB
class ASTFunction;
class ASTSetQuery;
class ASTSelectWithUnionQuery;
struct CreateQueryUUIDs;
class ASTStorage : public IAST
@ -101,17 +103,15 @@ public:
bool has_uuid{false}; // CREATE TABLE x UUID '...'
ASTColumns * columns_list = nullptr;
StorageID to_table_id = StorageID::createEmpty(); /// For CREATE MATERIALIZED VIEW mv TO table.
UUID to_inner_uuid = UUIDHelpers::Nil; /// For materialized view with inner table
ASTStorage * inner_storage = nullptr; /// For window view with inner table
ASTStorage * storage = nullptr;
ASTPtr watermark_function;
ASTPtr lateness_function;
String as_database;
String as_table;
IAST * as_table_function = nullptr;
ASTSelectWithUnionQuery * select = nullptr;
ASTViewTargets * targets = nullptr;
IAST * comment = nullptr;
ASTPtr sql_security = nullptr;
@ -153,17 +153,26 @@ public:
QueryKind getQueryKind() const override { return QueryKind::Create; }
struct UUIDs
{
UUID uuid = UUIDHelpers::Nil;
UUID to_inner_uuid = UUIDHelpers::Nil;
UUIDs() = default;
explicit UUIDs(const ASTCreateQuery & query);
String toString() const;
static UUIDs fromString(const String & str);
};
UUIDs generateRandomUUID(bool always_generate_new_uuid = false);
void setUUID(const UUIDs & uuids);
/// Generates a random UUID for this create query if it's not specified already.
/// The function also generates random UUIDs for inner target tables if this create query implies that
/// (for example, if it's a `CREATE MATERIALIZED VIEW` query with an inner storage).
void generateRandomUUIDs();
/// Removes UUID from this create query.
/// The function also removes UUIDs for inner target tables from this create query (see also generateRandomUUID()).
void resetUUIDs();
/// Returns information about a target table.
/// If that information isn't specified in this create query (or even not allowed) then the function returns an empty value.
StorageID getTargetTableID(ViewTarget::Kind target_kind) const;
bool hasTargetTableID(ViewTarget::Kind target_kind) const;
UUID getTargetInnerUUID(ViewTarget::Kind target_kind) const;
bool hasInnerUUIDs() const;
std::shared_ptr<ASTStorage> getTargetInnerEngine(ViewTarget::Kind target_kind) const;
void setTargetInnerEngine(ViewTarget::Kind target_kind, ASTPtr storage_def);
bool is_materialized_view_with_external_target() const { return is_materialized_view && hasTargetTableID(ViewTarget::To); }
bool is_materialized_view_with_inner_table() const { return is_materialized_view && !hasTargetTableID(ViewTarget::To); }
protected:
void formatQueryImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override;
@ -171,8 +180,8 @@ protected:
void forEachPointerToChild(std::function<void(void**)> f) override
{
f(reinterpret_cast<void **>(&columns_list));
f(reinterpret_cast<void **>(&inner_storage));
f(reinterpret_cast<void **>(&storage));
f(reinterpret_cast<void **>(&targets));
f(reinterpret_cast<void **>(&as_table_function));
f(reinterpret_cast<void **>(&select));
f(reinterpret_cast<void **>(&comment));

View File

@ -0,0 +1,300 @@
#include <Parsers/ASTViewTargets.h>
#include <Parsers/ASTCreateQuery.h>
#include <Parsers/CommonParsers.h>
#include <IO/WriteHelpers.h>
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
extern const int LOGICAL_ERROR;
}
std::string_view toString(ViewTarget::Kind kind)
{
switch (kind)
{
case ViewTarget::To: return "to";
case ViewTarget::Inner: return "inner";
}
throw Exception(ErrorCodes::LOGICAL_ERROR, "{} doesn't support kind {}", __FUNCTION__, kind);
}
void parseFromString(ViewTarget::Kind & out, std::string_view str)
{
for (auto kind : magic_enum::enum_values<ViewTarget::Kind>())
{
if (toString(kind) == str)
{
out = kind;
return;
}
}
throw Exception(ErrorCodes::BAD_ARGUMENTS, "{}: Unexpected string {}", __FUNCTION__, str);
}
std::vector<ViewTarget::Kind> ASTViewTargets::getKinds() const
{
std::vector<ViewTarget::Kind> kinds;
kinds.reserve(targets.size());
for (const auto & target : targets)
kinds.push_back(target.kind);
return kinds;
}
void ASTViewTargets::setTableID(ViewTarget::Kind kind, const StorageID & table_id_)
{
for (auto & target : targets)
{
if (target.kind == kind)
{
target.table_id = table_id_;
return;
}
}
if (table_id_)
targets.emplace_back(kind).table_id = table_id_;
}
StorageID ASTViewTargets::getTableID(ViewTarget::Kind kind) const
{
if (const auto * target = tryGetTarget(kind))
return target->table_id;
return StorageID::createEmpty();
}
bool ASTViewTargets::hasTableID(ViewTarget::Kind kind) const
{
if (const auto * target = tryGetTarget(kind))
return !target->table_id.empty();
return false;
}
void ASTViewTargets::setCurrentDatabase(const String & current_database)
{
for (auto & target : targets)
{
auto & table_id = target.table_id;
if (!table_id.table_name.empty() && table_id.database_name.empty())
table_id.database_name = current_database;
}
}
void ASTViewTargets::setInnerUUID(ViewTarget::Kind kind, const UUID & inner_uuid_)
{
for (auto & target : targets)
{
if (target.kind == kind)
{
target.inner_uuid = inner_uuid_;
return;
}
}
if (inner_uuid_ != UUIDHelpers::Nil)
targets.emplace_back(kind).inner_uuid = inner_uuid_;
}
UUID ASTViewTargets::getInnerUUID(ViewTarget::Kind kind) const
{
if (const auto * target = tryGetTarget(kind))
return target->inner_uuid;
return UUIDHelpers::Nil;
}
bool ASTViewTargets::hasInnerUUID(ViewTarget::Kind kind) const
{
return getInnerUUID(kind) != UUIDHelpers::Nil;
}
void ASTViewTargets::resetInnerUUIDs()
{
for (auto & target : targets)
target.inner_uuid = UUIDHelpers::Nil;
}
bool ASTViewTargets::hasInnerUUIDs() const
{
for (const auto & target : targets)
{
if (target.inner_uuid != UUIDHelpers::Nil)
return true;
}
return false;
}
void ASTViewTargets::setInnerEngine(ViewTarget::Kind kind, ASTPtr storage_def)
{
auto new_inner_engine = typeid_cast<std::shared_ptr<ASTStorage>>(storage_def);
if (!new_inner_engine && storage_def)
throw Exception(DB::ErrorCodes::LOGICAL_ERROR, "Bad cast from type {} to ASTStorage", storage_def->getID());
for (auto & target : targets)
{
if (target.kind == kind)
{
if (target.inner_engine == new_inner_engine)
return;
if (new_inner_engine)
children.push_back(new_inner_engine);
if (target.inner_engine)
std::erase(children, target.inner_engine);
target.inner_engine = new_inner_engine;
return;
}
}
if (new_inner_engine)
{
targets.emplace_back(kind).inner_engine = new_inner_engine;
children.push_back(new_inner_engine);
}
}
std::shared_ptr<ASTStorage> ASTViewTargets::getInnerEngine(ViewTarget::Kind kind) const
{
if (const auto * target = tryGetTarget(kind))
return target->inner_engine;
return nullptr;
}
std::vector<std::shared_ptr<ASTStorage>> ASTViewTargets::getInnerEngines() const
{
std::vector<std::shared_ptr<ASTStorage>> res;
res.reserve(targets.size());
for (const auto & target : targets)
{
if (target.inner_engine)
res.push_back(target.inner_engine);
}
return res;
}
const ViewTarget * ASTViewTargets::tryGetTarget(ViewTarget::Kind kind) const
{
for (const auto & target : targets)
{
if (target.kind == kind)
return &target;
}
return nullptr;
}
ASTPtr ASTViewTargets::clone() const
{
auto res = std::make_shared<ASTViewTargets>(*this);
res->children.clear();
for (auto & target : res->targets)
{
if (target.inner_engine)
{
target.inner_engine = typeid_cast<std::shared_ptr<ASTStorage>>(target.inner_engine->clone());
res->children.push_back(target.inner_engine);
}
}
return res;
}
void ASTViewTargets::formatImpl(const FormatSettings & s, FormatState & state, FormatStateStacked frame) const
{
for (const auto & target : targets)
formatTarget(target, s, state, frame);
}
void ASTViewTargets::formatTarget(ViewTarget::Kind kind, const FormatSettings & s, FormatState & state, FormatStateStacked frame) const
{
for (const auto & target : targets)
{
if (target.kind == kind)
formatTarget(target, s, state, frame);
}
}
void ASTViewTargets::formatTarget(const ViewTarget & target, const FormatSettings & s, FormatState & state, FormatStateStacked frame)
{
if (target.table_id)
{
auto keyword = getKeywordForTableID(target.kind);
if (!keyword)
throw Exception(ErrorCodes::LOGICAL_ERROR, "No keyword for table name of kind {}", toString(target.kind));
s.ostr << " " << (s.hilite ? hilite_keyword : "") << toStringView(*keyword)
<< (s.hilite ? hilite_none : "") << " "
<< (!target.table_id.database_name.empty() ? backQuoteIfNeed(target.table_id.database_name) + "." : "")
<< backQuoteIfNeed(target.table_id.table_name);
}
if (target.inner_uuid != UUIDHelpers::Nil)
{
auto keyword = getKeywordForInnerUUID(target.kind);
if (!keyword)
throw Exception(ErrorCodes::LOGICAL_ERROR, "No prefix keyword for inner UUID of kind {}", toString(target.kind));
s.ostr << " " << (s.hilite ? hilite_keyword : "") << toStringView(*keyword)
<< (s.hilite ? hilite_none : "") << " " << quoteString(toString(target.inner_uuid));
}
if (target.inner_engine)
{
auto keyword = getKeywordForInnerStorage(target.kind);
if (!keyword)
throw Exception(ErrorCodes::LOGICAL_ERROR, "No prefix keyword for table engine of kind {}", toString(target.kind));
s.ostr << " " << (s.hilite ? hilite_keyword : "") << toStringView(*keyword) << (s.hilite ? hilite_none : "");
target.inner_engine->formatImpl(s, state, frame);
}
}
std::optional<Keyword> ASTViewTargets::getKeywordForTableID(ViewTarget::Kind kind)
{
switch (kind)
{
case ViewTarget::To: return Keyword::TO; /// TO mydb.mydata
case ViewTarget::Inner: return std::nullopt;
}
UNREACHABLE();
}
std::optional<Keyword> ASTViewTargets::getKeywordForInnerStorage(ViewTarget::Kind kind)
{
switch (kind)
{
case ViewTarget::To: return std::nullopt; /// ENGINE = MergeTree()
case ViewTarget::Inner: return Keyword::INNER; /// INNER ENGINE = MergeTree()
}
UNREACHABLE();
}
std::optional<Keyword> ASTViewTargets::getKeywordForInnerUUID(ViewTarget::Kind kind)
{
switch (kind)
{
case ViewTarget::To: return Keyword::TO_INNER_UUID; /// TO INNER UUID 'XXX'
case ViewTarget::Inner: return std::nullopt;
}
UNREACHABLE();
}
void ASTViewTargets::forEachPointerToChild(std::function<void(void**)> f)
{
for (auto & target : targets)
{
if (target.inner_engine)
{
ASTStorage * new_inner_engine = target.inner_engine.get();
f(reinterpret_cast<void **>(&new_inner_engine));
if (new_inner_engine != target.inner_engine.get())
{
if (new_inner_engine)
target.inner_engine = typeid_cast<std::shared_ptr<ASTStorage>>(new_inner_engine->ptr());
else
target.inner_engine.reset();
}
}
}
}
}

View File

@ -0,0 +1,115 @@
#pragma once
#include <Parsers/IAST.h>
#include <Interpreters/StorageID.h>
namespace DB
{
class ASTStorage;
enum class Keyword : size_t;
/// Information about target tables (external or inner) of a materialized view or a window view.
/// See ASTViewTargets for more details.
struct ViewTarget
{
enum Kind
{
/// If `kind == ViewTarget::To` then `ViewTarget` contains information about the "TO" table of a materialized view or a window view:
/// CREATE MATERIALIZED VIEW db.mv_name {TO [db.]to_target | ENGINE to_engine} AS SELECT ...
/// or
/// CREATE WINDOW VIEW db.wv_name {TO [db.]to_target | ENGINE to_engine} AS SELECT ...
To,
/// If `kind == ViewTarget::Inner` then `ViewTarget` contains information about the "INNER" table of a window view:
/// CREATE WINDOW VIEW db.wv_name {INNER ENGINE inner_engine} AS SELECT ...
Inner,
};
Kind kind = To;
/// StorageID of the target table, if it's not inner.
/// That storage ID can be seen for example after "TO" in a statement like CREATE MATERIALIZED VIEW ... TO ...
StorageID table_id = StorageID::createEmpty();
/// UUID of the target table, if it's inner.
/// The UUID is calculated automatically and can be seen for example after "TO INNER UUID" in a statement like
/// CREATE MATERIALIZED VIEW ... TO INNER UUID ...
UUID inner_uuid = UUIDHelpers::Nil;
/// Table engine of the target table, if it's inner.
/// That engine can be seen for example after "ENGINE" in a statement like CREATE MATERIALIZED VIEW ... ENGINE ...
std::shared_ptr<ASTStorage> inner_engine;
};
/// Converts ViewTarget::Kind to a string.
std::string_view toString(ViewTarget::Kind kind);
void parseFromString(ViewTarget::Kind & out, std::string_view str);
/// Information about all target tables (external or inner) of a view.
///
/// For example, for a materialized view:
/// CREATE MATERIALIZED VIEW db.mv_name [TO [db.]to_target | ENGINE to_engine] AS SELECT ...
/// this class contains information about the "TO" table: its name and database (if it's external), its UUID and engine (if it's inner).
///
/// For a window view:
/// CREATE WINDOW VIEW db.wv_name [TO [db.]to_target | ENGINE to_engine] [INNER ENGINE inner_engine] AS SELECT ...
/// this class contains information about both the "TO" table and the "INNER" table.
class ASTViewTargets : public IAST
{
public:
std::vector<ViewTarget> targets;
/// Sets the StorageID of the target table, if it's not inner.
/// That storage ID can be seen for example after "TO" in a statement like CREATE MATERIALIZED VIEW ... TO ...
void setTableID(ViewTarget::Kind kind, const StorageID & table_id_);
StorageID getTableID(ViewTarget::Kind kind) const;
bool hasTableID(ViewTarget::Kind kind) const;
/// Replaces an empty database in the StorageID of the target table with a specified database.
void setCurrentDatabase(const String & current_database);
/// Sets the UUID of the target table, if it's inner.
/// The UUID is calculated automatically and can be seen for example after "TO INNER UUID" in a statement like
/// CREATE MATERIALIZED VIEW ... TO INNER UUID ...
void setInnerUUID(ViewTarget::Kind kind, const UUID & inner_uuid_);
UUID getInnerUUID(ViewTarget::Kind kind) const;
bool hasInnerUUID(ViewTarget::Kind kind) const;
void resetInnerUUIDs();
bool hasInnerUUIDs() const;
/// Sets the table engine of the target table, if it's inner.
/// That engine can be seen for example after "ENGINE" in a statement like CREATE MATERIALIZED VIEW ... ENGINE ...
void setInnerEngine(ViewTarget::Kind kind, ASTPtr storage_def);
std::shared_ptr<ASTStorage> getInnerEngine(ViewTarget::Kind kind) const;
std::vector<std::shared_ptr<ASTStorage>> getInnerEngines() const;
/// Returns a list of all kinds of views in this ASTViewTargets.
std::vector<ViewTarget::Kind> getKinds() const;
/// Returns information about a target table.
/// The function returns null if such target doesn't exist.
const ViewTarget * tryGetTarget(ViewTarget::Kind kind) const;
String getID(char) const override { return "ViewTargets"; }
ASTPtr clone() const override;
void formatImpl(const FormatSettings & s, FormatState & state, FormatStateStacked frame) const override;
/// Formats information only about a specific target table.
void formatTarget(ViewTarget::Kind kind, const FormatSettings & s, FormatState & state, FormatStateStacked frame) const;
static void formatTarget(const ViewTarget & target, const FormatSettings & s, FormatState & state, FormatStateStacked frame);
/// Helper functions for class ParserViewTargets. Returns a prefix keyword matching a specified target kind.
static std::optional<Keyword> getKeywordForTableID(ViewTarget::Kind kind);
static std::optional<Keyword> getKeywordForInnerUUID(ViewTarget::Kind kind);
static std::optional<Keyword> getKeywordForInnerStorage(ViewTarget::Kind kind);
protected:
void forEachPointerToChild(std::function<void(void**)> f) override;
};
}

View File

@ -0,0 +1,168 @@
#include <Parsers/CreateQueryUUIDs.h>
#include <Parsers/ASTCreateQuery.h>
#include <Parsers/ASTFunction.h>
#include <IO/ReadBufferFromString.h>
#include <IO/ReadHelpers.h>
namespace DB
{
CreateQueryUUIDs::CreateQueryUUIDs(const ASTCreateQuery & query, bool generate_random, bool force_random)
{
if (!generate_random || !force_random)
{
uuid = query.uuid;
if (query.targets)
{
for (const auto & target : query.targets->targets)
setTargetInnerUUID(target.kind, target.inner_uuid);
}
}
if (generate_random)
{
if (uuid == UUIDHelpers::Nil)
uuid = UUIDHelpers::generateV4();
/// For an ATTACH query we should never generate UUIDs for its inner target tables
/// because for an ATTACH query those inner target tables probably already exist and can be accessible by names.
/// If we generate random UUIDs for already existing tables then those UUIDs will not be correct making those inner target table inaccessible.
/// Thus it's not safe for example to replace
/// "ATTACH MATERIALIZED VIEW mv AS SELECT a FROM b" with
/// "ATTACH MATERIALIZED VIEW mv TO INNER UUID "XXXX" AS SELECT a FROM b"
/// This replacement is safe only for CREATE queries when inner target tables don't exist yet.
if (!query.attach)
{
auto generate_target_uuid = [&](ViewTarget::Kind target_kind)
{
if ((query.getTargetInnerUUID(target_kind) == UUIDHelpers::Nil) && query.getTargetTableID(target_kind).empty())
setTargetInnerUUID(target_kind, UUIDHelpers::generateV4());
};
/// If destination table (to_table_id) is not specified for materialized view,
/// then MV will create inner table. We should generate UUID of inner table here.
if (query.is_materialized_view)
generate_target_uuid(ViewTarget::To);
}
}
}
bool CreateQueryUUIDs::empty() const
{
if (uuid != UUIDHelpers::Nil)
return false;
for (const auto & [_, inner_uuid] : targets_inner_uuids)
{
if (inner_uuid != UUIDHelpers::Nil)
return false;
}
return true;
}
String CreateQueryUUIDs::toString() const
{
WriteBufferFromOwnString out;
out << "{";
bool need_comma = false;
auto add_name_and_uuid_to_string = [&](std::string_view name_, const UUID & uuid_)
{
if (std::exchange(need_comma, true))
out << ", ";
out << "\"" << name_ << "\": \"" << uuid_ << "\"";
};
if (uuid != UUIDHelpers::Nil)
add_name_and_uuid_to_string("uuid", uuid);
for (const auto & [kind, inner_uuid] : targets_inner_uuids)
{
if (inner_uuid != UUIDHelpers::Nil)
add_name_and_uuid_to_string(::DB::toString(kind), inner_uuid);
}
out << "}";
return out.str();
}
CreateQueryUUIDs CreateQueryUUIDs::fromString(const String & str)
{
ReadBufferFromString in{str};
CreateQueryUUIDs res;
skipWhitespaceIfAny(in);
in >> "{";
skipWhitespaceIfAny(in);
char c;
while (in.peek(c) && c != '}')
{
String name;
String value;
readDoubleQuotedString(name, in);
skipWhitespaceIfAny(in);
in >> ":";
skipWhitespaceIfAny(in);
readDoubleQuotedString(value, in);
skipWhitespaceIfAny(in);
if (name == "uuid")
{
res.uuid = parse<UUID>(value);
}
else
{
ViewTarget::Kind kind;
parseFromString(kind, name);
res.setTargetInnerUUID(kind, parse<UUID>(value));
}
if (in.peek(c) && c == ',')
{
in.ignore(1);
skipWhitespaceIfAny(in);
}
}
in >> "}";
return res;
}
void CreateQueryUUIDs::setTargetInnerUUID(ViewTarget::Kind kind, const UUID & new_inner_uuid)
{
for (auto & pair : targets_inner_uuids)
{
if (pair.first == kind)
{
pair.second = new_inner_uuid;
return;
}
}
if (new_inner_uuid != UUIDHelpers::Nil)
targets_inner_uuids.emplace_back(kind, new_inner_uuid);
}
UUID CreateQueryUUIDs::getTargetInnerUUID(ViewTarget::Kind kind) const
{
for (const auto & pair : targets_inner_uuids)
{
if (pair.first == kind)
return pair.second;
}
return UUIDHelpers::Nil;
}
void CreateQueryUUIDs::copyToQuery(ASTCreateQuery & query) const
{
query.uuid = uuid;
if (query.targets)
query.targets->resetInnerUUIDs();
if (!targets_inner_uuids.empty())
{
if (!query.targets)
query.set(query.targets, std::make_shared<ASTViewTargets>());
for (const auto & [kind, inner_uuid] : targets_inner_uuids)
{
if (inner_uuid != UUIDHelpers::Nil)
query.targets->setInnerUUID(kind, inner_uuid);
}
}
}
}

View File

@ -0,0 +1,40 @@
#pragma once
#include <Parsers/ASTViewTargets.h>
namespace DB
{
class ASTCreateQuery;
/// The UUID of a table or a database defined with a CREATE QUERY along with the UUIDs of its inner targets.
struct CreateQueryUUIDs
{
CreateQueryUUIDs() = default;
/// Collect UUIDs from ASTCreateQuery.
/// Parameters:
/// `generate_random` - if it's true then unspecified in the query UUIDs will be generated randomly;
/// `force_random` - if it's true then all UUIDs (even specified in the query) will be (re)generated randomly.
explicit CreateQueryUUIDs(const ASTCreateQuery & query, bool generate_random = false, bool force_random = false);
bool empty() const;
explicit operator bool() const { return !empty(); }
String toString() const;
static CreateQueryUUIDs fromString(const String & str);
void setTargetInnerUUID(ViewTarget::Kind kind, const UUID & new_inner_uuid);
UUID getTargetInnerUUID(ViewTarget::Kind kind) const;
/// Copies UUIDs to ASTCreateQuery.
void copyToQuery(ASTCreateQuery & query) const;
/// UUID of the table.
UUID uuid = UUIDHelpers::Nil;
/// UUIDs of its target table (or tables).
std::vector<std::pair<ViewTarget::Kind, UUID>> targets_inner_uuids;
};
}

View File

@ -22,6 +22,7 @@
#include <Parsers/ParserSelectWithUnionQuery.h>
#include <Parsers/ParserSetQuery.h>
#include <Parsers/ParserRefreshStrategy.h>
#include <Parsers/ParserViewTargets.h>
#include <Common/typeid_cast.h>
#include <Parsers/ASTColumnDeclaration.h>
@ -693,7 +694,8 @@ bool ParserCreateTableQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
ASTPtr table;
ASTPtr columns_list;
ASTPtr storage;
std::shared_ptr<ASTStorage> storage;
ASTPtr targets;
ASTPtr as_database;
ASTPtr as_table;
ASTPtr as_table_function;
@ -773,6 +775,17 @@ bool ParserCreateTableQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
return true;
}
auto parse_storage = [&]
{
chassert(!storage);
ASTPtr ast;
if (!storage_p.parse(pos, ast, expected))
return false;
storage = typeid_cast<std::shared_ptr<ASTStorage>>(ast);
return true;
};
auto need_parse_as_select = [&is_create_empty, &pos, &expected]()
{
if (ParserKeyword{Keyword::EMPTY_AS}.ignore(pos, expected))
@ -798,7 +811,7 @@ bool ParserCreateTableQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
if (!s_rparen.ignore(pos, expected))
return false;
auto storage_parse_result = storage_p.parse(pos, storage, expected);
auto storage_parse_result = parse_storage();
if ((storage_parse_result || is_temporary) && need_parse_as_select())
{
@ -820,7 +833,7 @@ bool ParserCreateTableQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
*/
else
{
storage_p.parse(pos, storage, expected);
parse_storage();
/// CREATE|ATTACH TABLE ... AS ...
if (need_parse_as_select())
@ -843,7 +856,7 @@ bool ParserCreateTableQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
/// Optional - ENGINE can be specified.
if (!storage)
storage_p.parse(pos, storage, expected);
parse_storage();
}
}
}
@ -904,6 +917,7 @@ bool ParserCreateTableQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
tryGetIdentifierNameInto(as_database, query->as_database);
tryGetIdentifierNameInto(as_table, query->as_table);
query->set(query->select, select);
query->set(query->targets, targets);
query->is_create_empty = is_create_empty;
if (from_path)
@ -977,6 +991,13 @@ bool ParserCreateLiveViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & e
return false;
}
std::shared_ptr<ASTViewTargets> targets;
if (to_table)
{
targets = std::make_shared<ASTViewTargets>();
targets->setTableID(ViewTarget::To, to_table->as<ASTTableIdentifier>()->getTableId());
}
/// Optional - a list of columns can be specified. It must fully comply with SELECT.
if (s_lparen.ignore(pos, expected))
{
@ -1017,14 +1038,12 @@ bool ParserCreateLiveViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & e
if (query->table)
query->children.push_back(query->table);
if (to_table)
query->to_table_id = to_table->as<ASTTableIdentifier>()->getTableId();
query->set(query->columns_list, columns_list);
tryGetIdentifierNameInto(as_database, query->as_database);
tryGetIdentifierNameInto(as_table, query->as_table);
query->set(query->select, select);
query->set(query->targets, targets);
if (comment)
query->set(query->comment, comment);
@ -1139,6 +1158,18 @@ bool ParserCreateWindowViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
storage_p.parse(pos, storage, expected);
}
std::shared_ptr<ASTViewTargets> targets;
if (to_table || storage || inner_storage)
{
targets = std::make_shared<ASTViewTargets>();
if (to_table)
targets->setTableID(ViewTarget::To, to_table->as<ASTTableIdentifier>()->getTableId());
if (storage)
targets->setInnerEngine(ViewTarget::To, storage);
if (inner_storage)
targets->setInnerEngine(ViewTarget::Inner, inner_storage);
}
// WATERMARK
if (s_watermark.ignore(pos, expected))
{
@ -1195,12 +1226,8 @@ bool ParserCreateWindowViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
if (query->table)
query->children.push_back(query->table);
if (to_table)
query->to_table_id = to_table->as<ASTTableIdentifier>()->getTableId();
query->set(query->columns_list, columns_list);
query->set(query->storage, storage);
query->set(query->inner_storage, inner_storage);
query->is_watermark_strictly_ascending = is_watermark_strictly_ascending;
query->is_watermark_ascending = is_watermark_ascending;
query->is_watermark_bounded = is_watermark_bounded;
@ -1213,6 +1240,7 @@ bool ParserCreateWindowViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected &
tryGetIdentifierNameInto(as_database, query->as_database);
tryGetIdentifierNameInto(as_table, query->as_table);
query->set(query->select, select);
query->set(query->targets, targets);
return true;
}
@ -1436,6 +1464,7 @@ bool ParserCreateDatabaseQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & e
return true;
}
bool ParserCreateViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
ParserKeyword s_create(Keyword::CREATE);
@ -1622,13 +1651,8 @@ bool ParserCreateViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (query->table)
query->children.push_back(query->table);
if (to_table)
query->to_table_id = to_table->as<ASTTableIdentifier>()->getTableId();
if (to_inner_uuid)
query->to_inner_uuid = parseFromString<UUID>(to_inner_uuid->as<ASTLiteral>()->value.get<String>());
query->set(query->columns_list, columns_list);
query->set(query->storage, storage);
if (refresh_strategy)
query->set(query->refresh_strategy, refresh_strategy);
if (comment)
@ -1639,29 +1663,41 @@ bool ParserCreateViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (query->columns_list && query->columns_list->primary_key)
{
/// If engine is not set will use default one
if (!query->storage)
query->set(query->storage, std::make_shared<ASTStorage>());
else if (query->storage->primary_key)
if (!storage)
storage = std::make_shared<ASTStorage>();
auto & storage_ref = typeid_cast<ASTStorage &>(*storage);
if (storage_ref.primary_key)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Multiple primary keys are not allowed.");
query->storage->primary_key = query->columns_list->primary_key;
storage_ref.primary_key = query->columns_list->primary_key;
}
if (query->columns_list && (query->columns_list->primary_key_from_columns))
{
/// If engine is not set will use default one
if (!query->storage)
query->set(query->storage, std::make_shared<ASTStorage>());
else if (query->storage->primary_key)
if (!storage)
storage = std::make_shared<ASTStorage>();
auto & storage_ref = typeid_cast<ASTStorage &>(*storage);
if (storage_ref.primary_key)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Multiple primary keys are not allowed.");
storage_ref.primary_key = query->columns_list->primary_key_from_columns;
}
query->storage->primary_key = query->columns_list->primary_key_from_columns;
std::shared_ptr<ASTViewTargets> targets;
if (to_table || to_inner_uuid || storage)
{
targets = std::make_shared<ASTViewTargets>();
if (to_table)
targets->setTableID(ViewTarget::To, to_table->as<ASTTableIdentifier>()->getTableId());
if (to_inner_uuid)
targets->setInnerUUID(ViewTarget::To, parseFromString<UUID>(to_inner_uuid->as<ASTLiteral>()->value.safeGet<String>()));
if (storage)
targets->setInnerEngine(ViewTarget::To, storage);
}
tryGetIdentifierNameInto(as_database, query->as_database);
tryGetIdentifierNameInto(as_table, query->as_table);
query->set(query->select, select);
query->set(query->targets, targets);
return true;
}

View File

@ -0,0 +1,88 @@
#include <Parsers/ParserViewTargets.h>
#include <Parsers/ASTIdentifier.h>
#include <Parsers/ASTViewTargets.h>
#include <Parsers/ExpressionElementParsers.h>
#include <Parsers/ParserCreateQuery.h>
#include <IO/ReadHelpers.h>
namespace DB
{
ParserViewTargets::ParserViewTargets()
{
for (auto kind : magic_enum::enum_values<ViewTarget::Kind>())
accept_kinds.push_back(kind);
}
bool ParserViewTargets::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
ParserStringLiteral literal_p;
ParserStorage storage_p{ParserStorage::TABLE_ENGINE};
ParserCompoundIdentifier table_name_p(/*table_name_with_optional_uuid*/ true, /*allow_query_parameter*/ true);
std::shared_ptr<ASTViewTargets> res;
auto result = [&] -> ASTViewTargets &
{
if (!res)
res = std::make_shared<ASTViewTargets>();
return *res;
};
for (;;)
{
auto start = pos;
for (auto kind : accept_kinds)
{
auto current = pos;
auto keyword = ASTViewTargets::getKeywordForInnerUUID(kind);
if (keyword && ParserKeyword{*keyword}.ignore(pos, expected))
{
ASTPtr ast;
if (literal_p.parse(pos, ast, expected))
{
result().setInnerUUID(kind, parseFromString<UUID>(ast->as<ASTLiteral>()->value.safeGet<String>()));
break;
}
}
pos = current;
keyword = ASTViewTargets::getKeywordForInnerStorage(kind);
if (keyword && ParserKeyword{*keyword}.ignore(pos, expected))
{
ASTPtr ast;
if (storage_p.parse(pos, ast, expected))
{
result().setInnerEngine(kind, ast);
break;
}
}
pos = current;
keyword = ASTViewTargets::getKeywordForTableID(kind);
if (keyword && ParserKeyword{*keyword}.ignore(pos, expected))
{
ASTPtr ast;
if (table_name_p.parse(pos, ast, expected))
{
result().setTableID(kind, ast->as<ASTTableIdentifier>()->getTableId());
break;
}
}
pos = current;
}
if (pos == start)
break;
}
if (!res || res->targets.empty())
return false;
node = res;
return true;
}
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <Parsers/IParserBase.h>
#include <Parsers/ASTViewTargets.h>
namespace DB
{
/// Parses information about target tables (external or inner) of a materialized view or a window view.
/// The function parses one or multiple parts of a CREATE query looking like this:
/// TO db.table_name
/// TO INNER UUID 'XXX'
/// {ENGINE / INNER ENGINE} TableEngine(arguments) [ORDER BY ...] [SETTINGS ...]
/// Returns ASTViewTargets if succeeded.
class ParserViewTargets : public IParserBase
{
public:
ParserViewTargets();
explicit ParserViewTargets(const std::vector<ViewTarget::Kind> & accept_kinds_) : accept_kinds(accept_kinds_) { }
protected:
const char * getName() const override { return "ViewTargets"; }
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
std::vector<ViewTarget::Kind> accept_kinds;
};
}

View File

@ -93,11 +93,6 @@ StorageMaterializedView::StorageMaterializedView(
{
StorageInMemoryMetadata storage_metadata;
storage_metadata.setColumns(columns_);
auto * storage_def = query.storage;
if (storage_def && storage_def->primary_key)
storage_metadata.primary_key = KeyDescription::getKeyFromAST(storage_def->primary_key->ptr(),
storage_metadata.columns,
local_context->getGlobalContext());
if (query.sql_security)
storage_metadata.setSQLSecurity(query.sql_security->as<ASTSQLSecurity &>());
@ -110,12 +105,21 @@ StorageMaterializedView::StorageMaterializedView(
throw Exception(ErrorCodes::INCORRECT_QUERY, "SELECT query is not specified for {}", getName());
/// If the destination table is not set, use inner table
has_inner_table = query.to_table_id.empty();
if (has_inner_table && !query.storage)
auto to_table_id = query.getTargetTableID(ViewTarget::To);
has_inner_table = to_table_id.empty();
auto to_inner_uuid = query.getTargetInnerUUID(ViewTarget::To);
auto to_table_engine = query.getTargetInnerEngine(ViewTarget::To);
if (has_inner_table && !to_table_engine)
throw Exception(ErrorCodes::INCORRECT_QUERY,
"You must specify where to save results of a MaterializedView query: "
"either ENGINE or an existing table in a TO clause");
if (to_table_engine && to_table_engine->primary_key)
storage_metadata.primary_key = KeyDescription::getKeyFromAST(to_table_engine->primary_key->ptr(),
storage_metadata.columns,
local_context->getGlobalContext());
auto select = SelectQueryDescription::getSelectQueryFromASTForMatView(query.select->clone(), query.refresh_strategy != nullptr, local_context);
if (select.select_table_id)
{
@ -135,25 +139,25 @@ StorageMaterializedView::StorageMaterializedView(
setInMemoryMetadata(storage_metadata);
bool point_to_itself_by_uuid = has_inner_table && query.to_inner_uuid != UUIDHelpers::Nil
&& query.to_inner_uuid == table_id_.uuid;
bool point_to_itself_by_name = !has_inner_table && query.to_table_id.database_name == table_id_.database_name
&& query.to_table_id.table_name == table_id_.table_name;
bool point_to_itself_by_uuid = has_inner_table && to_inner_uuid != UUIDHelpers::Nil
&& to_inner_uuid == table_id_.uuid;
bool point_to_itself_by_name = !has_inner_table && to_table_id.database_name == table_id_.database_name
&& to_table_id.table_name == table_id_.table_name;
if (point_to_itself_by_uuid || point_to_itself_by_name)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Materialized view {} cannot point to itself", table_id_.getFullTableName());
if (!has_inner_table)
{
target_table_id = query.to_table_id;
target_table_id = to_table_id;
}
else if (LoadingStrictnessLevel::ATTACH <= mode)
{
/// If there is an ATTACH request, then the internal table must already be created.
target_table_id = StorageID(getStorageID().database_name, generateInnerTableName(getStorageID()), query.to_inner_uuid);
target_table_id = StorageID(getStorageID().database_name, generateInnerTableName(getStorageID()), to_inner_uuid);
}
else
{
const String & engine = query.storage->engine->name;
const String & engine = to_table_engine->engine->name;
const auto & storage_features = StorageFactory::instance().getStorageFeatures(engine);
/// We will create a query to create an internal table.
@ -161,8 +165,8 @@ StorageMaterializedView::StorageMaterializedView(
auto manual_create_query = std::make_shared<ASTCreateQuery>();
manual_create_query->setDatabase(getStorageID().database_name);
manual_create_query->setTable(generateInnerTableName(getStorageID()));
manual_create_query->uuid = query.to_inner_uuid;
manual_create_query->has_uuid = query.to_inner_uuid != UUIDHelpers::Nil;
manual_create_query->uuid = to_inner_uuid;
manual_create_query->has_uuid = to_inner_uuid != UUIDHelpers::Nil;
auto new_columns_list = std::make_shared<ASTColumns>();
new_columns_list->set(new_columns_list->columns, query.columns_list->columns->ptr());
@ -184,7 +188,9 @@ StorageMaterializedView::StorageMaterializedView(
}
manual_create_query->set(manual_create_query->columns_list, new_columns_list);
manual_create_query->set(manual_create_query->storage, query.storage->ptr());
if (to_table_engine)
manual_create_query->set(manual_create_query->storage, to_table_engine);
InterpreterCreateQuery create_interpreter(manual_create_query, create_context);
create_interpreter.setInternal(true);

View File

@ -484,7 +484,8 @@ protected:
if (ast_create && !context->getSettingsRef().show_table_uuid_in_table_create_query_if_not_nil)
{
ast_create->uuid = UUIDHelpers::Nil;
ast_create->to_inner_uuid = UUIDHelpers::Nil;
if (ast_create->targets)
ast_create->targets->resetInnerUUIDs();
}
if (columns_mask[src_index++])

View File

@ -1209,8 +1209,11 @@ StorageWindowView::StorageWindowView(
setInMemoryMetadata(storage_metadata);
/// If the target table is not set, use inner target table
has_inner_target_table = query.to_table_id.empty();
if (has_inner_target_table && !query.storage)
auto to_table_id = query.getTargetTableID(ViewTarget::To);
has_inner_target_table = to_table_id.empty();
auto to_table_engine = query.getTargetInnerEngine(ViewTarget::To);
if (has_inner_target_table && !to_table_engine)
throw Exception(ErrorCodes::INCORRECT_QUERY,
"You must specify where to save results of a WindowView query: "
"either ENGINE or an existing table in a TO clause");
@ -1225,12 +1228,12 @@ StorageWindowView::StorageWindowView(
auto inner_query = initInnerQuery(query.select->list_of_selects->children.at(0)->as<ASTSelectQuery &>(), context_);
if (query.inner_storage)
inner_table_engine = query.inner_storage->clone();
if (auto inner_storage = query.getTargetInnerEngine(ViewTarget::Inner))
inner_table_engine = inner_storage->clone();
inner_table_id = StorageID(getStorageID().database_name, generateInnerTableName(getStorageID()));
inner_fetch_query = generateInnerFetchQuery(inner_table_id);
target_table_id = has_inner_target_table ? StorageID(table_id_.database_name, generateTargetTableName(table_id_)) : query.to_table_id;
target_table_id = has_inner_target_table ? StorageID(table_id_.database_name, generateTargetTableName(table_id_)) : to_table_id;
if (is_proctime)
next_fire_signal = getWindowUpperBound(now());
@ -1255,7 +1258,7 @@ StorageWindowView::StorageWindowView(
new_columns_list->set(new_columns_list->columns, query.columns_list->columns->ptr());
target_create_query->set(target_create_query->columns_list, new_columns_list);
target_create_query->set(target_create_query->storage, query.storage->ptr());
target_create_query->set(target_create_query->storage, to_table_engine);
InterpreterCreateQuery create_interpreter_(target_create_query, create_context_);
create_interpreter_.setInternal(true);