aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoraneporada <aneporada@yandex-team.ru>2022-02-24 00:54:02 +0300
committeraneporada <aneporada@yandex-team.ru>2022-02-24 00:54:02 +0300
commit3d4ff3fedcc8246cef9be0ddcc1f6825fbaf7875 (patch)
treea1a73de4e8e902848cff0c96dbafca7d009dfa56
parent9f906d188714c5bd1dfc2d29117ba02ee1859730 (diff)
downloadydb-3d4ff3fedcc8246cef9be0ddcc1f6825fbaf7875.tar.gz
[YQL-10265] Generate WinOnGroups/WinOnRanges in parser
ref:80d7724ff4e722f4fdf2de2cfa38fc4bc1ec0fc8
-rw-r--r--ydb/library/yql/core/type_ann/type_ann_core.cpp5
-rw-r--r--ydb/library/yql/core/type_ann/type_ann_list.cpp18
-rw-r--r--ydb/library/yql/core/type_ann/type_ann_list.h3
-rw-r--r--ydb/library/yql/sql/v1/node.cpp44
-rw-r--r--ydb/library/yql/sql/v1/sql.cpp137
-rw-r--r--ydb/library/yql/sql/v1/sql_ut.cpp13
6 files changed, 148 insertions, 72 deletions
diff --git a/ydb/library/yql/core/type_ann/type_ann_core.cpp b/ydb/library/yql/core/type_ann/type_ann_core.cpp
index 743692061a..9596c5942b 100644
--- a/ydb/library/yql/core/type_ann/type_ann_core.cpp
+++ b/ydb/library/yql/core/type_ann/type_ann_core.cpp
@@ -13018,8 +13018,9 @@ template <NKikimr::NUdf::EDataSlot DataSlot>
Functions["MultiAggregate"] = &MultiAggregateWrapper;
Functions["Aggregate"] = &AggregateWrapper;
Functions["SqlAggregateAll"] = &SqlAggregateAllWrapper;
- Functions["WinOnRows"] = &WinOnRowsWrapper;
- Functions["WinOnRange"] = &WinOnRangeWrapper;
+ Functions["WinOnRows"] = &WinOnWrapper;
+ Functions["WinOnGroups"] = &WinOnWrapper;
+ Functions["WinOnRange"] = &WinOnWrapper;
Functions["WindowTraits"] = &WindowTraitsWrapper;
Functions["CalcOverWindow"] = &CalcOverWindowWrapper;
Functions["CalcOverSessionWindow"] = &CalcOverWindowWrapper;
diff --git a/ydb/library/yql/core/type_ann/type_ann_list.cpp b/ydb/library/yql/core/type_ann/type_ann_list.cpp
index 1fa47b7600..4724ac20b5 100644
--- a/ydb/library/yql/core/type_ann/type_ann_list.cpp
+++ b/ydb/library/yql/core/type_ann/type_ann_list.cpp
@@ -4941,7 +4941,17 @@ namespace {
return IGraphTransformer::TStatus::Ok;
}
- IGraphTransformer::TStatus WinOnRowsWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx) {
+ IGraphTransformer::TStatus WinOnWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx) {
+ if (input->IsCallable("WinOnGroups")) {
+ ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(input->Pos()), "GROUPS in frame specification are not supported yet"));
+ return IGraphTransformer::TStatus::Error;
+ }
+ if (input->IsCallable("WinOnRange")) {
+ ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(input->Pos()), "RANGE in frame specification is not supported yet"));
+ return IGraphTransformer::TStatus::Error;
+ }
+ YQL_ENSURE(input->IsCallable("WinOnRows"));
+
if (!EnsureMinArgsCount(*input, 1, ctx.Expr)) {
return IGraphTransformer::TStatus::Error;
}
@@ -4996,12 +5006,6 @@ namespace {
return IGraphTransformer::TStatus::Ok;
}
- IGraphTransformer::TStatus WinOnRangeWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx) {
- Y_UNUSED(output);
- ctx.Expr.AddError(TIssue(ctx.Expr.GetPosition(input->Pos()), "WinOnRange is not supported yet"));
- return IGraphTransformer::TStatus::Error;
- }
-
IGraphTransformer::TStatus WindowTraitsWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx) {
if (!EnsureArgsCount(*input, 6, ctx.Expr)) {
return IGraphTransformer::TStatus::Error;
diff --git a/ydb/library/yql/core/type_ann/type_ann_list.h b/ydb/library/yql/core/type_ann/type_ann_list.h
index c56b4879ee..4b84ae9846 100644
--- a/ydb/library/yql/core/type_ann/type_ann_list.h
+++ b/ydb/library/yql/core/type_ann/type_ann_list.h
@@ -97,8 +97,7 @@ namespace NTypeAnnImpl {
IGraphTransformer::TStatus SkipNullMembersWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
IGraphTransformer::TStatus FilterNullElementsWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
IGraphTransformer::TStatus SkipNullElementsWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
- IGraphTransformer::TStatus WinOnRowsWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
- IGraphTransformer::TStatus WinOnRangeWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
+ IGraphTransformer::TStatus WinOnWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
IGraphTransformer::TStatus WindowTraitsWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
IGraphTransformer::TStatus CalcOverWindowWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
IGraphTransformer::TStatus CalcOverWindowGroupWrapper(const TExprNode::TPtr& input, TExprNode::TPtr& output, TContext& ctx);
diff --git a/ydb/library/yql/sql/v1/node.cpp b/ydb/library/yql/sql/v1/node.cpp
index 364c1ec946..2e46537327 100644
--- a/ydb/library/yql/sql/v1/node.cpp
+++ b/ydb/library/yql/sql/v1/node.cpp
@@ -1833,14 +1833,34 @@ private:
TSourcePtr FakeSource;
};
-TNodePtr BuildFrameNode(const TFrameBound& frame) {
- YQL_ENSURE(frame.Settings != FrameUndefined);
+TNodePtr BuildFrameNode(const TFrameBound& frame, EFrameType frameType) {
+ TString settingStr;
+ switch (frame.Settings) {
+ case FramePreceding: settingStr = "preceding"; break;
+ case FrameCurrentRow: settingStr = "currentRow"; break;
+ case FrameFollowing: settingStr = "following"; break;
+ default: YQL_ENSURE(false, "Unexpected frame setting");
+ }
TNodePtr node = frame.Bound;
TPosition pos = frame.Pos;
- if (frame.Settings == FrameCurrentRow) {
- node = BuildQuotedAtom(pos, "currentRow", TNodeFlags::Default);
- } else if (!node) {
+ if (frameType != EFrameType::FrameByRows) {
+ TVector<TNodePtr> settings;
+ settings.push_back(BuildQuotedAtom(pos, settingStr, TNodeFlags::Default));
+ if (frame.Settings != FrameCurrentRow) {
+ if (!node) {
+ node = BuildQuotedAtom(pos, "unbounded", TNodeFlags::Default);
+ } else if (!node->IsLiteral()) {
+ node = new TYqlFrameBound(pos, node);
+ }
+ settings.push_back(std::move(node));
+ }
+ return BuildTuple(pos, std::move(settings));
+ }
+
+ // TODO: switch FrameByRows to common format above
+ YQL_ENSURE(frame.Settings != FrameCurrentRow, "Should be already replaced by 0 preceding/following");
+ if (!node) {
node = BuildLiteralVoid(pos);
} else if (node->IsLiteral()) {
YQL_ENSURE(node->GetLiteralType() == "Int32");
@@ -1860,13 +1880,12 @@ TNodePtr BuildFrameNode(const TFrameBound& frame) {
}
TNodePtr ISource::BuildWindowFrame(const TFrameSpecification& spec, bool isCompact) {
- YQL_ENSURE(spec.FrameType == FrameByRows);
YQL_ENSURE(spec.FrameExclusion == FrameExclNone);
YQL_ENSURE(spec.FrameBegin);
YQL_ENSURE(spec.FrameEnd);
- auto frameBeginNode = BuildFrameNode(*spec.FrameBegin);
- auto frameEndNode = BuildFrameNode(*spec.FrameEnd);
+ auto frameBeginNode = BuildFrameNode(*spec.FrameBegin, spec.FrameType);
+ auto frameEndNode = BuildFrameNode(*spec.FrameEnd, spec.FrameType);
auto begin = Q(Y(Q("begin"), frameBeginNode));
auto end = Q(Y(Q("end"), frameEndNode));
@@ -1937,7 +1956,14 @@ TNodePtr ISource::BuildCalcOverWindow(TContext& ctx, const TString& label) {
const auto& funcs = (funcsIter == FuncOverWindow.end()) ? TVector<TNodePtr>() : funcsIter->second;
auto frames = Y();
- auto callOnFrame = Y("WinOnRows", BuildWindowFrame(*spec->Frame, spec->IsCompact));
+ TString frameType;
+ switch (spec->Frame->FrameType) {
+ case EFrameType::FrameByRows: frameType = "WinOnRows"; break;
+ case EFrameType::FrameByRange: frameType = "WinOnRange"; break;
+ case EFrameType::FrameByGroups: frameType = "WinOnGroups"; break;
+ }
+ YQL_ENSURE(frameType);
+ auto callOnFrame = Y(frameType, BuildWindowFrame(*spec->Frame, spec->IsCompact));
for (auto& agg : aggs) {
auto winTraits = agg->WindowTraits(listType);
callOnFrame = L(callOnFrame, winTraits);
diff --git a/ydb/library/yql/sql/v1/sql.cpp b/ydb/library/yql/sql/v1/sql.cpp
index b3f12bb4e3..19a01db6df 100644
--- a/ydb/library/yql/sql/v1/sql.cpp
+++ b/ydb/library/yql/sql/v1/sql.cpp
@@ -859,11 +859,11 @@ protected:
bool RoleNameClause(const TRule_role_name& node, TDeferredAtom& result, bool allowSystemRoles);
bool RoleParameters(const TRule_create_user_option& node, TRoleParameters& result) ;
private:
- static bool IsValidFrameSettings(TContext& ctx, const TFrameBound& begin, const TFrameBound& end);
+ static bool IsValidFrameSettings(TContext& ctx, const TFrameSpecification& frameSpec, size_t sortSpecSize);
static TString FrameSettingsToString(EFrameSettings settings, bool isUnbounded);
bool FrameBound(const TRule_window_frame_bound& rule, TFrameBoundPtr& bound);
- bool FrameClause(const TRule_window_frame_clause& node, TFrameSpecificationPtr& frameSpec);
+ bool FrameClause(const TRule_window_frame_clause& node, TFrameSpecificationPtr& frameSpec, size_t sortSpecSize);
bool SortSpecification(const TRule_sort_specification& node, TVector<TSortSpecificationPtr>& sortSpecs);
bool ClusterExpr(const TRule_cluster_expr& node, bool allowWildcard, bool allowBinding, TString& service, TDeferredAtom& cluster, bool& isBinding);
@@ -6746,8 +6746,10 @@ bool CheckFrameBoundLiteral(TContext& ctx, const TFrameBound& bound, TMaybe<i32>
return true;
}
-bool TSqlTranslation::IsValidFrameSettings(TContext& ctx, const TFrameBound& begin, const TFrameBound& end) {
- Y_UNUSED(ctx);
+bool TSqlTranslation::IsValidFrameSettings(TContext& ctx, const TFrameSpecification& frameSpec, size_t sortSpecSize) {
+ const TFrameBound& begin = *frameSpec.FrameBegin;
+ const TFrameBound& end = *frameSpec.FrameEnd;
+
YQL_ENSURE(begin.Settings != FrameUndefined);
YQL_ENSURE(end.Settings != FrameUndefined);
@@ -6770,11 +6772,25 @@ bool TSqlTranslation::IsValidFrameSettings(TContext& ctx, const TFrameBound& beg
return false;
}
+ if (frameSpec.FrameType == FrameByRange && sortSpecSize != 1) {
+ TStringBuf msg = "RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause";
+ if (begin.Bound) {
+ ctx.Error(begin.Bound->GetPos()) << msg;
+ return false;
+ }
+ if (end.Bound) {
+ ctx.Error(end.Bound->GetPos()) << msg;
+ return false;
+ }
+ }
+
TMaybe<i32> beginValue;
TMaybe<i32> endValue;
- if (!CheckFrameBoundLiteral(ctx, begin, beginValue) || !CheckFrameBoundLiteral(ctx, end, endValue)) {
- return false;
+ if (frameSpec.FrameType != EFrameType::FrameByRange) {
+ if (!CheckFrameBoundLiteral(ctx, begin, beginValue) || !CheckFrameBoundLiteral(ctx, end, endValue)) {
+ return false;
+ }
}
if (beginValue.Defined() && endValue.Defined()) {
@@ -6840,25 +6856,21 @@ bool TSqlTranslation::FrameBound(const TRule_window_frame_bound& rule, TFrameBou
return true;
}
-bool TSqlTranslation::FrameClause(const TRule_window_frame_clause& rule, TFrameSpecificationPtr& frameSpec) {
+bool TSqlTranslation::FrameClause(const TRule_window_frame_clause& rule, TFrameSpecificationPtr& frameSpec, size_t sortSpecSize) {
+ // window_frame_clause: window_frame_units window_frame_extent window_frame_exclusion?;
frameSpec = new TFrameSpecification;
const TString frameUnitStr = to_lower(Token(rule.GetRule_window_frame_units1().GetToken1()));
if (frameUnitStr == "rows") {
frameSpec->FrameType = EFrameType::FrameByRows;
} else if (frameUnitStr == "range") {
frameSpec->FrameType = EFrameType::FrameByRange;
- } else if (frameUnitStr == "groups") {
- frameSpec->FrameType = EFrameType::FrameByGroups;
} else {
- Ctx.Error() << "Unknown frame type in window specification: " << frameUnitStr;
- return false;
- }
- if (frameSpec->FrameType != EFrameType::FrameByRows) {
- Ctx.Error() << "Only ROWS is supported as window frame unit";
- return false;
+ YQL_ENSURE(frameUnitStr == "groups");
+ frameSpec->FrameType = EFrameType::FrameByGroups;
}
auto frameExtent = rule.GetRule_window_frame_extent2();
+ // window_frame_extent: window_frame_bound | window_frame_between;
switch (frameExtent.Alt_case()) {
case TRule_window_frame_extent::kAltWindowFrameExtent1: {
auto start = frameExtent.GetAlt_window_frame_extent1().GetRule_window_frame_bound1();
@@ -6866,12 +6878,14 @@ bool TSqlTranslation::FrameClause(const TRule_window_frame_clause& rule, TFrameS
return false;
}
+ // frame end is CURRENT ROW
frameSpec->FrameEnd = new TFrameBound;
frameSpec->FrameEnd->Pos = frameSpec->FrameBegin->Pos;
frameSpec->FrameEnd->Settings = FrameCurrentRow;
break;
}
case TRule_window_frame_extent::kAltWindowFrameExtent2: {
+ // window_frame_between: BETWEEN window_frame_bound AND window_frame_bound;
auto between = frameExtent.GetAlt_window_frame_extent2().GetRule_window_frame_between1();
if (!FrameBound(between.GetRule_window_frame_bound2(), frameSpec->FrameBegin)) {
return false;
@@ -6886,13 +6900,12 @@ bool TSqlTranslation::FrameClause(const TRule_window_frame_clause& rule, TFrameS
}
YQL_ENSURE(frameSpec->FrameBegin);
YQL_ENSURE(frameSpec->FrameEnd);
- // check for literal
- if (!IsValidFrameSettings(Ctx, *frameSpec->FrameBegin, *frameSpec->FrameEnd))
- {
+ if (!IsValidFrameSettings(Ctx, *frameSpec, sortSpecSize)) {
return false;
}
if (rule.HasBlock3()) {
+ // window_frame_exclusion: EXCLUDE CURRENT ROW | EXCLUDE GROUP | EXCLUDE TIES | EXCLUDE NO OTHERS;
switch (rule.GetBlock3().GetRule_window_frame_exclusion1().Alt_case()) {
case TRule_window_frame_exclusion::kAltWindowFrameExclusion1:
frameSpec->FrameExclusion = FrameExclCurRow;
@@ -6916,22 +6929,6 @@ bool TSqlTranslation::FrameClause(const TRule_window_frame_clause& rule, TFrameS
return false;
}
- if (frameSpec->FrameType != EFrameType::FrameByRange) {
- // replace FrameCurrentRow for ROWS/GROUPS with 0 preceding/following
- // FrameCurrentRow has special meaning ( = first/last peer row)
- auto replaceCurrent = [](TFrameBound& frame, bool preceding) {
- frame.Settings = preceding ? EFrameSettings::FramePreceding : EFrameSettings::FrameFollowing;
- frame.Bound = new TLiteralNumberNode<i32>(frame.Pos, "Int32", "0");
- };
- if (frameSpec->FrameBegin->Settings == EFrameSettings::FrameCurrentRow) {
- replaceCurrent(*frameSpec->FrameBegin, true);
- }
-
- if (frameSpec->FrameEnd->Settings == EFrameSettings::FrameCurrentRow) {
- replaceCurrent(*frameSpec->FrameEnd, false);
- }
- }
-
return true;
}
@@ -6975,37 +6972,73 @@ TWindowSpecificationPtr TSqlTranslation::WindowSpecification(const TRule_window_
return {};
}
}
+ const bool ordered = !winSpecPtr->OrderBy.empty();
if (rule.HasBlock4()) {
- if (!FrameClause(rule.GetBlock4().GetRule_window_frame_clause1(), winSpecPtr->Frame)) {
+ if (!FrameClause(rule.GetBlock4().GetRule_window_frame_clause1(), winSpecPtr->Frame, winSpecPtr->OrderBy.size())) {
return {};
}
} else {
winSpecPtr->Frame = new TFrameSpecification;
- winSpecPtr->Frame->FrameType = EFrameType::FrameByRows;
- winSpecPtr->Frame->FrameExclusion = EFrameExclusions::FrameExclNone;
-
- // BETWEEN UNBOUNDED PRECEDING AND ...
winSpecPtr->Frame->FrameBegin = new TFrameBound;
- winSpecPtr->Frame->FrameBegin->Settings = EFrameSettings::FramePreceding;
-
- const bool ordered = !winSpecPtr->OrderBy.empty();
winSpecPtr->Frame->FrameEnd = new TFrameBound;
winSpecPtr->Frame->FrameBegin->Pos = winSpecPtr->Frame->FrameEnd->Pos = Ctx.Pos();
- if (ordered) {
- if (Ctx.AnsiCurrentRow) {
- // RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
- // We emit FrameByRows here, but FrameCurrentRow works specially for ROWS
- winSpecPtr->Frame->FrameEnd->Settings = EFrameSettings::FrameCurrentRow;
- } else {
- // ROWS BETWEEN UNBOUNDED PRECEDING AND 0 FOLLOWING
- winSpecPtr->Frame->FrameEnd->Settings = EFrameSettings::FrameFollowing;
- winSpecPtr->Frame->FrameEnd->Bound = new TLiteralNumberNode<i32>(Ctx.Pos(), "Int32", "0");
- }
+ winSpecPtr->Frame->FrameExclusion = EFrameExclusions::FrameExclNone;
+
+ winSpecPtr->Frame->FrameBegin->Settings = EFrameSettings::FramePreceding;
+ if (Ctx.AnsiCurrentRow) {
+ // RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ winSpecPtr->Frame->FrameType = EFrameType::FrameByRange;
+ winSpecPtr->Frame->FrameEnd->Settings = EFrameSettings::FrameCurrentRow;
+ } else if (ordered) {
+ // legacy behavior
+ // ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ winSpecPtr->Frame->FrameType = EFrameType::FrameByRows;
+ winSpecPtr->Frame->FrameEnd->Settings = EFrameSettings::FrameCurrentRow;
} else {
// ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ winSpecPtr->Frame->FrameType = EFrameType::FrameByRows;
winSpecPtr->Frame->FrameEnd->Settings = EFrameSettings::FrameFollowing;
}
}
+
+ // Normalize and simplify
+ auto replaceCurrentWith = [](TFrameBound& frame, bool preceding, TNodePtr value ) {
+ frame.Settings = preceding ? EFrameSettings::FramePreceding : EFrameSettings::FrameFollowing;
+ frame.Bound = value;
+ };
+
+ const auto frameSpec = winSpecPtr->Frame;
+ if (frameSpec->FrameType == EFrameType::FrameByRange) {
+ if (!ordered) {
+ // CURRENT ROW -> UNBOUNDED
+ if (frameSpec->FrameBegin->Settings == EFrameSettings::FrameCurrentRow) {
+ replaceCurrentWith(*frameSpec->FrameBegin, true, nullptr);
+ }
+ if (frameSpec->FrameEnd->Settings == EFrameSettings::FrameCurrentRow) {
+ replaceCurrentWith(*frameSpec->FrameBegin, false, nullptr);
+ }
+ }
+
+ // RANGE UNBOUNDED -> ROWS UNBOUNDED
+ if (frameSpec->FrameBegin->Settings == EFrameSettings::FramePreceding && !frameSpec->FrameBegin->Bound &&
+ frameSpec->FrameEnd->Settings == EFrameSettings::FrameFollowing && !frameSpec->FrameEnd->Bound)
+ {
+ frameSpec->FrameType = EFrameType::FrameByRows;
+ }
+ } else {
+ // replace FrameCurrentRow for ROWS/GROUPS with 0 preceding/following
+ // FrameCurrentRow has special meaning ( = first/last peer row)
+ if (frameSpec->FrameBegin->Settings == EFrameSettings::FrameCurrentRow) {
+ TNodePtr zero = new TLiteralNumberNode<i32>(winSpecPtr->Frame->FrameBegin->Pos, "Int32", "0");
+ replaceCurrentWith(*frameSpec->FrameBegin, true, zero);
+ }
+
+ if (frameSpec->FrameEnd->Settings == EFrameSettings::FrameCurrentRow) {
+ TNodePtr zero = new TLiteralNumberNode<i32>(winSpecPtr->Frame->FrameEnd->Pos, "Int32", "0");
+ replaceCurrentWith(*frameSpec->FrameEnd, false, zero);
+ }
+ }
+
return winSpecPtr;
}
diff --git a/ydb/library/yql/sql/v1/sql_ut.cpp b/ydb/library/yql/sql/v1/sql_ut.cpp
index 1fe963dc96..25b656d4aa 100644
--- a/ydb/library/yql/sql/v1/sql_ut.cpp
+++ b/ydb/library/yql/sql/v1/sql_ut.cpp
@@ -2629,6 +2629,19 @@ select FormatType($f());
check("ROWS BETWEEN 5 FOLLOWING AND 5 PRECEDING", "<main>:2:14: Error: Frame cannot start from FOLLOWING and end with PRECEDING\n");
}
+ Y_UNIT_TEST(BlockedRangeValueWithoutSingleOrderBy) {
+ UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM plato.Input").IsOk());
+ UNIT_ASSERT(SqlToYql("SELECT COUNT(*) OVER (RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM plato.Input").IsOk());
+
+ auto res = SqlToYql("SELECT COUNT(*) OVER (RANGE 5 PRECEDING) FROM plato.Input");
+ UNIT_ASSERT(!res.Root);
+ UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:29: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n");
+
+ res = SqlToYql("SELECT COUNT(*) OVER (ORDER BY key, value RANGE 5 PRECEDING) FROM plato.Input");
+ UNIT_ASSERT(!res.Root);
+ UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:1:49: Error: RANGE with <offset> PRECEDING/FOLLOWING requires exactly one expression in ORDER BY partition clause\n");
+ }
+
Y_UNIT_TEST(NoColumnsInFrameBounds) {
NYql::TAstParseResult res = SqlToYql(
"SELECT SUM(x) OVER w FROM plato.Input WINDOW w AS (ROWS BETWEEN\n"