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:
Marek Vavruša 2017-10-21 13:08:49 -07:00
parent 8dd5c9dac2
commit 975a7ada42
10 changed files with 98 additions and 37 deletions

View File

@ -22,8 +22,6 @@ String getTableDefinitionFromCreateQuery(const ASTPtr & query)
/// We remove everything that is not needed for ATTACH from the query. /// We remove everything that is not needed for ATTACH from the query.
create.attach = true; create.attach = true;
create.database.clear(); create.database.clear();
create.as_database.clear();
create.as_table.clear();
create.if_not_exists = false; create.if_not_exists = false;
create.is_populate = false; create.is_populate = false;
@ -33,6 +31,13 @@ String getTableDefinitionFromCreateQuery(const ASTPtr & query)
if (engine != "View" && engine != "MaterializedView") if (engine != "View" && engine != "MaterializedView")
create.select = nullptr; 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; std::ostringstream statement_stream;
formatAST(create, statement_stream, 0, false); formatAST(create, statement_stream, 0, false);
statement_stream << '\n'; statement_stream << '\n';
@ -58,6 +63,8 @@ std::pair<String, StoragePtr> createTableFromDefinition(
/// We do not directly use `InterpreterCreateQuery::execute`, because /// We do not directly use `InterpreterCreateQuery::execute`, because
/// - the database has not been created yet; /// - the database has not been created yet;
/// - the code is simpler, since the query is already brought to a suitable form. /// - 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); 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) else if (ast_create_query.is_materialized_view)
storage_name = "MaterializedView"; storage_name = "MaterializedView";
else else
{
if (!ast_create_query.storage)
throw Exception("Missing ENGINE definition");
storage_name = typeid_cast<ASTFunction &>(*ast_create_query.storage).name; storage_name = typeid_cast<ASTFunction &>(*ast_create_query.storage).name;
}
return return
{ {

View File

@ -438,6 +438,12 @@ String InterpreterCreateQuery::setEngine(
{ {
storage_name = typeid_cast<ASTFunction &>(*create.storage).name; 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()) 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. /// 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 else
storage_name = as_storage->getName(); 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 else
throw Exception("Incorrect CREATE query: required ENGINE.", ErrorCodes::ENGINE_REQUIRED); throw Exception("Incorrect CREATE query: required ENGINE.", ErrorCodes::ENGINE_REQUIRED);

View File

@ -105,7 +105,8 @@ protected:
if (!as_table.empty()) 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); << (!as_database.empty() ? backQuoteIfNeed(as_database) + "." : "") << backQuoteIfNeed(as_table);
} }

View File

@ -162,6 +162,7 @@ bool ParserCreateQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
bool is_materialized_view = false; bool is_materialized_view = false;
bool is_populate = false; bool is_populate = false;
bool is_temporary = false; bool is_temporary = false;
bool to_table = false;
if (!s_create.ignore(pos, expected)) if (!s_create.ignore(pos, expected))
{ {
@ -304,6 +305,22 @@ bool ParserCreateQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
return false; 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. /// Optional - a list of columns can be specified. It must fully comply with SELECT.
if (s_lparen.ignore(pos, expected)) 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 /// 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)) if (s_populate.ignore(pos, expected))
is_populate = true; is_populate = true;

View File

@ -214,7 +214,7 @@ protected:
* CREATE|ATTACH DATABASE db [ENGINE = engine] * CREATE|ATTACH DATABASE db [ENGINE = engine]
* *
* Or: * 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 class ParserCreateQuery : public IParserBase
{ {

View File

@ -71,7 +71,7 @@ StorageMaterializedView::StorageMaterializedView(
if (!create.select) if (!create.select)
throw Exception("SELECT query is not specified for " + getName(), ErrorCodes::INCORRECT_QUERY); 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); throw Exception("ENGINE of MaterializedView should be specified explicitly", ErrorCodes::INCORRECT_QUERY);
ASTSelectQuery & select = typeid_cast<ASTSelectQuery &>(*create.select); ASTSelectQuery & select = typeid_cast<ASTSelectQuery &>(*create.select);
@ -86,16 +86,28 @@ StorageMaterializedView::StorageMaterializedView(
DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(select_database_name, select_table_name),
DatabaseAndTableName(database_name, 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; inner_query = create.select;
/// If there is an ATTACH request, then the internal table must already be connected. /// 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. /// We will create a query to create an internal table.
auto manual_create_query = std::make_shared<ASTCreateQuery>(); auto manual_create_query = std::make_shared<ASTCreateQuery>();
manual_create_query->database = database_name; manual_create_query->database = target_database_name;
manual_create_query->table = inner_table_name; manual_create_query->table = target_table_name;
manual_create_query->columns = create.columns; manual_create_query->columns = create.columns;
manual_create_query->children.push_back(manual_create_query->columns); manual_create_query->children.push_back(manual_create_query->columns);
manual_create_query->storage = create.inner_storage; manual_create_query->storage = create.inner_storage;
@ -122,12 +134,12 @@ StorageMaterializedView::StorageMaterializedView(
NameAndTypePair StorageMaterializedView::getColumn(const String & column_name) const 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 bool StorageMaterializedView::hasColumn(const String & column_name) const
{ {
return getInnerTable()->hasColumn(column_name); return getTargetTable()->hasColumn(column_name);
} }
BlockInputStreams StorageMaterializedView::read( BlockInputStreams StorageMaterializedView::read(
@ -138,12 +150,12 @@ BlockInputStreams StorageMaterializedView::read(
const size_t max_block_size, const size_t max_block_size,
const unsigned num_streams) 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) BlockOutputStreamPtr StorageMaterializedView::write(const ASTPtr & query, const Settings & settings)
{ {
return getInnerTable()->write(query, settings); return getTargetTable()->write(query, settings);
} }
void StorageMaterializedView::drop() void StorageMaterializedView::drop()
@ -152,14 +164,12 @@ void StorageMaterializedView::drop()
DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(select_database_name, select_table_name),
DatabaseAndTableName(database_name, table_name)); DatabaseAndTableName(database_name, table_name));
auto inner_table_name = getInnerTableName(); if (has_inner_table && context.tryGetTable(target_database_name, target_table_name))
if (context.tryGetTable(database_name, inner_table_name))
{ {
/// We create and execute `drop` query for internal table. /// We create and execute `drop` query for internal table.
auto drop_query = std::make_shared<ASTDropQuery>(); auto drop_query = std::make_shared<ASTDropQuery>();
drop_query->database = database_name; drop_query->database = target_database_name;
drop_query->table = inner_table_name; drop_query->table = target_table_name;
ASTPtr ast_drop_query = drop_query; ASTPtr ast_drop_query = drop_query;
InterpreterDropQuery drop_interpreter(ast_drop_query, context); InterpreterDropQuery drop_interpreter(ast_drop_query, context);
drop_interpreter.execute(); 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) 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);
} }

View File

@ -20,18 +20,17 @@ public:
std::string getName() const override { return "MaterializedView"; } std::string getName() const override { return "MaterializedView"; }
std::string getTableName() const override { return table_name; } std::string getTableName() const override { return table_name; }
const NamesAndTypesList & getColumnsListImpl() const override { return *columns; } const NamesAndTypesList & getColumnsListImpl() const override { return *columns; }
std::string getInnerTableName() const { return ".inner." + table_name; }
ASTPtr getInnerQuery() const { return inner_query->clone(); }; ASTPtr getInnerQuery() const { return inner_query->clone(); };
StoragePtr getInnerTable() const; StoragePtr getTargetTable() const;
NameAndTypePair getColumn(const String & column_name) const override; NameAndTypePair getColumn(const String & column_name) const override;
bool hasColumn(const String & column_name) const override; bool hasColumn(const String & column_name) const override;
bool supportsSampling() const override { return getInnerTable()->supportsSampling(); } bool supportsSampling() const override { return getTargetTable()->supportsSampling(); }
bool supportsPrewhere() const override { return getInnerTable()->supportsPrewhere(); } bool supportsPrewhere() const override { return getTargetTable()->supportsPrewhere(); }
bool supportsFinal() const override { return getInnerTable()->supportsFinal(); } bool supportsFinal() const override { return getTargetTable()->supportsFinal(); }
bool supportsParallelReplicas() const override { return getInnerTable()->supportsParallelReplicas(); } bool supportsParallelReplicas() const override { return getTargetTable()->supportsParallelReplicas(); }
bool supportsIndexForIn() const override { return getInnerTable()->supportsIndexForIn(); } bool supportsIndexForIn() const override { return getTargetTable()->supportsIndexForIn(); }
BlockOutputStreamPtr write(const ASTPtr & query, const Settings & settings) override; BlockOutputStreamPtr write(const ASTPtr & query, const Settings & settings) override;
void drop() override; void drop() override;
@ -48,11 +47,14 @@ public:
private: private:
String select_database_name; String select_database_name;
String select_table_name; String select_table_name;
String target_database_name;
String target_table_name;
String table_name; String table_name;
String database_name; String database_name;
ASTPtr inner_query; ASTPtr inner_query;
Context & context; Context & context;
NamesAndTypesListPtr columns; NamesAndTypesListPtr columns;
bool has_inner_table = false;
StorageMaterializedView( StorageMaterializedView(
const String & table_name_, const String & table_name_,

View File

@ -0,0 +1,2 @@
1
2

View File

@ -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;

View File

@ -107,7 +107,7 @@ At the moment, ``ALTER`` queries for replicated tables are not supported yet.
CREATE VIEW 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. 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. 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. 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 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. 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. Views look the same as normal tables. For example, they are listed in the result of the SHOW TABLES query.