mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
StorageMaterializedView: allow CREATE MATERIALIZED VIEW x TO y
This allows creation of materialized views without inner tables, using an existing table for materialized rows instead. This is useful for cases when you want to detach the materializing view, but keep the already materialized data readable, especially when the inner table is replicated.
This commit is contained in:
parent
8dd5c9dac2
commit
975a7ada42
@ -22,8 +22,6 @@ String getTableDefinitionFromCreateQuery(const ASTPtr & query)
|
||||
/// We remove everything that is not needed for ATTACH from the query.
|
||||
create.attach = true;
|
||||
create.database.clear();
|
||||
create.as_database.clear();
|
||||
create.as_table.clear();
|
||||
create.if_not_exists = false;
|
||||
create.is_populate = false;
|
||||
|
||||
@ -33,6 +31,13 @@ String getTableDefinitionFromCreateQuery(const ASTPtr & query)
|
||||
if (engine != "View" && engine != "MaterializedView")
|
||||
create.select = nullptr;
|
||||
|
||||
/// For "MATERIALIZED VIEW x TO y" it's necessary to save destination table
|
||||
if (engine != "MaterializedView" || create.inner_storage)
|
||||
{
|
||||
create.as_database.clear();
|
||||
create.as_table.clear();
|
||||
}
|
||||
|
||||
std::ostringstream statement_stream;
|
||||
formatAST(create, statement_stream, 0, false);
|
||||
statement_stream << '\n';
|
||||
@ -58,6 +63,8 @@ std::pair<String, StoragePtr> createTableFromDefinition(
|
||||
/// We do not directly use `InterpreterCreateQuery::execute`, because
|
||||
/// - the database has not been created yet;
|
||||
/// - the code is simpler, since the query is already brought to a suitable form.
|
||||
if (!ast_create_query.columns)
|
||||
throw Exception("Missing definition of columns");
|
||||
|
||||
InterpreterCreateQuery::ColumnsInfo columns_info = InterpreterCreateQuery::getColumnsInfo(ast_create_query.columns, context);
|
||||
|
||||
@ -68,7 +75,11 @@ std::pair<String, StoragePtr> createTableFromDefinition(
|
||||
else if (ast_create_query.is_materialized_view)
|
||||
storage_name = "MaterializedView";
|
||||
else
|
||||
{
|
||||
if (!ast_create_query.storage)
|
||||
throw Exception("Missing ENGINE definition");
|
||||
storage_name = typeid_cast<ASTFunction &>(*ast_create_query.storage).name;
|
||||
}
|
||||
|
||||
return
|
||||
{
|
||||
|
@ -438,6 +438,12 @@ String InterpreterCreateQuery::setEngine(
|
||||
{
|
||||
storage_name = typeid_cast<ASTFunction &>(*create.storage).name;
|
||||
}
|
||||
else if (create.is_temporary)
|
||||
set_engine("Memory");
|
||||
else if (create.is_view)
|
||||
set_engine("View");
|
||||
else if (create.is_materialized_view)
|
||||
set_engine("MaterializedView");
|
||||
else 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.
|
||||
@ -460,12 +466,6 @@ String InterpreterCreateQuery::setEngine(
|
||||
else
|
||||
storage_name = as_storage->getName();
|
||||
}
|
||||
else if (create.is_temporary)
|
||||
set_engine("Memory");
|
||||
else if (create.is_view)
|
||||
set_engine("View");
|
||||
else if (create.is_materialized_view)
|
||||
set_engine("MaterializedView");
|
||||
else
|
||||
throw Exception("Incorrect CREATE query: required ENGINE.", ErrorCodes::ENGINE_REQUIRED);
|
||||
|
||||
|
@ -105,7 +105,8 @@ protected:
|
||||
|
||||
if (!as_table.empty())
|
||||
{
|
||||
settings.ostr << (settings.hilite ? hilite_keyword : "") << " AS " << (settings.hilite ? hilite_none : "")
|
||||
std::string what = (!is_materialized_view ? " AS " : " TO ");
|
||||
settings.ostr << (settings.hilite ? hilite_keyword : "") << what << (settings.hilite ? hilite_none : "")
|
||||
<< (!as_database.empty() ? backQuoteIfNeed(as_database) + "." : "") << backQuoteIfNeed(as_table);
|
||||
}
|
||||
|
||||
|
@ -162,6 +162,7 @@ bool ParserCreateQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
bool is_materialized_view = false;
|
||||
bool is_populate = false;
|
||||
bool is_temporary = false;
|
||||
bool to_table = false;
|
||||
|
||||
if (!s_create.ignore(pos, expected))
|
||||
{
|
||||
@ -304,6 +305,22 @@ bool ParserCreateQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
return false;
|
||||
}
|
||||
|
||||
// TO [db.]table
|
||||
if (ParserKeyword{"TO"}.ignore(pos, expected))
|
||||
{
|
||||
to_table = true;
|
||||
|
||||
if (!name_p.parse(pos, as_table, expected))
|
||||
return false;
|
||||
|
||||
if (s_dot.ignore(pos, expected))
|
||||
{
|
||||
as_database = as_table;
|
||||
if (!name_p.parse(pos, as_table, expected))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional - a list of columns can be specified. It must fully comply with SELECT.
|
||||
if (s_lparen.ignore(pos, expected))
|
||||
{
|
||||
@ -315,7 +332,8 @@ bool ParserCreateQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|
||||
}
|
||||
|
||||
/// Optional - internal ENGINE for MATERIALIZED VIEW can be specified
|
||||
engine_p.parse(pos, inner_storage, expected);
|
||||
if (!to_table)
|
||||
engine_p.parse(pos, inner_storage, expected);
|
||||
|
||||
if (s_populate.ignore(pos, expected))
|
||||
is_populate = true;
|
||||
|
@ -214,7 +214,7 @@ protected:
|
||||
* CREATE|ATTACH DATABASE db [ENGINE = engine]
|
||||
*
|
||||
* Or:
|
||||
* CREATE|ATTACH [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]name [ENGINE = engine] [POPULATE] AS SELECT ...
|
||||
* CREATE|ATTACH [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]name [TO [db.]name] [ENGINE = engine] [POPULATE] AS SELECT ...
|
||||
*/
|
||||
class ParserCreateQuery : public IParserBase
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ StorageMaterializedView::StorageMaterializedView(
|
||||
if (!create.select)
|
||||
throw Exception("SELECT query is not specified for " + getName(), ErrorCodes::INCORRECT_QUERY);
|
||||
|
||||
if (!create.inner_storage)
|
||||
if (!create.inner_storage && create.as_table.empty())
|
||||
throw Exception("ENGINE of MaterializedView should be specified explicitly", ErrorCodes::INCORRECT_QUERY);
|
||||
|
||||
ASTSelectQuery & select = typeid_cast<ASTSelectQuery &>(*create.select);
|
||||
@ -86,16 +86,28 @@ StorageMaterializedView::StorageMaterializedView(
|
||||
DatabaseAndTableName(select_database_name, select_table_name),
|
||||
DatabaseAndTableName(database_name, table_name));
|
||||
|
||||
String inner_table_name = getInnerTableName();
|
||||
// If the destination table is not set, use inner table
|
||||
if (!create.inner_storage)
|
||||
{
|
||||
target_database_name = create.as_database;
|
||||
target_table_name = create.as_table;
|
||||
}
|
||||
else
|
||||
{
|
||||
target_database_name = database_name;
|
||||
target_table_name = ".inner." + table_name;
|
||||
has_inner_table = true;
|
||||
}
|
||||
|
||||
inner_query = create.select;
|
||||
|
||||
/// If there is an ATTACH request, then the internal table must already be connected.
|
||||
if (!attach_)
|
||||
if (!attach_ && has_inner_table)
|
||||
{
|
||||
/// We will create a query to create an internal table.
|
||||
auto manual_create_query = std::make_shared<ASTCreateQuery>();
|
||||
manual_create_query->database = database_name;
|
||||
manual_create_query->table = inner_table_name;
|
||||
manual_create_query->database = target_database_name;
|
||||
manual_create_query->table = target_table_name;
|
||||
manual_create_query->columns = create.columns;
|
||||
manual_create_query->children.push_back(manual_create_query->columns);
|
||||
manual_create_query->storage = create.inner_storage;
|
||||
@ -122,12 +134,12 @@ StorageMaterializedView::StorageMaterializedView(
|
||||
|
||||
NameAndTypePair StorageMaterializedView::getColumn(const String & column_name) const
|
||||
{
|
||||
return getInnerTable()->getColumn(column_name);
|
||||
return getTargetTable()->getColumn(column_name);
|
||||
}
|
||||
|
||||
bool StorageMaterializedView::hasColumn(const String & column_name) const
|
||||
{
|
||||
return getInnerTable()->hasColumn(column_name);
|
||||
return getTargetTable()->hasColumn(column_name);
|
||||
}
|
||||
|
||||
BlockInputStreams StorageMaterializedView::read(
|
||||
@ -138,12 +150,12 @@ BlockInputStreams StorageMaterializedView::read(
|
||||
const size_t max_block_size,
|
||||
const unsigned num_streams)
|
||||
{
|
||||
return getInnerTable()->read(column_names, query_info, context, processed_stage, max_block_size, num_streams);
|
||||
return getTargetTable()->read(column_names, query_info, context, processed_stage, max_block_size, num_streams);
|
||||
}
|
||||
|
||||
BlockOutputStreamPtr StorageMaterializedView::write(const ASTPtr & query, const Settings & settings)
|
||||
{
|
||||
return getInnerTable()->write(query, settings);
|
||||
return getTargetTable()->write(query, settings);
|
||||
}
|
||||
|
||||
void StorageMaterializedView::drop()
|
||||
@ -152,14 +164,12 @@ void StorageMaterializedView::drop()
|
||||
DatabaseAndTableName(select_database_name, select_table_name),
|
||||
DatabaseAndTableName(database_name, table_name));
|
||||
|
||||
auto inner_table_name = getInnerTableName();
|
||||
|
||||
if (context.tryGetTable(database_name, inner_table_name))
|
||||
if (has_inner_table && context.tryGetTable(target_database_name, target_table_name))
|
||||
{
|
||||
/// We create and execute `drop` query for internal table.
|
||||
auto drop_query = std::make_shared<ASTDropQuery>();
|
||||
drop_query->database = database_name;
|
||||
drop_query->table = inner_table_name;
|
||||
drop_query->database = target_database_name;
|
||||
drop_query->table = target_table_name;
|
||||
ASTPtr ast_drop_query = drop_query;
|
||||
InterpreterDropQuery drop_interpreter(ast_drop_query, context);
|
||||
drop_interpreter.execute();
|
||||
@ -168,12 +178,12 @@ void StorageMaterializedView::drop()
|
||||
|
||||
bool StorageMaterializedView::optimize(const ASTPtr & query, const ASTPtr & partition, bool final, bool deduplicate, const Context & context)
|
||||
{
|
||||
return getInnerTable()->optimize(query, partition, final, deduplicate, context);
|
||||
return getTargetTable()->optimize(query, partition, final, deduplicate, context);
|
||||
}
|
||||
|
||||
StoragePtr StorageMaterializedView::getInnerTable() const
|
||||
StoragePtr StorageMaterializedView::getTargetTable() const
|
||||
{
|
||||
return context.getTable(database_name, getInnerTableName());
|
||||
return context.getTable(target_database_name, target_table_name);
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,18 +20,17 @@ public:
|
||||
std::string getName() const override { return "MaterializedView"; }
|
||||
std::string getTableName() const override { return table_name; }
|
||||
const NamesAndTypesList & getColumnsListImpl() const override { return *columns; }
|
||||
std::string getInnerTableName() const { return ".inner." + table_name; }
|
||||
ASTPtr getInnerQuery() const { return inner_query->clone(); };
|
||||
StoragePtr getInnerTable() const;
|
||||
StoragePtr getTargetTable() const;
|
||||
|
||||
NameAndTypePair getColumn(const String & column_name) const override;
|
||||
bool hasColumn(const String & column_name) const override;
|
||||
|
||||
bool supportsSampling() const override { return getInnerTable()->supportsSampling(); }
|
||||
bool supportsPrewhere() const override { return getInnerTable()->supportsPrewhere(); }
|
||||
bool supportsFinal() const override { return getInnerTable()->supportsFinal(); }
|
||||
bool supportsParallelReplicas() const override { return getInnerTable()->supportsParallelReplicas(); }
|
||||
bool supportsIndexForIn() const override { return getInnerTable()->supportsIndexForIn(); }
|
||||
bool supportsSampling() const override { return getTargetTable()->supportsSampling(); }
|
||||
bool supportsPrewhere() const override { return getTargetTable()->supportsPrewhere(); }
|
||||
bool supportsFinal() const override { return getTargetTable()->supportsFinal(); }
|
||||
bool supportsParallelReplicas() const override { return getTargetTable()->supportsParallelReplicas(); }
|
||||
bool supportsIndexForIn() const override { return getTargetTable()->supportsIndexForIn(); }
|
||||
|
||||
BlockOutputStreamPtr write(const ASTPtr & query, const Settings & settings) override;
|
||||
void drop() override;
|
||||
@ -48,11 +47,14 @@ public:
|
||||
private:
|
||||
String select_database_name;
|
||||
String select_table_name;
|
||||
String target_database_name;
|
||||
String target_table_name;
|
||||
String table_name;
|
||||
String database_name;
|
||||
ASTPtr inner_query;
|
||||
Context & context;
|
||||
NamesAndTypesListPtr columns;
|
||||
bool has_inner_table = false;
|
||||
|
||||
StorageMaterializedView(
|
||||
const String & table_name_,
|
||||
|
@ -0,0 +1,2 @@
|
||||
1
|
||||
2
|
@ -0,0 +1,16 @@
|
||||
DROP TABLE IF EXISTS test.src;
|
||||
DROP TABLE IF EXISTS test.dst;
|
||||
DROP TABLE IF EXISTS test.mv;
|
||||
|
||||
CREATE TABLE test.src (x UInt8) ENGINE = Null;
|
||||
CREATE TABLE test.dst (x UInt8) ENGINE = Memory();
|
||||
|
||||
CREATE MATERIALIZED VIEW test.mv TO test.dst AS SELECT * FROM test.src;
|
||||
INSERT INTO test.src VALUES (1), (2);
|
||||
|
||||
-- Drop the MV and see if the data is still readable
|
||||
DROP TABLE test.mv;
|
||||
SELECT * FROM test.dst;
|
||||
|
||||
DROP TABLE test.src;
|
||||
DROP TABLE test.dst;
|
@ -107,7 +107,7 @@ At the moment, ``ALTER`` queries for replicated tables are not supported yet.
|
||||
|
||||
CREATE VIEW
|
||||
~~~~~~~~~~~
|
||||
``CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]name [ENGINE = engine] [POPULATE] AS SELECT ...``
|
||||
``CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]name [TO [db.]name] [ENGINE = engine] [POPULATE] AS SELECT ...``
|
||||
|
||||
Creates a view. There are two types of views: normal and MATERIALIZED.
|
||||
|
||||
@ -133,7 +133,7 @@ This query is fully equivalent to using the subquery:
|
||||
|
||||
Materialized views store data transformed by the corresponding SELECT query.
|
||||
|
||||
When creating a materialized view, you can specify ENGINE - the table engine for storing data. By default, it uses the same engine as for the table that the SELECT query is made from.
|
||||
When creating a materialized view, you can either specify ENGINE - the table engine for storing data, or target table for materialized results. By default, it uses the same engine as for the table that the SELECT query is made from.
|
||||
|
||||
A materialized view is arranged as follows: when inserting data to the table specified in SELECT, part of the inserted data is converted by this SELECT query, and the result is inserted in the view.
|
||||
|
||||
@ -142,6 +142,7 @@ If you specify POPULATE, the existing table data is inserted in the view when cr
|
||||
The SELECT query can contain DISTINCT, GROUP BY, ORDER BY, LIMIT ... Note that the corresponding conversions are performed independently on each block of inserted data. For example, if GROUP BY is set, data is aggregated during insertion, but only within a single packet of inserted data. The data won't be further aggregated. The exception is when using an ENGINE that independently performs data aggregation, such as SummingMergeTree.
|
||||
|
||||
The execution of ALTER queries on materialized views has not been fully developed, so they might be inconvenient.
|
||||
If the materialized view uses a ``TO [db.]name`` to specify a target table, it is possible to DETACH the view, ALTER the target table, and ATTACH the view again.
|
||||
|
||||
Views look the same as normal tables. For example, they are listed in the result of the SHOW TABLES query.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user