Skip to content

Commit 640b352

Browse files
committed
Adding an implementation of the new LISTAGG function
1 parent 8e52fde commit 640b352

File tree

10 files changed

+285
-19
lines changed

10 files changed

+285
-19
lines changed

src/common/ParserTokens.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ PARSER_TOKEN(TOK_LIKE, "LIKE", false)
289289
PARSER_TOKEN(TOK_LIMBO, "LIMBO", true)
290290
PARSER_TOKEN(TOK_LINGER, "LINGER", true)
291291
PARSER_TOKEN(TOK_LIST, "LIST", true)
292+
PARSER_TOKEN(TOK_LISTAGG, "LISTAGG", true)
292293
PARSER_TOKEN(TOK_LN, "LN", true)
293294
PARSER_TOKEN(TOK_LATERAL, "LATERAL", false)
294295
PARSER_TOKEN(TOK_LOCAL, "LOCAL", false)
@@ -555,6 +556,7 @@ PARSER_TOKEN(TOK_WHERE, "WHERE", false)
555556
PARSER_TOKEN(TOK_WHILE, "WHILE", false)
556557
PARSER_TOKEN(TOK_WINDOW, "WINDOW", false)
557558
PARSER_TOKEN(TOK_WITH, "WITH", false)
559+
PARSER_TOKEN(TOK_WITHIN, "WITHIN", false)
558560
PARSER_TOKEN(TOK_WITHOUT, "WITHOUT", false)
559561
PARSER_TOKEN(TOK_WORK, "WORK", true)
560562
PARSER_TOKEN(TOK_WRITE, "WRITE", true)

src/dsql/AggNodes.cpp

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ string AggNode::internalPrint(NodePrinter& printer) const
124124
NODE_PRINT(printer, dialect1);
125125
NODE_PRINT(printer, arg);
126126
NODE_PRINT(printer, asb);
127+
NODE_PRINT(printer, sort);
127128
NODE_PRINT(printer, indexed);
128129

129130
return aggInfo.name;
@@ -307,7 +308,7 @@ bool AggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other,
307308
// ASF: We compare name address. That should be ok, as we have only one AggInfo instance
308309
// per function.
309310
return aggInfo.blr == o->aggInfo.blr && aggInfo.name == o->aggInfo.name &&
310-
distinct == o->distinct && dialect1 == o->dialect1;
311+
distinct == o->distinct && dialect1 == o->dialect1 && sort == o->sort;;
311312
}
312313

313314
void AggNode::setParameterName(dsql_par* parameter) const
@@ -352,6 +353,8 @@ AggNode* AggNode::pass2(thread_db* tdbb, CompilerScratch* csb)
352353
dsc desc;
353354
getDesc(tdbb, csb, &desc);
354355
impureOffset = csb->allocImpure<impure_value_ex>();
356+
if (sort)
357+
doPass2(tdbb, csb, sort.getAddress());
355358

356359
return this;
357360
}
@@ -361,7 +364,7 @@ void AggNode::aggInit(thread_db* tdbb, Request* request) const
361364
impure_value_ex* impure = request->getImpure<impure_value_ex>(impureOffset);
362365
impure->vlux_count = 0;
363366

364-
if (distinct)
367+
if (distinct || sort)
365368
{
366369
// Initialize a sort to reject duplicate values.
367370

@@ -373,8 +376,8 @@ void AggNode::aggInit(thread_db* tdbb, Request* request) const
373376

374377
asbImpure->iasb_sort = FB_NEW_POOL(request->req_sorts.getPool()) Sort(
375378
tdbb->getDatabase(), &request->req_sorts, asb->length,
376-
asb->keyItems.getCount(), 1, asb->keyItems.begin(),
377-
RecordSource::rejectDuplicate, 0);
379+
asb->keyItems.getCount(), (distinct ? 1 : asb->keyItems.getCount()),
380+
asb->keyItems.begin(), (distinct ? RecordSource::rejectDuplicate : nullptr), 0);
378381
}
379382
}
380383

