Compare commits

...

18 Commits

Author SHA1 Message Date
Dmitry Novik
1675bd38ee
Merge 92cc90a711 into 71b651d4a8 2024-11-27 11:29:07 +08:00
Dmitry Novik
92cc90a711
Remove logs 2024-11-04 15:29:17 +01:00
Dmitry Novik
2f36feff7d Merge remote-tracking branch 'origin/master' into param-view-analyze 2024-10-28 15:20:52 +01:00
Dmitry Novik
ec22024639 Merge remote-tracking branch 'origin/master' into param-view-analyze 2024-09-26 10:35:16 +02:00
Dmitry Novik
d8c93dc70f Update test 2024-09-25 14:23:10 +02:00
Dmitry Novik
0d4226a1c4 Fix fuzzer issue 2024-09-24 18:38:59 +02:00
Dmitry Novik
08438c5659 Add test for #69598 2024-09-19 17:33:46 +02:00
Dmitry Novik
993e10dc26 Merge remote-tracking branch 'origin/master' into param-view-analyze 2024-09-19 17:29:01 +02:00
Dmitry Novik
586182a045
Fix clang-tidy build 2024-09-05 16:06:24 +02:00
Dmitry Novik
31026aa74a Add a test 2024-09-03 15:03:57 +02:00
Dmitry Novik
5c39591ad1 Add tests 2024-09-03 15:03:57 +02:00
Dmitry Novik
769589dbc9 Remove redundant code 2024-09-03 15:03:57 +02:00
Dmitry Novik
355d7bce05 Improve param view docs 2024-09-03 15:03:57 +02:00
Dmitry Novik
34ceebe992 Rollback changes in QueryTreeBuilder 2024-09-03 15:03:57 +02:00
Dmitry Novik
887b49649c Small refactoring 2024-09-03 15:03:57 +02:00
Dmitry Novik
f4c4b58e64 Rewrite DESCRIBE for parameterized view implementation 2024-09-03 15:03:57 +02:00
Dmitry Novik
79140a3589 Support DESCRIBE for parameterized view; Fix usage in scalars 2024-09-03 15:03:57 +02:00
Dmitry Novik
967ba9d3d4 Parameterized views: Analyze SELECT query 2024-09-03 15:03:57 +02:00
11 changed files with 141 additions and 28 deletions

View File

@ -41,15 +41,24 @@ SELECT a, b, c FROM (SELECT ...)
## Parameterized View
Parametrized views are similar to normal views, but can be created with parameters which are not resolved immediately. These views can be used with table functions, which specify the name of the view as function name and the parameter values as its arguments.
Parametrized views are similar to normal views, but can be created with parameters which are not resolved immediately.
These views can be used with table functions, which specify the name of the view as function name and the parameter values as its arguments.
``` sql
CREATE VIEW view AS SELECT * FROM TABLE WHERE Column1={column1:datatype1} and Column2={column2:datatype2} ...
CREATE VIEW param_view AS SELECT * FROM TABLE WHERE Column1={column1:datatype1} and Column2={column2:datatype2} ...
```
The above creates a view for table which can be used as table function by substituting parameters as shown below.
``` sql
SELECT * FROM view(column1=value1, column2=value2 ...)
SELECT * FROM param_view(column1=value1, column2=value2 ...)
```
Since the parameterized view depends on the parameter values, it doesn't have a schema when parameters are not provided.
That means there's no information about parameterized views in the `system.columns` table.
Also, `DESCRIBE` queries would work only if parameters are provided.
```sql
DESCRIBE param_view(column1=value1, column2=value2 ...)
```
## Materialized View

View File

