Merge pull request #24576 from ClickHouse/aku/inherit-window

allow inheriting from a named window in window definition
This commit is contained in:
Alexander Kuzmenkov 2021-05-28 23:43:35 +03:00 committed by GitHub
commit 3824ba656c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 176 additions and 29 deletions

View File

@ -474,10 +474,58 @@ bool ExpressionAnalyzer::makeAggregateDescriptions(ActionsDAGPtr & actions)
return !aggregates().empty();
}
void makeWindowDescriptionFromAST(WindowDescription & desc, const IAST * ast)
void makeWindowDescriptionFromAST(const WindowDescriptions & existing_descriptions,
WindowDescription & desc, const IAST * ast)
{
const auto & definition = ast->as<const ASTWindowDefinition &>();
if (!definition.parent_window_name.empty())
{
auto it = existing_descriptions.find(definition.parent_window_name);
if (it == existing_descriptions.end())
{
throw Exception(ErrorCodes::BAD_ARGUMENTS,
"Window definition '{}' references an unknown window '{}'",
definition.formatForErrorMessage(),
definition.parent_window_name);
}
const auto & parent = it->second;
desc.partition_by = parent.partition_by;
desc.order_by = parent.order_by;
desc.frame = parent.frame;
// If an existing_window_name is specified it must refer to an earlier
// entry in the WINDOW list; the new window copies its partitioning clause
// from that entry, as well as its ordering clause if any. In this case
// the new window cannot specify its own PARTITION BY clause, and it can
// specify ORDER BY only if the copied window does not have one. The new
// window always uses its own frame clause; the copied window must not
// specify a frame clause.
// -- https://www.postgresql.org/docs/current/sql-select.html
if (definition.partition_by)
{
throw Exception(ErrorCodes::BAD_ARGUMENTS,
"Derived window definition '{}' is not allowed to override PARTITION BY",
definition.formatForErrorMessage());
}
if (definition.order_by && !parent.order_by.empty())
{
throw Exception(ErrorCodes::BAD_ARGUMENTS,
"Derived window definition '{}' is not allowed to override a non-empty ORDER BY",
definition.formatForErrorMessage());
}
if (!parent.frame.is_default)
{
throw Exception(ErrorCodes::BAD_ARGUMENTS,
"Parent window '{}' is not allowed to define a frame: while processing derived window definition '{}'",
definition.parent_window_name,
definition.formatForErrorMessage());
}
}
if (definition.partition_by)
{
for (const auto & column_ast : definition.partition_by->children)
@ -557,7 +605,7 @@ void ExpressionAnalyzer::makeWindowDescriptions(ActionsDAGPtr actions)
const auto & elem = ptr->as<const ASTWindowListElement &>();
WindowDescription desc;
desc.window_name = elem.name;
makeWindowDescriptionFromAST(desc, elem.definition.get());
makeWindowDescriptionFromAST(window_descriptions, desc, elem.definition.get());
auto [it, inserted] = window_descriptions.insert(
{desc.window_name, desc});
@ -642,7 +690,7 @@ void ExpressionAnalyzer::makeWindowDescriptions(ActionsDAGPtr actions)
const ASTWindowDefinition &>();
WindowDescription desc;
desc.window_name = definition.getDefaultWindowName();
makeWindowDescriptionFromAST(desc, &definition);
makeWindowDescriptionFromAST(window_descriptions, desc, &definition);
auto [it, inserted] = window_descriptions.insert(
{desc.window_name, desc});

View File

@ -12,6 +12,8 @@ ASTPtr ASTWindowDefinition::clone() const
{
auto result = std::make_shared<ASTWindowDefinition>();
result->parent_window_name = parent_window_name;
if (partition_by)
{
result->partition_by = partition_by->clone();
@ -38,31 +40,48 @@ void ASTWindowDefinition::formatImpl(const FormatSettings & settings,
FormatState & state, FormatStateStacked format_frame) const
{
format_frame.expression_list_prepend_whitespace = false;
bool need_space = false;
if (!parent_window_name.empty())
{
settings.ostr << backQuoteIfNeed(parent_window_name);
need_space = true;
}
if (partition_by)
{
if (need_space)
{
settings.ostr << " ";
}
settings.ostr << "PARTITION BY ";
partition_by->formatImpl(settings, state, format_frame);
}
if (partition_by && order_by)
{
settings.ostr << " ";
need_space = true;
}
if (order_by)
{
if (need_space)
{
settings.ostr << " ";
}
settings.ostr << "ORDER BY ";
order_by->formatImpl(settings, state, format_frame);
}
if ((partition_by || order_by) && !frame.is_default)
{
settings.ostr << " ";
need_space = true;
}
if (!frame.is_default)
{
if (need_space)
{
settings.ostr << " ";
}
settings.ostr << WindowFrame::toString(frame.type) << " BETWEEN ";
if (frame.begin_type == WindowFrame::BoundaryType::Current)
{

View File

@ -10,6 +10,8 @@ namespace DB
struct ASTWindowDefinition : public IAST
{
std::string parent_window_name;
ASTPtr partition_by;
ASTPtr order_by;

View File

@ -502,9 +502,6 @@ bool ParserWindowReference::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
// Variant 1:
// function_name ( * ) OVER window_name
// FIXME doesn't work anyway for now -- never used anywhere, window names
// can't be defined, and TreeRewriter thinks the window name is a column so
// the query fails.
if (pos->type != TokenType::OpeningRoundBracket)
{
ASTPtr window_name_ast;
@ -662,16 +659,10 @@ static bool tryParseFrameDefinition(ASTWindowDefinition * node, IParser::Pos & p
return true;
}
bool ParserWindowDefinition::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
// All except parent window name.
static bool parseWindowDefinitionParts(IParser::Pos & pos,
ASTWindowDefinition & node, Expected & expected)
{
auto result = std::make_shared<ASTWindowDefinition>();
ParserToken parser_openging_bracket(TokenType::OpeningRoundBracket);
if (!parser_openging_bracket.ignore(pos, expected))
{
return false;
}
ParserKeyword keyword_partition_by("PARTITION BY");
ParserNotEmptyExpressionList columns_partition_by(
false /* we don't allow declaring aliases here*/);
@ -683,8 +674,8 @@ bool ParserWindowDefinition::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
ASTPtr partition_by_ast;
if (columns_partition_by.parse(pos, partition_by_ast, expected))
{
result->children.push_back(partition_by_ast);
result->partition_by = partition_by_ast;
node.children.push_back(partition_by_ast);
node.partition_by = partition_by_ast;
}
else
{
@ -697,8 +688,8 @@ bool ParserWindowDefinition::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
ASTPtr order_by_ast;
if (columns_order_by.parse(pos, order_by_ast, expected))
{
result->children.push_back(order_by_ast);
result->order_by = order_by_ast;
node.children.push_back(order_by_ast);
node.order_by = order_by_ast;
}
else
{
@ -706,9 +697,45 @@ bool ParserWindowDefinition::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
}
}
if (!tryParseFrameDefinition(result.get(), pos, expected))
return tryParseFrameDefinition(&node, pos, expected);
}
bool ParserWindowDefinition::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
auto result = std::make_shared<ASTWindowDefinition>();
ParserToken parser_openging_bracket(TokenType::OpeningRoundBracket);
if (!parser_openging_bracket.ignore(pos, expected))
{
return false;
}
// We can have a parent window name specified before all other things. No
// easy way to distinguish identifier from keywords, so just try to parse it
// both ways.
if (parseWindowDefinitionParts(pos, *result, expected))
{
// Successfully parsed without parent window specifier. It can be empty,
// so check that it is followed by the closing bracket.
ParserToken parser_closing_bracket(TokenType::ClosingRoundBracket);
if (parser_closing_bracket.ignore(pos, expected))
{
node = result;
return true;
}
}
// Try to parse with parent window specifier.
ParserIdentifier parser_parent_window;
ASTPtr window_name_identifier;
if (!parser_parent_window.parse(pos, window_name_identifier, expected))
{
return false;
}
result->parent_window_name = window_name_identifier->as<const ASTIdentifier &>().name();
if (!parseWindowDefinitionParts(pos, *result, expected))
{
/* Broken frame definition. */
return false;
}

View File

@ -1120,3 +1120,31 @@ select count() over (rows between 2147483648 preceding and 2147493648 following)
select count() over () from (select 1 a) l inner join (select 2 a) r using a;
-- This case works as expected, one empty input chunk marked as input end.
select count() over () where null;
-- Inheriting another window.
select number, count() over (w1 rows unbounded preceding) from numbers(10)
window
w0 as (partition by intDiv(number, 5) as p),
w1 as (w0 order by mod(number, 3) as o)
order by p, o, number
;
0 1
3 2
1 3
4 4
2 5
6 1
9 2
7 3
5 4
8 5
-- can't redefine PARTITION BY
select count() over (w partition by number) from numbers(1) window w as (partition by intDiv(number, 5)); -- { serverError 36 }
-- can't redefine existing ORDER BY
select count() over (w order by number) from numbers(1) window w as (partition by intDiv(number, 5) order by mod(number, 3)); -- { serverError 36 }
-- parent window can't have frame
select count() over (w range unbounded preceding) from numbers(1) window w as (partition by intDiv(number, 5) order by mod(number, 3) rows unbounded preceding); -- { serverError 36 }
-- looks weird but probably should work -- this is a window that inherits and changes nothing
select count() over (w) from numbers(1) window w as ();
1
-- nonexistent parent window
select count() over (w2 rows unbounded preceding); -- { serverError 36 }

View File

@ -427,3 +427,26 @@ select count() over (rows between 2147483648 preceding and 2147493648 following)
select count() over () from (select 1 a) l inner join (select 2 a) r using a;
-- This case works as expected, one empty input chunk marked as input end.
select count() over () where null;
-- Inheriting another window.
select number, count() over (w1 rows unbounded preceding) from numbers(10)
window
w0 as (partition by intDiv(number, 5) as p),
w1 as (w0 order by mod(number, 3) as o)
order by p, o, number
;
-- can't redefine PARTITION BY
select count() over (w partition by number) from numbers(1) window w as (partition by intDiv(number, 5)); -- { serverError 36 }
-- can't redefine existing ORDER BY
select count() over (w order by number) from numbers(1) window w as (partition by intDiv(number, 5) order by mod(number, 3)); -- { serverError 36 }
-- parent window can't have frame
select count() over (w range unbounded preceding) from numbers(1) window w as (partition by intDiv(number, 5) order by mod(number, 3) rows unbounded preceding); -- { serverError 36 }
-- looks weird but probably should work -- this is a window that inherits and changes nothing
select count() over (w) from numbers(1) window w as ();
-- nonexistent parent window
select count() over (w2 rows unbounded preceding); -- { serverError 36 }