@@ -427,6 +430,44 @@ bool AggNode::aggPass(thread_db* tdbb, Request* request) const
427430
ULONG* const pDummy = reinterpret_cast<ULONG*>(data + asb->length - sizeof(ULONG));
428431
*pDummy = asbImpure->iasb_dummy++;
429432

433+
return true;
434+
}
435+
else if (sort)
436+
{
437+
fb_assert(asb);
438+
// "Put" the value to sort.
439+
impure_agg_sort* asbImpure = request->getImpure<impure_agg_sort>(asb->impure);
440+
UCHAR* data;
441+
asbImpure->iasb_sort->put(tdbb, reinterpret_cast<ULONG**>(&data));
442+
443+
MOVE_CLEAR(data, asb->length);
444+
445+
auto descOrder = asb->descOrder.begin();
446+
auto keyItem = asb->keyItems.begin();
447+
448+
for (auto& nodeOrder : sort->expressions)
449+
{
450+
dsc toDesc = *(descOrder++);
451+
toDesc.dsc_address = data + (IPTR)toDesc.dsc_address;
452+
if (const auto fromDsc = EVL_expr(tdbb, request, nodeOrder))
453+
{
454+
if (IS_INTL_DATA(fromDsc))
455+
INTL_string_to_key(tdbb, INTL_TEXT_TO_INDEX(fromDsc->getTextType()),
456+
fromDsc, &toDesc, INTL_KEY_UNIQUE);
457+
else
458+
MOV_move(tdbb, fromDsc, &toDesc);
459+
}
460+
else
461+
*(data + keyItem->getSkdOffset()) = TRUE;
462+
463+
// The first key for NULLS FIRST/LAST, the second key for the sorter
464+
keyItem += 2;
465+
}
466+
467+
dsc toDesc = asb->desc;
468+
toDesc.dsc_address = data + (IPTR)toDesc.dsc_address;
469+
MOV_move(tdbb, desc, &toDesc);
470+
430471
return true;
431472
}
432473
}
@@ -455,7 +496,7 @@ dsc* AggNode::execute(thread_db* tdbb, Request* request) const
455496
impure->vlu_blob = NULL;
456497
}
457498

458-
if (distinct)
499+
if (distinct || sort)
459500
{
460501
impure_agg_sort* asbImpure = request->getImpure<impure_agg_sort>(asb->impure);
461502
dsc desc = asb->desc;
@@ -478,7 +519,10 @@ dsc* AggNode::execute(thread_db* tdbb, Request* request) const
478519
break;
479520
}
480521

481-
desc.dsc_address = data + (asb->intl ? asb->keyItems[1].getSkdOffset() : 0);
522+
if (distinct)
523+
desc.dsc_address = data + (asb->intl ? asb->keyItems[1].getSkdOffset() : 0);
524+
else
525+
desc.dsc_address = data + (IPTR)asb->desc.dsc_address;
482526

483527
aggPass(tdbb, request, &desc);
484528
}
@@ -877,18 +921,19 @@ AggNode* AvgAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/
877921
static AggNode::Register<ListAggNode> listAggInfo("LIST", blr_agg_list, blr_agg_list_distinct);
878922

879923
ListAggNode::ListAggNode(MemoryPool& pool, bool aDistinct, ValueExprNode* aArg,
880-
ValueExprNode* aDelimiter)
924+
ValueExprNode* aDelimiter, ValueListNode* aOrderClause)
881925
: AggNode(pool, listAggInfo, aDistinct, false, aArg),
882-
delimiter(aDelimiter)
926+
delimiter(aDelimiter),
927+
dsqlOrderClause(aOrderClause)
883928
{
884929
}
885930

