diff --git a/doc/sql.extensions/README.listagg b/doc/sql.extensions/README.listagg new file mode 100644 index 00000000000..03b432fbdd6 --- /dev/null +++ b/doc/sql.extensions/README.listagg @@ -0,0 +1,159 @@ +SQL Language Extension: LISTAGG + +Function: + The current implementation has an aggregate function LIST which concatenates multiple row + fields into a blob. The SQL standard has a similar function called LISTAGG. The major + difference is that it also supports the ordered concatenation. + +Authors: + Chudaykin Alex + +Format: + ::= + LISTAGG [ ] [ ] [ ] + + ::= + + + ::= + ON OVERFLOW + + ::= + ERROR | TRUNCATE [ ] + + ::= + + + ::= + WITH COUNT | WITHOUT COUNT + + ::= + WITHIN GROUP ORDER BY + +Syntax Rules: + The legacy LIST syntax is preserved for backward compatibility, LISTAGG is added to cover the + standard features. + + There is a rule in the standard, which is intended to output an error + when the output value overflows. Since the LIST function always returns a BLOB, it was decided + that this rule would be meaningless. So the OVERFLOW clause is syntactically supported but + silently ignored if specified. + +Examples: +CREATE TABLE TEST_T + (COL1 INT, COL2 VARCHAR(2), COL3 VARCHAR(2), COL4 VARCHAR(2), COL5 BOOLEAN, COL6 VARCHAR(2) + CHARACTER SET WIN1251); +COMMIT; +INSERT INTO TEST_T values(1, 'A', 'A', 'J', false, 'П'); +INSERT INTO TEST_T values(2, 'B', 'B', 'I', false, 'Д'); +INSERT INTO TEST_T values(3, 'C', 'A', 'L', true, 'Ж'); +INSERT INTO TEST_T values(4, 'D', 'B', 'K', true, 'Й'); +COMMIT; + +SELECT LISTAGG (ALL COL4, ':') AS FROM TEST_T; +======= +J:I:L:K + +SELECT LISTAGG (DISTINCT COL4, ':') FROM TEST_T; +======== +I:J:K:L + +SELECT LISTAGG (DISTINCT COL3, ':') FROM TEST_T; +==== +A:B + +SELECT LISTAGG (DISTINCT COL3, ':') WITHIN GROUP (ORDER BY COL3 ASCENDING) FROM TEST_T; +==== +A:B + +SELECT LISTAGG (DISTINCT COL3, ':') WITHIN GROUP (ORDER BY COL3 DESCENDING) FROM TEST_T; +==== +B:A + +SELECT LISTAGG (DISTINCT COL3, ':') WITHIN GROUP (ORDER BY COL3 DESCENDING, COL4, COL5) FROM TEST_T; +==== +B:A + +SELECT LISTAGG (DISTINCT COL3, ':') WITHIN GROUP (ORDER BY COL4, COL3 DESCENDING, COL5) FROM TEST_T; +==== +A:B + +SELECT LISTAGG (DISTINCT COL3, ':') WITHIN GROUP (ORDER BY COL2) FROM TEST_T; +==== +A:B + +SELECT LISTAGG (DISTINCT COL3, ':') WITHIN GROUP (ORDER BY COL2 DESCENDING) FROM TEST_T; +==== +A:B + +SELECT LISTAGG (COL2, ':') WITHIN GROUP (ORDER BY COL2 DESCENDING) FROM TEST_T; +======= +D:C:B:A + +SELECT LISTAGG (COL4, ':') WITHIN GROUP (ORDER BY COL3 DESC) FROM TEST_T; +======= +I:K:J:L + +SELECT LISTAGG (COL3, ':') WITHIN GROUP (ORDER BY COL5 ASCENDING) FROM TEST_T; +======= +A:B:A:B + +SELECT LISTAGG (COL4, ':') WITHIN GROUP (ORDER BY COL3 ASC) FROM TEST_T; +======= +J:L:I:K + +SELECT LISTAGG (ALL COL2) WITHIN GROUP (ORDER BY COL4) FROM TEST_T; +======= +B,A,D,C + +SELECT LISTAGG (COL2, ':') WITHIN GROUP (ORDER BY COL3 DESC, COL4 ASC) FROM TEST_T; +======= +B:D:A:C + +SELECT LISTAGG (COL2, ':') WITHIN GROUP (ORDER BY COL3 DESC, COL4 DESC) FROM TEST_T; +======= +D:B:C:A + +SELECT LISTAGG (COL2, ':') WITHIN GROUP (ORDER BY COL3 ASC, COL4 DESC) FROM TEST_T; +======= +C:A:D:B + +SELECT LISTAGG (ALL COL6, ':') FROM TEST_T; +======= +П:Д:Ж:Й + +SELECT LISTAGG (ALL COL6, ':') WITHIN GROUP (ORDER BY COL2 DESC) FROM TEST_T; +======= +Й:Ж:Д:П + +SELECT LISTAGG (ALL COL2, ':') WITHIN GROUP (ORDER BY COL6) FROM TEST_T; +======= +B:C:D:A + +SELECT LISTAGG (COL4, ':' ON OVERFLOW TRUNCATE '...' WITHOUT COUNT) WITHIN GROUP (ORDER BY COL3 ASC) FROM TEST_T; +======= +J:L:I:K + +SELECT LISTAGG (COL4, ':' ON OVERFLOW TRUNCATE '...' WITH COUNT) WITHIN GROUP (ORDER BY COL3 DESC) FROM TEST_T; +====== +I:K:J:L + +SELECT LISTAGG (DISTINCT COL3, ':' ON OVERFLOW ERROR) WITHIN GROUP (ORDER BY COL3) FROM TEST_T; +=== +A:B + +INSERT INTO TEST_T values(5, 'E', NULL, NULL, NULL, NULL); +INSERT INTO TEST_T values(6, 'F', 'C', 'N', true, 'К'); + +SELECT LISTAGG (ALL COL2, ':') WITHIN GROUP (ORDER BY COL3) FROM TEST_T; +=========== +E:A:C:B:D:F + +SELECT LISTAGG (ALL COL2, ':') WITHIN GROUP (ORDER BY COL3 NULLS LAST) FROM TEST_T; +=========== +A:C:B:D:F:E + +SELECT LISTAGG (ALL COL2, ':') WITHIN GROUP (ORDER BY COL6 NULLS FIRST) FROM TEST_T; +=========== +E:B:C:D:F:A + diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 8a44da58a38..2155a6e625c 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -205,6 +205,7 @@ PARSER_TOKEN(TOK_ENCRYPT, "ENCRYPT", true) PARSER_TOKEN(TOK_END, "END", false) PARSER_TOKEN(TOK_ENGINE, "ENGINE", true) PARSER_TOKEN(TOK_ENTRY_POINT, "ENTRY_POINT", true) +PARSER_TOKEN(TOK_ERROR, "ERROR", true) PARSER_TOKEN(TOK_ESCAPE, "ESCAPE", false) PARSER_TOKEN(TOK_EXCEPTION, "EXCEPTION", true) PARSER_TOKEN(TOK_EXCESS, "EXCESS", true) @@ -292,6 +293,7 @@ PARSER_TOKEN(TOK_LIKE, "LIKE", false) PARSER_TOKEN(TOK_LIMBO, "LIMBO", true) PARSER_TOKEN(TOK_LINGER, "LINGER", true) PARSER_TOKEN(TOK_LIST, "LIST", true) +PARSER_TOKEN(TOK_LISTAGG, "LISTAGG", false) PARSER_TOKEN(TOK_LN, "LN", true) PARSER_TOKEN(TOK_LATERAL, "LATERAL", false) PARSER_TOKEN(TOK_LOCAL, "LOCAL", false) @@ -522,6 +524,7 @@ PARSER_TOKEN(TOK_TRIGGER, "TRIGGER", false) PARSER_TOKEN(TOK_TRIM, "TRIM", false) PARSER_TOKEN(TOK_TRUE, "TRUE", false) PARSER_TOKEN(TOK_TRUNC, "TRUNC", true) +PARSER_TOKEN(TOK_TRUNCATE, "TRUNCATE", false) PARSER_TOKEN(TOK_TRUSTED, "TRUSTED", true) PARSER_TOKEN(TOK_TWO_PHASE, "TWO_PHASE", true) PARSER_TOKEN(TOK_TYPE, "TYPE", true) @@ -558,6 +561,7 @@ PARSER_TOKEN(TOK_WHERE, "WHERE", false) PARSER_TOKEN(TOK_WHILE, "WHILE", false) PARSER_TOKEN(TOK_WINDOW, "WINDOW", false) PARSER_TOKEN(TOK_WITH, "WITH", false) +PARSER_TOKEN(TOK_WITHIN, "WITHIN", false) PARSER_TOKEN(TOK_WITHOUT, "WITHOUT", false) PARSER_TOKEN(TOK_WORK, "WORK", true) PARSER_TOKEN(TOK_WRITE, "WRITE", true) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 0cf1f80488e..81f64170cf0 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -124,6 +124,7 @@ string AggNode::internalPrint(NodePrinter& printer) const NODE_PRINT(printer, dialect1); NODE_PRINT(printer, arg); NODE_PRINT(printer, asb); + NODE_PRINT(printer, sort); NODE_PRINT(printer, indexed); return aggInfo.name; @@ -352,6 +353,8 @@ AggNode* AggNode::pass2(thread_db* tdbb, CompilerScratch* csb) dsc desc; getDesc(tdbb, csb, &desc); impureOffset = csb->allocImpure(); + if (sort) + doPass2(tdbb, csb, sort.getAddress()); return this; } @@ -361,7 +364,7 @@ void AggNode::aggInit(thread_db* tdbb, Request* request) const impure_value_ex* impure = request->getImpure(impureOffset); impure->vlux_count = 0; - if (distinct) + if (distinct || sort) { // Initialize a sort to reject duplicate values. @@ -373,8 +376,8 @@ void AggNode::aggInit(thread_db* tdbb, Request* request) const asbImpure->iasb_sort = FB_NEW_POOL(request->req_sorts.getPool()) Sort( tdbb->getDatabase(), &request->req_sorts, asb->length, - asb->keyItems.getCount(), 1, asb->keyItems.begin(), - RecordSource::rejectDuplicate, 0); + asb->keyItems.getCount(), (distinct ? 1 : asb->keyItems.getCount()), + asb->keyItems.begin(), (distinct ? RecordSource::rejectDuplicate : nullptr), 0); } } @@ -427,6 +430,46 @@ bool AggNode::aggPass(thread_db* tdbb, Request* request) const ULONG* const pDummy = reinterpret_cast(data + asb->length - sizeof(ULONG)); *pDummy = asbImpure->iasb_dummy++; + return true; + } + else if (sort) + { + fb_assert(asb); + // "Put" the value to sort. + impure_agg_sort* asbImpure = request->getImpure(asb->impure); + UCHAR* data; + asbImpure->iasb_sort->put(tdbb, reinterpret_cast(&data)); + + MOVE_CLEAR(data, asb->length); + + auto descOrder = asb->descOrder.begin(); + auto keyItem = asb->keyItems.begin(); + + for (auto& nodeOrder : sort->expressions) + { + dsc toDesc = *(descOrder++); + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; + if (const auto fromDsc = EVL_expr(tdbb, request, nodeOrder)) + { + if (IS_INTL_DATA(fromDsc)) + { + INTL_string_to_key(tdbb, INTL_TEXT_TO_INDEX(fromDsc->getTextType()), + fromDsc, &toDesc, INTL_KEY_UNIQUE); + } + else + MOV_move(tdbb, fromDsc, &toDesc); + } + else + *(data + keyItem->getSkdOffset()) = TRUE; + + // The first key for NULLS FIRST/LAST, the second key for the sorter + keyItem += 2; + } + + dsc toDesc = asb->desc; + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; + MOV_move(tdbb, desc, &toDesc); + return true; } } @@ -455,7 +498,7 @@ dsc* AggNode::execute(thread_db* tdbb, Request* request) const impure->vlu_blob = NULL; } - if (distinct) + if (distinct || sort) { impure_agg_sort* asbImpure = request->getImpure(asb->impure); dsc desc = asb->desc; @@ -478,7 +521,10 @@ dsc* AggNode::execute(thread_db* tdbb, Request* request) const break; } - desc.dsc_address = data + (asb->intl ? asb->keyItems[1].getSkdOffset() : 0); + if (distinct) + desc.dsc_address = data + (asb->intl ? asb->keyItems[1].getSkdOffset() : 0); + else + desc.dsc_address = data + (IPTR) asb->desc.dsc_address; aggPass(tdbb, request, &desc); } @@ -877,21 +923,38 @@ AggNode* AvgAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ static AggNode::Register listAggInfo("LIST", blr_agg_list, blr_agg_list_distinct); ListAggNode::ListAggNode(MemoryPool& pool, bool aDistinct, ValueExprNode* aArg, - ValueExprNode* aDelimiter) + ValueExprNode* aDelimiter, ValueListNode* aOrderClause) : AggNode(pool, listAggInfo, aDistinct, false, aArg), - delimiter(aDelimiter) + delimiter(aDelimiter), + dsqlOrderClause(aOrderClause) { } DmlNode* ListAggNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) { - ListAggNode* node = FB_NEW_POOL(pool) ListAggNode(pool, - (blrOp == blr_agg_list_distinct)); + ListAggNode* node = FB_NEW_POOL(pool) ListAggNode(pool, (blrOp == blr_agg_list_distinct)); node->arg = PAR_parse_value(tdbb, csb); node->delimiter = PAR_parse_value(tdbb, csb); + if (csb->csb_blr_reader.peekByte() == blr_sort) + node->sort = PAR_sort(tdbb, csb, blr_sort, true); + return node; } +bool ListAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const +{ + if (!AggNode::dsqlMatch(dsqlScratch, other, ignoreMapCast)) + return false; + + const ListAggNode* o = nodeAs(other); + fb_assert(o); + + if (dsqlOrderClause || o->dsqlOrderClause) + return PASS1_node_match(dsqlScratch, dsqlOrderClause, o->dsqlOrderClause, ignoreMapCast); + + return true; +} + void ListAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) { DsqlDescMaker::fromNode(dsqlScratch, desc, arg); @@ -899,6 +962,13 @@ void ListAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) desc->setNullable(true); } +void ListAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + AggNode::genBlr(dsqlScratch); + if (dsqlOrderClause) + GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); +} + bool ListAggNode::setParameterType(DsqlCompilerScratch* dsqlScratch, std::function makeDesc, bool forceVarChar) { @@ -920,6 +990,7 @@ ValueExprNode* ListAggNode::copy(thread_db* tdbb, NodeCopier& copier) const node->nodScale = nodScale; node->arg = copier.copy(tdbb, arg); node->delimiter = copier.copy(tdbb, delimiter); + node->sort = sort->copy(tdbb, copier); return node; } @@ -985,7 +1056,7 @@ dsc* ListAggNode::aggExecute(thread_db* tdbb, Request* request) const { impure_value_ex* impure = request->getImpure(impureOffset); - if (distinct) + if (distinct || sort) { if (impure->vlu_blob) { @@ -1005,7 +1076,8 @@ AggNode* ListAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ thread_db* tdbb = JRD_get_thread_data(); AggNode* node = FB_NEW_POOL(dsqlScratch->getPool()) ListAggNode(dsqlScratch->getPool(), distinct, - doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, delimiter)); + doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, delimiter), + doDsqlPass(dsqlScratch, dsqlOrderClause)); dsc argDesc; node->arg->make(dsqlScratch, &argDesc); diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index 3f07c4b878d..20b0fd94971 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -95,8 +95,8 @@ class AvgAggNode final : public AggNode class ListAggNode final : public AggNode { public: - explicit ListAggNode(MemoryPool& pool, bool aDistinct, ValueExprNode* aArg = NULL, - ValueExprNode* aDelimiter = NULL); + explicit ListAggNode(MemoryPool& pool, bool aDistinct, ValueExprNode* aArg = nullptr, + ValueExprNode* aDelimiter = nullptr, ValueListNode* aOrderClause = nullptr); static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); @@ -111,8 +111,12 @@ class ListAggNode final : public AggNode holder.add(delimiter); } + bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const override; + Firebird::string internalPrint(NodePrinter& printer) const override; void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; + void genBlr(DsqlCompilerScratch* dsqlScratch) override; + bool setParameterType(DsqlCompilerScratch* dsqlScratch, std::function makeDesc, bool forceVarChar) override; void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; @@ -127,6 +131,7 @@ class ListAggNode final : public AggNode private: NestConst delimiter; + NestConst dsqlOrderClause; }; class CountAggNode final : public AggNode diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index b26f2015980..3f824a22267 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -49,6 +49,7 @@ class RseNode; class SlidingWindow; class TypeClause; class ValueExprNode; +class SortNode; // Must be less then MAX_SSHORT. Not used for static arrays. @@ -1113,6 +1114,7 @@ class AggNode : public TypedNode const AggInfo& aggInfo; NestConst arg; const AggregateSort* asb; + NestConst sort; bool distinct; bool dialect1; bool indexed; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index c2b34b66216..a31c7e3d4aa 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -718,6 +718,8 @@ using namespace Firebird; %token SCHEMA %token SEARCH_PATH %token UNLIST +%token LISTAGG +%token WITHIN // precedence declarations for expression evaluation @@ -4720,6 +4722,9 @@ keyword_or_column | RTRIM | GREATEST | LEAST + | WITHIN + | LISTAGG + | TRUNCATE ; col_opt @@ -8538,10 +8543,8 @@ aggregate_function_prefix { $$ = newNode(MaxMinAggNode::TYPE_MAX, $4); } | MAXIMUM '(' DISTINCT value ')' { $$ = newNode(MaxMinAggNode::TYPE_MAX, $4); } - | LIST '(' all_noise value delimiter_opt ')' - { $$ = newNode(false, $4, $5); } - | LIST '(' DISTINCT value delimiter_opt ')' - { $$ = newNode(true, $4, $5); } + | listagg_set_function + { $$ = $1; } | STDDEV_SAMP '(' value ')' { $$ = newNode(StdDevAggNode::TYPE_STDDEV_SAMP, $3); } | STDDEV_POP '(' value ')' @@ -8586,6 +8589,78 @@ aggregate_function_prefix { $$ = newNode(BinAggNode::TYPE_BIN_XOR_DISTINCT, $4); } ; +%type listagg_set_function +listagg_set_function + : listagg_function '(' quantifier_opt value delimiter_opt listagg_overflow_clause_opt ')' + within_group_specification_opt + { + $$ = newNode($3, $4, $5, $8); + } + ; + +%type listagg_function +listagg_function + : LIST + | LISTAGG + ; + +%type quantifier_opt +quantifier_opt + : all_noise { $$ = false; } + | DISTINCT { $$ = true; } + ; + +%type listagg_overflow_clause_opt +listagg_overflow_clause_opt + : /* nothing */ { $$ = newNode(0); } + | listagg_overflow_clause + ; + +%type listagg_overflow_clause +listagg_overflow_clause + : ON OVERFLOW overflow_behavior { $$ = $3; } + +%type overflow_behavior +overflow_behavior + : ERROR + { + $$ = newNode(0); + } + | TRUNCATE listagg_truncation_filler_opt listagg_count_indication + { + $$ = newNode(0); + $$->add($2); + } + ; + +%type listagg_truncation_filler_opt +listagg_truncation_filler_opt + : /*nothing*/ { $$ = MAKE_str_constant(newIntlString("..."), lex.charSetId); } + | listagg_truncation_filler { $$ = $1; } + ; + +%type listagg_truncation_filler +listagg_truncation_filler + : sql_string + ; + +%type listagg_count_indication +listagg_count_indication + : WITH COUNT { $$ = true; } + | WITHOUT COUNT { $$ = false; } + ; + +%type within_group_specification_opt +within_group_specification_opt + : /* nothing */ { $$ = newNode(0); } + | within_group_specification { $$ = $1; } + ; + +%type within_group_specification +within_group_specification + : WITHIN GROUP '(' order_clause ')' { $$ = $4; } + ; + %type window_function window_function : DENSE_RANK '(' ')' @@ -9960,6 +10035,7 @@ non_reserved_word | SEARCH_PATH | SCHEMA | UNLIST + | ERROR ; %% diff --git a/src/jrd/exe.h b/src/jrd/exe.h index 018015616aa..07cf11b7403 100644 --- a/src/jrd/exe.h +++ b/src/jrd/exe.h @@ -131,7 +131,8 @@ class AggregateSort : protected Firebird::PermanentStorage, public Printable public: explicit AggregateSort(Firebird::MemoryPool& p) : PermanentStorage(p), - keyItems(p) + keyItems(p), + descOrder(p) { } @@ -147,6 +148,7 @@ class AggregateSort : protected Firebird::PermanentStorage, public Printable bool intl = false; ULONG impure = 0; Firebird::HalfStaticArray keyItems; + Firebird::HalfStaticArray descOrder; }; // Inversion (i.e. nod_index) impure area diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index 7d6cf5d02f8..4f7c49e1210 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1378,13 +1378,22 @@ void Optimizer::generateAggregateDistincts(MapNode* map) sort_key_def* sort_key = asb->keyItems.getBuffer(asb->intl ? 2 : 1); sort_key->setSkdOffset(); + UCHAR direction = SKD_ascending; + if (aggNode->sort) + { + ValueExprNode* const node = aggNode->sort->expressions.front(); + const SortDirection sortDir = aggNode->sort->direction.front(); + if (aggNode->arg->sameAs(node, false) && sortDir == ORDER_DESC) + direction = SKD_descending; + } + if (asb->intl) { const USHORT key_length = ROUNDUP(INTL_key_length(tdbb, INTL_TEXT_TO_INDEX(desc->getTextType()), desc->getStringLength()), sizeof(SINT64)); sort_key->setSkdLength(SKD_bytes, key_length); - sort_key->skd_flags = SKD_ascending; + sort_key->skd_flags = direction; sort_key->skd_vary_offset = 0; ++sort_key; @@ -1412,15 +1421,128 @@ void Optimizer::generateAggregateDistincts(MapNode* map) // see AggNode::aggPass() for details; the length remains rounded properly asb->length += sizeof(ULONG); - sort_key->skd_flags = SKD_ascending; + sort_key->skd_flags = direction; asb->impure = csb->allocImpure(); asb->desc = *desc; aggNode->asb = asb; } + else if (aggNode && aggNode->sort) + { + generateAggregateSort(aggNode); + continue; + } } } +void Optimizer::generateAggregateSort(AggNode* aggNode) +{ + dsc descriptor; + dsc* desc = &descriptor; + + const auto asb = FB_NEW_POOL(getPool()) AggregateSort(getPool()); + + sort_key_def* prevKey = nullptr; + const auto keyCount = aggNode->sort->expressions.getCount() * 2; + sort_key_def* sortKey = asb->keyItems.getBuffer(keyCount); + + const auto* direction = aggNode->sort->direction.begin(); + const auto* nullOrder = aggNode->sort->nullOrder.begin(); + + for (auto& node : aggNode->sort->expressions) + { + node->getDesc(tdbb, csb, desc); + + // Allow for "key" forms of International text to grow + if (IS_INTL_DATA(desc)) + { + // Turn varying text and cstrings into text. + if (desc->dsc_dtype == dtype_varying) + { + desc->dsc_dtype = dtype_text; + desc->dsc_length -= sizeof(USHORT); + } + else if (desc->dsc_dtype == dtype_cstring) + { + desc->dsc_dtype = dtype_text; + desc->dsc_length--; + } + desc->dsc_length = INTL_key_length(tdbb, INTL_INDEX_TYPE(desc), desc->dsc_length); + } + + // Make key for null flag + sortKey->setSkdLength(SKD_text, 1); + sortKey->setSkdOffset(prevKey); + + // Handle nulls placement + sortKey->skd_flags = SKD_ascending; + + // Have SQL-compliant nulls ordering for ODS11+ + if ((*nullOrder == NULLS_DEFAULT && *direction != ORDER_DESC) || *nullOrder == NULLS_FIRST) + sortKey->skd_flags |= SKD_descending; + + prevKey = sortKey++; + + // Make key for sort key proper + fb_assert(desc->dsc_dtype < FB_NELEM(sort_dtypes)); + sortKey->setSkdLength(sort_dtypes[desc->dsc_dtype], desc->dsc_length); + sortKey->setSkdOffset(prevKey, desc); + + sortKey->skd_flags = SKD_ascending; + if (*direction == ORDER_DESC) + sortKey->skd_flags |= SKD_descending; + + if (!sortKey->skd_dtype) + { + ERR_post(Arg::Gds(isc_invalid_sort_datatype) + << Arg::Str(DSC_dtype_tostring(desc->dsc_dtype))); + } + + if (sortKey->skd_dtype == SKD_varying || sortKey->skd_dtype == SKD_cstring) + { + if (desc->dsc_ttype() == ttype_binary) + sortKey->skd_flags |= SKD_binary; + } + + if (desc->dsc_dtype == dtype_varying) + { + // allocate space to store varying length + sortKey->skd_vary_offset = sortKey->getSkdOffset() + ROUNDUP(desc->dsc_length, sizeof(SLONG)); + sortKey->setSkdLength(sort_dtypes[desc->dsc_dtype], sortKey->skd_vary_offset + sizeof(USHORT)); + } + else + sortKey->skd_vary_offset = 0; + + desc->dsc_address = (UCHAR*)(IPTR) sortKey->getSkdOffset(); + asb->descOrder.add(*desc); + + prevKey = sortKey++; + direction++; + nullOrder++; + } + + fb_assert(prevKey); + ULONG length = prevKey ? ROUNDUP(prevKey->getSkdOffset() + prevKey->getSkdLength(), sizeof(SLONG)) : 0; + + aggNode->arg->getDesc(tdbb, csb, desc); + + if (desc->dsc_dtype >= dtype_aligned) + length = FB_ALIGN(length, type_alignments[desc->dsc_dtype]); + + if (desc->dsc_dtype == dtype_varying) + length += sizeof(USHORT); + + desc->dsc_address = (UCHAR*)(IPTR)length; + length += desc->dsc_length; + + asb->desc = *desc; + + asb->length = ROUNDUP(length, sizeof(SLONG)); + + asb->impure = csb->allocImpure(); + aggNode->asb = asb; +} + // // Generate a record source block to handle either a sort or a project. diff --git a/src/jrd/optimizer/Optimizer.h b/src/jrd/optimizer/Optimizer.h index 9e6292f6918..a8058b5192e 100644 --- a/src/jrd/optimizer/Optimizer.h +++ b/src/jrd/optimizer/Optimizer.h @@ -478,6 +478,7 @@ class Optimizer final : public Firebird::PermanentStorage void compileRelation(StreamType stream); unsigned decomposeBoolean(BoolExprNode* boolNode, BoolExprNodeStack& stack); void generateAggregateDistincts(MapNode* map); + void generateAggregateSort(AggNode* aggNode); RecordSource* generateRetrieval(StreamType stream, SortNode** sortClause, bool outerFlag,