8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 02:03:04 +01:00

Fix #7018 - Problems with windows frames.

This commit is contained in:
Adriano dos Santos Fernandes 2021-10-21 14:51:37 -03:00
parent 399b154ab0
commit 2cbcbe8fa9
5 changed files with 129 additions and 140 deletions

View File

@ -9148,14 +9148,13 @@ WindowClause* WindowClause::dsqlPass(DsqlCompilerScratch* dsqlScratch)
doDsqlPass(dsqlScratch, extent ? extent : window->extent),
exclusion ? exclusion : window->exclusion);
if (node->order && node->extent && node->extent->unit == FrameExtent::Unit::RANGE &&
if (node->extent && node->extent->unit == FrameExtent::Unit::RANGE &&
(node->extent->frame1->value || (node->extent->frame2 && node->extent->frame2->value)))
{
if (node->order->items.getCount() > 1)
{
status_exception::raise(
Arg::Gds(isc_dsql_window_range_multi_key));
}
if (!node->order)
status_exception::raise(Arg::Gds(isc_dsql_window_range_inv_key_type));
else if (node->order->items.getCount() > 1)
status_exception::raise(Arg::Gds(isc_dsql_window_range_multi_key));
else
{
OrderNode* key = nodeAs<OrderNode>(node->order->items[0]);
@ -9165,10 +9164,7 @@ WindowClause* WindowClause::dsqlPass(DsqlCompilerScratch* dsqlScratch)
DsqlDescMaker::fromNode(dsqlScratch, &desc, key->value);
if (!desc.isDateTime() && !desc.isNumeric())
{
status_exception::raise(
Arg::Gds(isc_dsql_window_range_inv_key_type));
}
status_exception::raise(Arg::Gds(isc_dsql_window_range_inv_key_type));
}
}

View File

@ -822,7 +822,7 @@ Data source : @4"}, /* eds_statement */
{335545115, "Cannot open cursor for non-SELECT statement"}, /* no_cursor */
{335545116, "If <window frame bound 1> specifies @1, then <window frame bound 2> shall not specify @2"}, /* dsql_window_incompat_frames */
{335545117, "RANGE based window with <expr> {PRECEDING | FOLLOWING} cannot have ORDER BY with more than one value"}, /* dsql_window_range_multi_key */
{335545118, "RANGE based window must have an ORDER BY key of numerical, date, time or timestamp types"}, /* dsql_window_range_inv_key_type */
{335545118, "RANGE based window with <offset> PRECEDING/FOLLOWING must have a single ORDER BY key of numerical, date, time or timestamp types"}, /* dsql_window_range_inv_key_type */
{335545119, "Window RANGE/ROWS PRECEDING/FOLLOWING value must be of a numerical type"}, /* dsql_window_frame_value_inv_type */
{335545120, "Invalid PRECEDING or FOLLOWING offset in window function: cannot be negative"}, /* window_frame_value_invalid */
{335545121, "Window @1 not found"}, /* dsql_window_not_found */

View File

@ -790,6 +790,10 @@ namespace Jrd
class WindowedStream : public RecordSource
{
public:
using Frame = WindowClause::Frame;
using FrameExtent = WindowClause::FrameExtent;
using Exclusion = WindowClause::Exclusion;
class WindowStream : public BaseAggWinStream<WindowStream, BaseBufferedStream>
{
private:
@ -839,8 +843,8 @@ namespace Jrd
WindowStream(thread_db* tdbb, CompilerScratch* csb, StreamType stream,
const NestValueArray* group, BaseBufferedStream* next,
SortNode* order, MapNode* windowMap,
WindowClause::FrameExtent* frameExtent,
WindowClause::Exclusion exclusion);
FrameExtent* frameExtent,
Exclusion exclusion);
public:
void open(thread_db* tdbb) const;
@ -860,19 +864,19 @@ namespace Jrd
private:
const void getFrameValue(thread_db* tdbb, jrd_req* request,
const WindowClause::Frame* frame, impure_value_ex* impureValue) const;
const Frame* frame, impure_value_ex* impureValue) const;
SINT64 locateFrameRange(thread_db* tdbb, jrd_req* request, Impure* impure,
const WindowClause::Frame* frame, const dsc* offsetDesc, SINT64 position) const;
const Frame* frame, const dsc* offsetDesc, SINT64 position) const;
private:
NestConst<SortNode> m_order;
const MapNode* m_windowMap;
NestConst<WindowClause::FrameExtent> m_frameExtent;
NestConst<FrameExtent> m_frameExtent;
Firebird::Array<NestConst<ArithmeticNode> > m_arithNodes;
NestValueArray m_aggSources, m_aggTargets;
NestValueArray m_winPassSources, m_winPassTargets;
WindowClause::Exclusion m_exclusion;
Exclusion m_exclusion;
UCHAR m_invariantOffsets; // 0x1 | 0x2 bitmask
};