886931
DmlNode* ListAggNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
887932
{
888-
ListAggNode* node = FB_NEW_POOL(pool) ListAggNode(pool,
889-
(blrOp == blr_agg_list_distinct));
933+
ListAggNode* node = FB_NEW_POOL(pool) ListAggNode(pool, (blrOp == blr_agg_list_distinct));
890934
node->arg = PAR_parse_value(tdbb, csb);
891935
node->delimiter = PAR_parse_value(tdbb, csb);
936+
node->sort = PAR_sort(tdbb, csb, blr_sort, true);
892937
return node;
893938
}
894939

@@ -899,6 +944,26 @@ void ListAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
899944
desc->setNullable(true);
900945
}
901946

947+
void ListAggNode::genBlr(DsqlCompilerScratch* dsqlScratch)
948+
{
949+
AggNode::genBlr(dsqlScratch);
950+
GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause);
951+
}
952+
953+
AggNode* ListAggNode::pass1(thread_db* tdbb, CompilerScratch* csb)
954+
{
955+
if (sort && distinct)
956+
{
957+
ValueExprNode* const sortNode = *sort->expressions.begin();
958+
if (!arg->sameAs(sortNode, false) || sort->expressions.getCount() > 1)
959+
{
960+
ERR_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_command_err)
961+
<< Arg::Gds(isc_distinct_order_by_err));
962+
}
963+
}
964+
return AggNode::pass1(tdbb, csb);
965+
}
966+
902967
bool ListAggNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
903968
std::function<void (dsc*)> makeDesc, bool forceVarChar)
904969
{
@@ -920,6 +985,7 @@ ValueExprNode* ListAggNode::copy(thread_db* tdbb, NodeCopier& copier) const
920985
node->nodScale = nodScale;
921986
node->arg = copier.copy(tdbb, arg);
922987
node->delimiter = copier.copy(tdbb, delimiter);
988+
node->sort = sort->copy(tdbb, copier);
923989
return node;
924990
}
925991

@@ -985,7 +1051,7 @@ dsc* ListAggNode::aggExecute(thread_db* tdbb, Request* request) const
9851051
{
9861052
impure_value_ex* impure = request->getImpure<impure_value_ex>(impureOffset);
9871053

988-
if (distinct)
1054+
if (distinct || sort)
9891055
{
9901056
if (impure->vlu_blob)
9911057
{
@@ -1005,7 +1071,8 @@ AggNode* ListAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/
10051071
thread_db* tdbb = JRD_get_thread_data();
10061072

10071073
AggNode* node = FB_NEW_POOL(dsqlScratch->getPool()) ListAggNode(dsqlScratch->getPool(), distinct,
1008-
doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, delimiter));
1074+
doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, delimiter),
1075+
doDsqlPass(dsqlScratch, dsqlOrderClause));
10091076

10101077
dsc argDesc;
10111078
node->arg->make(dsqlScratch, &argDesc);

src/dsql/AggNodes.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ class AvgAggNode final : public AggNode
9595
class ListAggNode final : public AggNode
9696
{
9797
public:
98-
explicit ListAggNode(MemoryPool& pool, bool aDistinct, ValueExprNode* aArg = NULL,
99-
ValueExprNode* aDelimiter = NULL);
98+
explicit ListAggNode(MemoryPool& pool, bool aDistinct, ValueExprNode* aArg = nullptr,
99+
ValueExprNode* aDelimiter = nullptr, ValueListNode* aOrderClause = nullptr);
100100

101101
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
102102

@@ -113,6 +113,8 @@ class ListAggNode final : public AggNode
113113

114114
virtual Firebird::string internalPrint(NodePrinter& printer) const;
115115
virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc);
116+
virtual void genBlr(DsqlCompilerScratch* dsqlScratch) final;
117+
virtual AggNode* pass1(thread_db* tdbb, CompilerScratch* csb) final;
116118
virtual bool setParameterType(DsqlCompilerScratch* dsqlScratch,
117119
std::function<void (dsc*)> makeDesc, bool forceVarChar);
118120
virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc);
@@ -127,6 +129,7 @@ class ListAggNode final : public AggNode
127129