@ -4658,20 +4658,7 @@ void QueryAnalyzer::resolveTableFunction(QueryTreeNodePtr & table_function_node,
TableFunctionPtr table_function_ptr = TableFunctionFactory::instance().tryGet(table_function_name, scope_context);
if (!table_function_ptr)
{
String database_name = scope_context->getCurrentDatabase();
String table_name;
auto function_ast = table_function_node->toAST();
Identifier table_identifier{table_function_name};
if (table_identifier.getPartsSize() == 1)
{
table_name = table_identifier[0];
}
else if (table_identifier.getPartsSize() == 2)
{
database_name = table_identifier[0];
table_name = table_identifier[1];
}
auto [database_name, table_name] = extractDatabaseAndTableNameForParametrizedView(table_function_name, scope_context);
/// Collect parametrized view arguments
NameToNameMap view_params;
@ -4713,9 +4700,9 @@ void QueryAnalyzer::resolveTableFunction(QueryTreeNodePtr & table_function_node,
if (parametrized_view_storage)
{
auto fake_table_node = std::make_shared<TableNode>(parametrized_view_storage, scope_context);
fake_table_node->setAlias(table_function_node->getAlias());
table_function_node = fake_table_node;
std::vector<size_t> skip_analysis_arguments_indexes(table_function_node_typed.getArguments().getNodes().size());
std::iota(skip_analysis_arguments_indexes.begin(), skip_analysis_arguments_indexes.end(), 0);
table_function_node_typed.resolve({}, parametrized_view_storage, scope_context, std::move(skip_analysis_arguments_indexes));
return;
}

View File

@ -73,7 +73,7 @@ public:
/// Returns true, if table function is resolved, false otherwise
bool isResolved() const
{
return storage != nullptr && table_function != nullptr;
return storage != nullptr;
}
/// Get table function, returns nullptr if table function node is not resolved

View File

@ -944,4 +944,23 @@ QueryTreeNodePtr buildSubqueryToReadColumnsFromTableExpression(const QueryTreeNo
return buildSubqueryToReadColumnsFromTableExpression(columns_to_select, table_node, context);
}
std::pair<String, String> extractDatabaseAndTableNameForParametrizedView(const String & table_function_name, const ContextPtr & context)
{
String database_name = context->getCurrentDatabase();
String table_name;
Identifier table_identifier{table_function_name};
if (table_identifier.getPartsSize() == 1)
{
table_name = table_identifier[0];
}
else if (table_identifier.getPartsSize() == 2)
{
database_name = table_identifier[0];
table_name = table_identifier[1];
}
return { database_name, table_name };
}
}

View File

@ -159,5 +159,6 @@ QueryTreeNodePtr buildSubqueryToReadColumnsFromTableExpression(const NamesAndTyp
*/
QueryTreeNodePtr buildSubqueryToReadColumnsFromTableExpression(const QueryTreeNodePtr & table_node, const ContextPtr & context);
std::pair<String, String> extractDatabaseAndTableNameForParametrizedView(const String & table_function_name, const ContextPtr & context);
}

View File

@ -3,8 +3,15 @@
#include <QueryPipeline/BlockIO.h>
#include <DataTypes/DataTypeString.h>
#include <Parsers/queryToString.h>
#include <Parsers/FunctionParameterValuesVisitor.h>
#include <Common/typeid_cast.h>
#include <Analyzer/Utils.h>
#include <Analyzer/Passes/QueryAnalysisPass.h>
#include <Analyzer/QueryTreeBuilder.h>
#include <Analyzer/TableFunctionNode.h>
#include <Analyzer/TableNode.h>
#include <Core/Settings.h>
#include <Storages/StorageView.h>
#include <TableFunctions/ITableFunction.h>
#include <TableFunctions/TableFunctionFactory.h>
#include <Interpreters/InterpreterSelectWithUnionQuery.h>
@ -34,6 +41,14 @@ namespace Setting
extern const SettingsBool print_pretty_type_names;
}
namespace ErrorCodes
{
extern const int UNSUPPORTED_METHOD;
extern const int UNKNOWN_FUNCTION;
}
InterpreterDescribeQuery::InterpreterDescribeQuery(const ASTPtr & query_ptr_, ContextPtr context_)
: WithContext(context_)
, query_ptr(query_ptr_)
@ -133,10 +148,14 @@ BlockIO InterpreterDescribeQuery::execute()
void InterpreterDescribeQuery::fillColumnsFromSubquery(const ASTTableExpression & table_expression)
{
Block sample_block;
auto select_query = table_expression.subquery->children.at(0);
auto current_context = getContext();
fillColumnsFromSubqueryImpl(select_query, current_context);
}
void InterpreterDescribeQuery::fillColumnsFromSubqueryImpl(const ASTPtr & select_query, const ContextPtr & current_context)
{
Block sample_block;
if (settings[Setting::allow_experimental_analyzer])
{
SelectQueryOptions select_query_options;
@ -154,7 +173,39 @@ void InterpreterDescribeQuery::fillColumnsFromSubquery(const ASTTableExpression
void InterpreterDescribeQuery::fillColumnsFromTableFunction(const ASTTableExpression & table_expression)
{
auto current_context = getContext();
TableFunctionPtr table_function_ptr = TableFunctionFactory::instance().get(table_expression.table_function, current_context);
auto table_function_name = table_expression.table_function->as<ASTFunction>()->name;
TableFunctionPtr table_function_ptr = TableFunctionFactory::instance().tryGet(table_function_name, current_context);
if (!table_function_ptr)
{
auto [database_name, table_name] = extractDatabaseAndTableNameForParametrizedView(table_function_name, current_context);
auto table_id = getContext()->resolveStorageID({database_name, table_name});
getContext()->checkAccess(AccessType::SHOW_COLUMNS, table_id);
auto table = DatabaseCatalog::instance().getTable(table_id, getContext());
if (auto * storage_view = table->as<StorageView>())
{
if (storage_view->isParameterizedView())
{
auto query = storage_view->getInMemoryMetadataPtr()->getSelectQuery().inner_query->clone();
NameToNameMap parameterized_view_values = analyzeFunctionParamValues(table_expression.table_function, current_context);
StorageView::replaceQueryParametersIfParametrizedView(query, parameterized_view_values);
fillColumnsFromSubqueryImpl(query, current_context);
return;
}
}
auto hints = TableFunctionFactory::instance().getHints(table_function_name);
if (!hints.empty())
throw Exception(ErrorCodes::UNKNOWN_FUNCTION, "Unknown table function {}. Maybe you meant: {}", table_function_name, toString(hints));
else
throw Exception(ErrorCodes::UNKNOWN_FUNCTION, "Unknown table function {}", table_function_name);
}
else
{
table_function_ptr->parseArguments(table_expression.table_function, current_context);
}
auto column_descriptions = table_function_ptr->getActualTableStructure(getContext(), /*is_insert_query*/ true);
for (const auto & column : column_descriptions)
@ -180,6 +231,14 @@ void InterpreterDescribeQuery::fillColumnsFromTable(const ASTTableExpression & t
auto table_id = getContext()->resolveStorageID(table_expression.database_and_table_name);
getContext()->checkAccess(AccessType::SHOW_COLUMNS, table_id);
auto table = DatabaseCatalog::instance().getTable(table_id, getContext());
if (auto * storage_view = table->as<StorageView>())
{
if (storage_view->isParameterizedView())
throw Exception(ErrorCodes::UNSUPPORTED_METHOD,
"Cannot infer table schema for the parametrized view when no query parameters are provided");
}
auto table_lock = table->lockForShare(getContext()->getInitialQueryId(), settings[Setting::lock_acquire_timeout]);
auto metadata_snapshot = table->getInMemoryMetadataPtr();

View File

@ -1,5 +1,6 @@
#pragma once
#include <Interpreters/Context_fwd.h>
#include <Interpreters/IInterpreter.h>
#include <Storages/ColumnsDescription.h>
#include <Storages/StorageSnapshot.h>
@ -24,6 +25,7 @@ public:
private:
void fillColumnsFromSubquery(const ASTTableExpression & table_expression);
void fillColumnsFromSubqueryImpl(const ASTPtr & select_query, const ContextPtr & current_context);
void fillColumnsFromTableFunction(const ASTTableExpression & table_expression);
void fillColumnsFromTable(const ASTTableExpression & table_expression);

View File

@ -32,14 +32,15 @@ namespace ErrorCodes
namespace
{
class CollectSetsVisitor : public ConstInDepthQueryTreeVisitor<CollectSetsVisitor>
class CollectSetsVisitor : public InDepthQueryTreeVisitorWithContext<CollectSetsVisitor>
{
public:
explicit CollectSetsVisitor(PlannerContext & planner_context_)
: planner_context(planner_context_)
: InDepthQueryTreeVisitorWithContext<CollectSetsVisitor>(planner_context_.getQueryContext())
, planner_context(planner_context_)
{}
void visitImpl(const QueryTreeNodePtr & node)
void enterImpl(const QueryTreeNodePtr & node)
{
if (const auto * constant_node = node->as<ConstantNode>())
/// Collect sets from source expression as well.
@ -122,7 +123,7 @@ private:
}
void collectSets(const QueryTreeNodePtr & node, PlannerContext & planner_context)
void collectSets(QueryTreeNodePtr node, PlannerContext & planner_context)
{
CollectSetsVisitor visitor(planner_context);
visitor.visit(node);

View File

@ -12,6 +12,6 @@ struct SelectQueryOptions;
/** Collect prepared sets and sets for subqueries that are necessary to execute IN function and its variations.
* Collected sets are registered in planner context.
*/
void collectSets(const QueryTreeNodePtr & node, PlannerContext & planner_context);
void collectSets(QueryTreeNodePtr node, PlannerContext & planner_context);
}

View File

@ -0,0 +1,10 @@
number UInt64
number UInt64
number UInt64
\'Biba\' String
CAST(dummy, \'Int\') Int32
CAST(dummy, \'String\') String
0
0
55
45

View File

@ -0,0 +1,25 @@
create view paramview as select * from system.numbers where number <= {top:UInt64};
describe paramview; -- { serverError UNSUPPORTED_METHOD }
describe paramview(top = 10);
describe paramview(top = 2 + 2);
create view p2 as select number, {name:String} from system.numbers where number <= {top:UInt64};
describe p2(top = 10); -- { serverError UNKNOWN_QUERY_PARAMETER }
describe p2(name = 'Biba', top = 2);
create view p3 as select CAST(dummy, {t:String});
describe p3(t = 'Int');
describe p3(t = 'String');
describe (SELECT * FROM p3(t = 'Int64') union all SELECT * FROM p3(t = 'UInt64')); -- { serverError NO_COMMON_TYPE }
SELECT * FROM p3(t = 'String');
SELECT * FROM p3(plus(equals(equals(equals(t), equals(toLowCardinality(6) = 6, 2), top)), 'String', 6, 6), materialize(3), equals(p3(globalIn(1), p2(equals(top)), t = 'Int') = top, p3(globalIn(1), p2(equals(top)), t = 'Int'), 'Int', toNullable(toUInt256(3)), 3, 3), t = 'String') SETTINGS allow_experimental_analyzer = 1;
SELECT * FROM p3(plus(equals(equals(equals(t), equals(toLowCardinality(6) = 6, 2), top)), 'String', 6, 6), materialize(3), equals(p3(globalIn(1), p2(equals(top)), t = 'Int') = top, p3(globalIn(1), p2(equals(top)), t = 'Int'), 'Int', toNullable(toUInt256(3)), 3, 3), t = 'String') SETTINGS allow_experimental_analyzer = 0; -- { serverError NUMBER_OF_ARGUMENTS_DOESNT_MATCH }
select arrayReduce('sum', (select groupArray(number) from paramview(top=10)));
create view test_pv as select number from numbers({limit:UInt64});
with (select sum(number) from test_pv(limit=10)) as sm select sm;