View File

@ -191,35 +191,27 @@ WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb,
// Process the unpartioned and unordered map, if existent.
for (ObjectsArray<WindowSourceNode::Window>::iterator window = windows.begin();
window != windows.end();
++window)
for (auto& window : windows)
{
// While here, verify not supported functions/clauses.
if (window->order)
if (window.order || window.frameExtent->unit == FrameExtent::Unit::ROWS)
{
const NestConst<ValueExprNode>* source = window->map->sourceList.begin();
for (const NestConst<ValueExprNode>* const end = window->map->sourceList.end();
source != end;
++source)
for (const auto& source : window.map->sourceList)
{
const AggNode* aggNode = nodeAs<AggNode>(*source);
if (aggNode)
if (const auto aggNode = nodeAs<AggNode>(source))
{
if (aggNode->distinct)
{
status_exception::raise(
Arg::Gds(isc_wish_list) <<
Arg::Gds(isc_random) << "DISTINCT is not supported in ordered windows");
}
const char* arg = nullptr;
if (!(aggNode->getCapabilities() & AggNode::CAP_SUPPORTS_WINDOW_FRAME))
if (aggNode->distinct)
arg = "DISTINCT";
else if (!(aggNode->getCapabilities() & AggNode::CAP_SUPPORTS_WINDOW_FRAME))
arg = aggNode->aggInfo.name;
if (arg)
{
string msg;
msg.printf("%s is not supported in ordered windows", aggNode->aggInfo.name);
msg.printf("%s is not supported in windows with ORDER BY or frame by ROWS clauses", arg);
status_exception::raise(
Arg::Gds(isc_wish_list) <<
@ -229,15 +221,22 @@ WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb,
}
}
if (!window->group && !window->order)
if (!window.group && !window.order)
{
fb_assert(!m_joinedStream);
if (!m_joinedStream)
{
m_joinedStream = FB_NEW_POOL(csb->csb_pool) WindowStream(tdbb, csb, window.stream,
nullptr, FB_NEW_POOL(csb->csb_pool) BufferedStreamWindow(csb, m_next),
nullptr, window.map, window.frameExtent, window.exclusion);
}
else
{
m_joinedStream = FB_NEW_POOL(csb->csb_pool) WindowStream(tdbb, csb, window.stream,
nullptr, FB_NEW_POOL(csb->csb_pool) BufferedStream(csb, m_joinedStream),
nullptr, window.map, window.frameExtent, window.exclusion);
}
m_joinedStream = FB_NEW_POOL(csb->csb_pool) WindowStream(tdbb, csb, window->stream,
NULL, FB_NEW_POOL(csb->csb_pool) BufferedStreamWindow(csb, m_next),
NULL, window->map, NULL, window->exclusion);
OPT_gen_aggregate_distincts(tdbb, csb, window->map);
OPT_gen_aggregate_distincts(tdbb, csb, window.map);
}
}
@ -248,14 +247,8 @@ WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb,
StreamList streams;
for (ObjectsArray<WindowSourceNode::Window>::iterator window = windows.begin();
window != windows.end();
++window)
for (auto& window : windows)
{
// Refresh the stream list based on the last m_joinedStream.
streams.clear();
m_joinedStream->findUsedStreams(streams);
#if 0 //// FIXME: This causes problems, for example with FIRST_VALUE.
//// I think it can be fixed with the help of SlidingWindow.
@ -263,10 +256,10 @@ WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb,
// between !{<n> following || unbounded preceding} and unbounded following
if (window->frameExtent &&
window->frameExtent->frame2->bound == WindowClause::Frame::Bound::FOLLOWING &&
window->frameExtent->frame2->bound == Frame::Bound::FOLLOWING &&
!window->frameExtent->frame2->value &&
!(window->frameExtent->frame1->bound == WindowClause::Frame::Bound::FOLLOWING ||
(window->frameExtent->frame1->bound == WindowClause::Frame::Bound::PRECEDING &&
!(window->frameExtent->frame1->bound == Frame::Bound::FOLLOWING ||
(window->frameExtent->frame1->bound == Frame::Bound::PRECEDING &&
!window->frameExtent->frame1->value)))
{
if (window->order)
@ -286,14 +279,14 @@ WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb,
}
}
WindowClause::Frame* temp = window->frameExtent->frame1;
Frame* temp = window->frameExtent->frame1;
window->frameExtent->frame1 = window->frameExtent->frame2;
window->frameExtent->frame2 = temp;
window->frameExtent->frame1->bound = WindowClause::Frame::Bound::PRECEDING;
window->frameExtent->frame1->bound = Frame::Bound::PRECEDING;
if (window->frameExtent->frame2->bound == WindowClause::Frame::Bound::PRECEDING)
window->frameExtent->frame2->bound = WindowClause::Frame::Bound::FOLLOWING;
if (window->frameExtent->frame2->bound == Frame::Bound::PRECEDING)
window->frameExtent->frame2->bound = Frame::Bound::FOLLOWING;
}
#endif
@ -301,34 +294,38 @@ WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb,
SortNode* windowOrder;
if (window->group)
if (window.group)
{
windowOrder = FB_NEW_POOL(csb->csb_pool) SortNode(csb->csb_pool);
windowOrder->expressions.join(window->group->expressions);
windowOrder->direction.join(window->group->direction);
windowOrder->nullOrder.join(window->group->nullOrder);
windowOrder->expressions.join(window.group->expressions);
windowOrder->direction.join(window.group->direction);
windowOrder->nullOrder.join(window.group->nullOrder);
if (window->order)
if (window.order)
{
windowOrder->expressions.join(window->order->expressions);
windowOrder->direction.join(window->order->direction);
windowOrder->nullOrder.join(window->order->nullOrder);
windowOrder->expressions.join(window.order->expressions);
windowOrder->direction.join(window.order->direction);
windowOrder->nullOrder.join(window.order->nullOrder);
}
}
else
windowOrder = window->order;
windowOrder = window.order;
if (windowOrder)
{
SortedStream* sortedStream = OPT_gen_sort(tdbb, csb, streams, NULL,
// Refresh the stream list based on the last m_joinedStream.
streams.clear();
m_joinedStream->findUsedStreams(streams);
SortedStream* sortedStream = OPT_gen_sort(tdbb, csb, streams, nullptr,
m_joinedStream, windowOrder, false, false);
m_joinedStream = FB_NEW_POOL(csb->csb_pool) WindowStream(tdbb, csb, window->stream,
(window->group ? &window->group->expressions : NULL),
m_joinedStream = FB_NEW_POOL(csb->csb_pool) WindowStream(tdbb, csb, window.stream,
(window.group ? &window.group->expressions : nullptr),
FB_NEW_POOL(csb->csb_pool) BufferedStream(csb, sortedStream),
window->order, window->map, window->frameExtent, window->exclusion);
window.order, window.map, window.frameExtent, window.exclusion);
OPT_gen_aggregate_distincts(tdbb, csb, window->map);
OPT_gen_aggregate_distincts(tdbb, csb, window.map);
}
}
}
@ -419,8 +416,8 @@ void WindowedStream::nullRecords(thread_db* tdbb) const
WindowedStream::WindowStream::WindowStream(thread_db* tdbb, CompilerScratch* csb, StreamType stream,
const NestValueArray* group, BaseBufferedStream* next,
SortNode* order, MapNode* windowMap,
WindowClause::FrameExtent* frameExtent,
WindowClause::Exclusion exclusion)
FrameExtent* frameExtent,
Exclusion exclusion)
: BaseAggWinStream(tdbb, csb, stream, group, NULL, false, next),
m_order(order),
m_windowMap(windowMap),
@ -470,12 +467,12 @@ WindowedStream::WindowStream::WindowStream(thread_db* tdbb, CompilerScratch* csb
for (unsigned i = 0; i < 2; ++i)
{
WindowClause::Frame* frame = i == 0 ?
Frame* frame = i == 0 ?
m_frameExtent->frame1 : m_frameExtent->frame2;
if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::RANGE && frame->value)
if (m_frameExtent->unit == FrameExtent::Unit::RANGE && frame->value)
{
int direction = frame->bound == WindowClause::Frame::Bound::FOLLOWING ? 1 : -1;
int direction = frame->bound == Frame::Bound::FOLLOWING ? 1 : -1;
if (m_order->direction[0] == ORDER_DESC)
direction *= -1;
@ -605,37 +602,36 @@ bool WindowedStream::WindowStream::getRecord(thread_db* tdbb) const
// Find the window start.
if (m_order && m_frameExtent->frame1->value && !(m_invariantOffsets & 0x1))
if (m_frameExtent->frame1->value && !(m_invariantOffsets & 0x1))
getFrameValue(tdbb, request, m_frameExtent->frame1, &impure->startOffset);
if (!m_order)
impure->windowBlock.startPosition = impure->partitionBlock.startPosition;
// {range | rows} between unbounded preceding and ...
else if (m_frameExtent->frame1->bound == WindowClause::Frame::Bound::PRECEDING &&
!m_frameExtent->frame1->value)
// (no order by) range
if ((m_frameExtent->frame1->bound == Frame::Bound::PRECEDING && !m_frameExtent->frame1->value) ||
(!m_order && m_frameExtent->unit == FrameExtent::Unit::RANGE))
{
impure->windowBlock.startPosition = impure->partitionBlock.startPosition;
}
// rows between current row and ...
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::ROWS &&
m_frameExtent->frame1->bound == WindowClause::Frame::Bound::CURRENT_ROW)
else if (m_frameExtent->unit == FrameExtent::Unit::ROWS &&
m_frameExtent->frame1->bound == Frame::Bound::CURRENT_ROW)
{
impure->windowBlock.startPosition = position;
}
// rows between <n> {preceding | following} and ...
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::ROWS &&
else if (m_frameExtent->unit == FrameExtent::Unit::ROWS &&
m_frameExtent->frame1->value)
{
impure->windowBlock.startPosition = position + impure->startOffset.vlux_count;
}
// range between current row and ...
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::RANGE &&
m_frameExtent->frame1->bound == WindowClause::Frame::Bound::CURRENT_ROW)
else if (m_frameExtent->unit == FrameExtent::Unit::RANGE &&
m_frameExtent->frame1->bound == Frame::Bound::CURRENT_ROW)
{
impure->windowBlock.startPosition = position;
}
// range between <n> {preceding | following} and ...
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::RANGE &&
else if (m_frameExtent->unit == FrameExtent::Unit::RANGE &&
m_frameExtent->frame1->value)
{
impure->windowBlock.startPosition = locateFrameRange(tdbb, request, impure,
@ -649,32 +645,31 @@ bool WindowedStream::WindowStream::getRecord(thread_db* tdbb) const
// Find the window end.
if (m_order && m_frameExtent->frame2->value && !(m_invariantOffsets & 0x2))
if (m_frameExtent->frame2->value && !(m_invariantOffsets & 0x2))
getFrameValue(tdbb, request, m_frameExtent->frame2, &impure->endOffset);
if (!m_order)
impure->windowBlock.endPosition = impure->partitionBlock.endPosition;
// {range | rows} between ... and unbounded following
else if (m_frameExtent->frame2->bound == WindowClause::Frame::Bound::FOLLOWING &&
!m_frameExtent->frame2->value)
// (no order by) range
if ((m_frameExtent->frame2->bound == Frame::Bound::FOLLOWING && !m_frameExtent->frame2->value) ||
(!m_order && m_frameExtent->unit == FrameExtent::Unit::RANGE))
{
impure->windowBlock.endPosition = impure->partitionBlock.endPosition;
}
// rows between ... and current row
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::ROWS &&
m_frameExtent->frame2->bound == WindowClause::Frame::Bound::CURRENT_ROW)
else if (m_frameExtent->unit == FrameExtent::Unit::ROWS &&
m_frameExtent->frame2->bound == Frame::Bound::CURRENT_ROW)
{
impure->windowBlock.endPosition = position;
}
// rows between ... and <n> {preceding | following}
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::ROWS &&
else if (m_frameExtent->unit == FrameExtent::Unit::ROWS &&
m_frameExtent->frame2->value)
{
impure->windowBlock.endPosition = position + impure->endOffset.vlux_count;
}
// range between ... and current row
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::RANGE &&
m_frameExtent->frame2->bound == WindowClause::Frame::Bound::CURRENT_ROW)
else if (m_frameExtent->unit == FrameExtent::Unit::RANGE &&
m_frameExtent->frame2->bound == Frame::Bound::CURRENT_ROW)
{
SINT64 rangePos = position;
cacheValues(tdbb, request, &m_order->expressions, impure->orderValues,
@ -700,7 +695,7 @@ bool WindowedStream::WindowStream::getRecord(thread_db* tdbb) const
fb_assert(false);
}
// range between ... and <n> {preceding | following}
else if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::RANGE &&
else if (m_frameExtent->unit == FrameExtent::Unit::RANGE &&
m_frameExtent->frame2->value)
{
impure->windowBlock.endPosition = locateFrameRange(tdbb, request, impure,
@ -712,43 +707,37 @@ bool WindowedStream::WindowStream::getRecord(thread_db* tdbb) const
return false;
}
if (!m_order)
impure->rangePending = MAX(0, impure->windowBlock.endPosition - position);
else if (m_order && m_frameExtent->unit == WindowClause::FrameExtent::Unit::RANGE)
if ((m_frameExtent->frame1->bound == Frame::Bound::PRECEDING && !m_frameExtent->frame1->value &&
m_frameExtent->frame2->bound == Frame::Bound::FOLLOWING && !m_frameExtent->frame2->value) ||
(m_frameExtent->unit == FrameExtent::Unit::RANGE && !m_order))
{
if (m_frameExtent->frame1->bound == WindowClause::Frame::Bound::PRECEDING &&
!m_frameExtent->frame1->value &&
m_frameExtent->frame2->bound == WindowClause::Frame::Bound::FOLLOWING &&
!m_frameExtent->frame2->value)
{
impure->rangePending = MAX(0, impure->windowBlock.endPosition - position);
}
else
{
SINT64 rangePos = position;
cacheValues(tdbb, request, &m_order->expressions, impure->orderValues,
DummyAdjustFunctor());
while (++rangePos <= impure->partitionBlock.endPosition)
{
if (!m_next->getRecord(tdbb))
fb_assert(false);
if (lookForChange(tdbb, request, &m_order->expressions, m_order,
impure->orderValues))
{
break;
}
}
impure->rangePending = rangePos - position - 1;
}
m_next->locate(tdbb, position);
if (!m_next->getRecord(tdbb))
fb_assert(false);
impure->rangePending = MAX(0, impure->windowBlock.endPosition - position);
}
else if (m_frameExtent->unit == FrameExtent::Unit::RANGE)
{
SINT64 rangePos = position;
cacheValues(tdbb, request, &m_order->expressions, impure->orderValues,
DummyAdjustFunctor());
while (++rangePos <= impure->partitionBlock.endPosition)
{
if (!m_next->getRecord(tdbb))
fb_assert(false);
if (lookForChange(tdbb, request, &m_order->expressions, m_order,
impure->orderValues))
{
break;
}
}
impure->rangePending = rangePos - position - 1;
}
m_next->locate(tdbb, position);
if (!m_next->getRecord(tdbb))
fb_assert(false);
//// TODO: There is no need to pass record by record when m_aggSources.isEmpty()
@ -902,7 +891,7 @@ void WindowedStream::WindowStream::nullRecords(thread_db* tdbb) const
}
const void WindowedStream::WindowStream::getFrameValue(thread_db* tdbb, jrd_req* request,
const WindowClause::Frame* frame, impure_value_ex* impureValue) const
const Frame* frame, impure_value_ex* impureValue) const
{
dsc* desc = EVL_expr(tdbb, request, frame->value);
bool error = false;
@ -911,7 +900,7 @@ const void WindowedStream::WindowStream::getFrameValue(thread_db* tdbb, jrd_req*
error = true;
else
{
if (m_frameExtent->unit == WindowClause::FrameExtent::Unit::ROWS)
if (m_frameExtent->unit == FrameExtent::Unit::ROWS)
{
// Purposedly used 32-bit here. So long distance will complicate things for no gain.
impureValue->vlux_count = MOV_get_long(tdbb, desc, 0);
@ -919,7 +908,7 @@ const void WindowedStream::WindowStream::getFrameValue(thread_db* tdbb, jrd_req*
if (impureValue->vlux_count < 0)
error = true;
if (frame->bound == WindowClause::Frame::Bound::PRECEDING)
if (frame->bound == Frame::Bound::PRECEDING)
impureValue->vlux_count = -impureValue->vlux_count;
}
else if (MOV_compare(tdbb, desc, &zeroDsc) < 0)
@ -937,7 +926,7 @@ const void WindowedStream::WindowStream::getFrameValue(thread_db* tdbb, jrd_req*
}
SINT64 WindowedStream::WindowStream::locateFrameRange(thread_db* tdbb, jrd_req* request, Impure* impure,
const WindowClause::Frame* frame, const dsc* offsetDesc, SINT64 position) const
const Frame* frame, const dsc* offsetDesc, SINT64 position) const
{
if (m_order->expressions.getCount() != 1)
{
@ -983,7 +972,7 @@ SINT64 WindowedStream::WindowStream::locateFrameRange(thread_db* tdbb, jrd_req*
--rangePos;
}
}
else if (frame->bound == WindowClause::Frame::Bound::FOLLOWING)
else if (frame->bound == Frame::Bound::FOLLOWING)
{
const int bound = frame == m_frameExtent->frame1 ? 0 : 1;

View File

@ -905,7 +905,7 @@ Data source : @4', NULL, NULL)
('no_cursor', 'DSQL_open', 'dsql.cpp', NULL, 0, 795, NULL, 'Cannot open cursor for non-SELECT statement', NULL, NULL);
('dsql_window_incompat_frames', NULL, 'ExprNodes.cpp', NULL, 0, 796, NULL, 'If <window frame bound 1> specifies @1, then <window frame bound 2> shall not specify @2', NULL, NULL);
('dsql_window_range_multi_key', NULL, 'ExprNodes.cpp', NULL, 0, 797, NULL, 'RANGE based window with <expr> {PRECEDING | FOLLOWING} cannot have ORDER BY with more than one value', NULL, NULL);
('dsql_window_range_inv_key_type', NULL, 'ExprNodes.cpp', NULL, 0, 798, NULL, 'RANGE based window must have an ORDER BY key of numerical, date, time or timestamp types', NULL, NULL);
('dsql_window_range_inv_key_type', NULL, 'ExprNodes.cpp', NULL, 0, 798, NULL, 'RANGE based window with <offset> PRECEDING/FOLLOWING must have a single ORDER BY key of numerical, date, time or timestamp types', NULL, NULL);
('dsql_window_frame_value_inv_type', NULL, 'ExprNodes.cpp', NULL, 0, 799, NULL, 'Window RANGE/ROWS PRECEDING/FOLLOWING value must be of a numerical type', NULL, NULL);
('window_frame_value_invalid', NULL, 'ExprNodes.cpp', NULL, 0, 800, NULL, 'Invalid PRECEDING or FOLLOWING offset in window function: cannot be negative', NULL, NULL);
('dsql_window_not_found', NULL, 'ExprNodes.cpp', NULL, 0, 801, NULL, 'Window @1 not found', NULL, NULL);