mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
Merge pull request #24576 from ClickHouse/aku/inherit-window
allow inheriting from a named window in window definition
This commit is contained in:
commit
3824ba656c
@ -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});
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -10,6 +10,8 @@ namespace DB
|
||||
|
||||
struct ASTWindowDefinition : public IAST
|
||||
{
|
||||
std::string parent_window_name;
|
||||
|
||||
ASTPtr partition_by;
|
||||
|
||||
ASTPtr order_by;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
Loading…
Reference in New Issue
Block a user