128130
private:
129131
NestConst<ValueExprNode> delimiter;
132+
NestConst<ValueListNode> dsqlOrderClause;
130133
};
131134

132135
class CountAggNode final : public AggNode

src/dsql/Nodes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class RseNode;
4949
class SlidingWindow;
5050
class TypeClause;
5151
class ValueExprNode;
52+
class SortNode;
5253

5354

5455
// Must be less then MAX_SSHORT. Not used for static arrays.
@@ -1113,6 +1114,7 @@ class AggNode : public TypedNode<ValueExprNode, ExprNode::TYPE_AGGREGATE>
11131114
const AggInfo& aggInfo;
11141115
NestConst<ValueExprNode> arg;
11151116
const AggregateSort* asb;
1117+
NestConst<SortNode> sort;
11161118
bool distinct;
11171119
bool dialect1;
11181120
bool indexed;

src/dsql/parse.y

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,8 @@ using namespace Firebird;
715715
%token <metaNamePtr> SCHEMA
716716
%token <metaNamePtr> SEARCH_PATH
717717
%token <metaNamePtr> UNLIST
718+
%token <metaNamePtr> LISTAGG
719+
%token <metaNamePtr> WITHIN
718720

719721
// precedence declarations for expression evaluation
720722

@@ -4706,6 +4708,7 @@ keyword_or_column
47064708
| RTRIM
47074709
| GREATEST
47084710
| LEAST
4711+
| WITHIN
47094712
;
47104713

47114714
col_opt
@@ -8535,10 +8538,8 @@ aggregate_function_prefix
85358538
{ $$ = newNode<MaxMinAggNode>(MaxMinAggNode::TYPE_MAX, $4); }
85368539
| MAXIMUM '(' DISTINCT value ')'
85378540
{ $$ = newNode<MaxMinAggNode>(MaxMinAggNode::TYPE_MAX, $4); }
8538-
| LIST '(' all_noise value delimiter_opt ')'
8539-
{ $$ = newNode<ListAggNode>(false, $4, $5); }
8540-
| LIST '(' DISTINCT value delimiter_opt ')'
8541-
{ $$ = newNode<ListAggNode>(true, $4, $5); }
8541+
| listagg_set_function
8542+
{ $$ = $1; }
85428543
| STDDEV_SAMP '(' value ')'
85438544
{ $$ = newNode<StdDevAggNode>(StdDevAggNode::TYPE_STDDEV_SAMP, $3); }
85448545
| STDDEV_POP '(' value ')'
@@ -8575,6 +8576,78 @@ aggregate_function_prefix
85758576
{ $$ = newNode<AnyValueAggNode>($4); }
85768577
;
85778578

8579+
%type <aggNode> listagg_set_function
8580+
listagg_set_function
8581+
: listagg_function '(' quantifier_opt value delimiter_opt listagg_overflow_clause_opt ')'
8582+
within_group_specification_opt
8583+
{
8584+
$$ = newNode<ListAggNode>($3, $4, $5, $8);
8585+
}
8586+
;
8587+
8588+
%type <metaNamePtr> listagg_function
8589+
listagg_function
8590+
: LIST
8591+
| LISTAGG
8592+
;
8593+
8594+
%type <boolVal> quantifier_opt
8595+
quantifier_opt
8596+
: all_noise { $$ = false; }
8597+
| DISTINCT { $$ = true; }
8598+
;
8599+
8600+
%type <valueListNode> listagg_overflow_clause_opt
8601+
listagg_overflow_clause_opt
8602+
: /* nothing */ { $$ = newNode<ValueListNode>(0); }
8603+
| listagg_overflow_clause
8604+
;
8605+
8606+
%type <valueListNode> listagg_overflow_clause
8607+
listagg_overflow_clause
8608+
: ON OVERFLOW overflow_behavior { $$ = $3; }
8609+
8610+
%type <valueListNode> overflow_behavior
8611+
overflow_behavior
8612+
: ERROR
8613+
{
8614+
$$ = newNode<ValueListNode>(0);
8615+
}
8616+
| TRUNCATE listagg_truncation_filler_opt listagg_count_indication
8617+
{
8618+
$$ = newNode<ValueListNode>(0);
8619+
$$->add($2);
8620+
}
8621+
;
8622+
8623+
%type <valueExprNode> listagg_truncation_filler_opt
8624+
listagg_truncation_filler_opt
8625+
: /*nothing*/ { $$ = MAKE_str_constant(newIntlString("..."), lex.charSetId); }
8626+
| listagg_truncation_filler { $$ = $1; }
8627+
;
8628+
8629+
%type <valueExprNode>listagg_truncation_filler
8630+
listagg_truncation_filler
8631+
: sql_string
8632+
;
8633+
8634+
%type <boolVal> listagg_count_indication
8635+
listagg_count_indication
8636+
: WITH COUNT { $$ = true; }
8637+
| WITHOUT COUNT { $$ = false; }
8638+
;
8639+
8640+
%type <valueListNode> within_group_specification_opt
8641+
within_group_specification_opt
8642+
: /* nothing */ { $$ = newNode<ValueListNode>(0); }
8643+
| within_group_specification { $$ = $1; }
8644+
;
8645+
8646+
%type <valueListNode> within_group_specification
8647+
within_group_specification
8648+
: WITHIN GROUP '(' order_clause ')' { $$ = $4; }
8649+
;
8650+
85788651
%type <aggNode> window_function
85798652
window_function
85808653
: DENSE_RANK '(' ')'
@@ -9946,6 +10019,7 @@ non_reserved_word
994610019
| SEARCH_PATH
994710020
| SCHEMA
994810021
| UNLIST
10022+
| LISTAGG
994910023
;
995010024

995110025
%%

src/include/firebird/impl/msg/jrd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,3 +998,4 @@ FB_IMPL_MSG(JRD, 995, missing_value_for_format_pattern, -901, "HY", "000", "Cann
998998
FB_IMPL_MSG(JRD, 996, invalid_name, -901, "HY", "000", "Invalid name: @1")
999999
FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid list of unqualified names: @1")
10001000
FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored")
1001+
FB_IMPL_MSG(JRD, 999, distinct_order_by_err, -208, "42", "000", "Sort-key of the ORDER BY specification must match the argument list")

src/include/gen/Firebird.pas

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5839,6 +5839,7 @@ IProfilerStatsImpl = class(IProfilerStats)
58395839
isc_invalid_name = 335545316;
58405840
isc_invalid_unqualified_name_list = 335545317;
58415841
isc_no_user_att_while_restore = 335545318;
5842+
isc_distinct_order_by_err = 335545319;
58425843
isc_gfix_db_name = 335740929;
58435844
isc_gfix_invalid_sw = 335740930;
58445845
isc_gfix_incmp_sw = 335740932;

src/jrd/exe.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ class AggregateSort : protected Firebird::PermanentStorage, public Printable
131131
public:
132132
explicit AggregateSort(Firebird::MemoryPool& p)
133133
: PermanentStorage(p),
134-
keyItems(p)
134+
keyItems(p),
135+
descOrder(p)
135136
{
136137
}
137138

@@ -147,6 +148,7 @@ class AggregateSort : protected Firebird::PermanentStorage, public Printable
147148
bool intl = false;
148149
ULONG impure = 0;
149150
Firebird::HalfStaticArray<sort_key_def, 2> keyItems;
151+
Firebird::HalfStaticArray<dsc, 2> descOrder;
150152
};
151153

152154
// Inversion (i.e. nod_index) impure area

0 commit comments

Comments
 (0)