diff --git a/doc/sql.extensions/README.listagg b/doc/sql.extensions/README.listagg new file mode 100644 index 00000000000..968f0617bd6 --- /dev/null +++ b/doc/sql.extensions/README.listagg @@ -0,0 +1,144 @@ +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. It was not implemented and silently ignored if specified. + + If DISTINCT is specified for LISTAGG, then ORDER BY must fully match + + +Notes: + If DISTINCT is specified, the presence of WITHIN GROUP must obey the restriction and will not + affect the subsequent code execution. + +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 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 + +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 + +SELECT LISTAGG (DISTINCT COL3, ':') WITHIN GROUP (ORDER BY COL2) FROM TEST_T; +======== +Statement failed, SQLSTATE = 42000 +SQL error code = -104 +-Invalid command +-Sort-key of the ORDER BY specification must match the argument list + diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 3abce408f92..6914db3765f 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -289,6 +289,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", true) PARSER_TOKEN(TOK_LN, "LN", true) PARSER_TOKEN(TOK_LATERAL, "LATERAL", false) PARSER_TOKEN(TOK_LOCAL, "LOCAL", false) @@ -555,6 +556,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 b2674157c9d..3ba588642ae 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; @@ -307,7 +308,7 @@ bool AggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, // ASF: We compare name address. That should be ok, as we have only one AggInfo instance // per function. return aggInfo.blr == o->aggInfo.blr && aggInfo.name == o->aggInfo.name && - distinct == o->distinct && dialect1 == o->dialect1; + distinct == o->distinct && dialect1 == o->dialect1 && sort == o->sort;; } void AggNode::setParameterName(dsql_par* parameter) const @@ -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,44 @@ 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 +496,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 +519,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,18 +921,19 @@ 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); + node->sort = PAR_sort(tdbb, csb, blr_sort, true); return node; } @@ -899,6 +944,26 @@ void ListAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) desc->setNullable(true); } +void ListAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + AggNode::genBlr(dsqlScratch); + GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); +} + +AggNode* ListAggNode::pass1(thread_db* tdbb, CompilerScratch* csb) +{ + if (sort && distinct) + { + ValueExprNode* const sortNode = *sort->expressions.begin(); + if (!arg->sameAs(sortNode, false) || sort->expressions.getCount() > 1) + { + ERR_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_command_err) + << Arg::Gds(isc_distinct_order_by_err)); + } + } + return AggNode::pass1(tdbb, csb); +} + bool ListAggNode::setParameterType(DsqlCompilerScratch* dsqlScratch, std::function makeDesc, bool forceVarChar) { @@ -920,6 +985,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 +1051,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 +1071,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 861b1e6c377..9d5dc1418d4 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); @@ -113,6 +113,8 @@ class ListAggNode final : public AggNode virtual Firebird::string internalPrint(NodePrinter& printer) const; virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc); + virtual void genBlr(DsqlCompilerScratch* dsqlScratch) final; + virtual AggNode* pass1(thread_db* tdbb, CompilerScratch* csb) final; virtual bool setParameterType(DsqlCompilerScratch* dsqlScratch, std::function makeDesc, bool forceVarChar); virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc); @@ -127,6 +129,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 f0e02769c34..ca5a1b34b85 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -715,6 +715,8 @@ using namespace Firebird; %token SCHEMA %token SEARCH_PATH %token UNLIST +%token LISTAGG +%token WITHIN // precedence declarations for expression evaluation @@ -4706,6 +4708,7 @@ keyword_or_column | RTRIM | GREATEST | LEAST + | WITHIN ; col_opt @@ -8535,10 +8538,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 ')' @@ -8575,6 +8576,78 @@ aggregate_function_prefix { $$ = newNode($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 '(' ')' @@ -9946,6 +10019,7 @@ non_reserved_word | SEARCH_PATH | SCHEMA | UNLIST + | LISTAGG ; %% diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index b693a3b83a2..c97a580e3ab 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -998,3 +998,4 @@ FB_IMPL_MSG(JRD, 995, missing_value_for_format_pattern, -901, "HY", "000", "Cann FB_IMPL_MSG(JRD, 996, invalid_name, -901, "HY", "000", "Invalid name: @1") FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid list of unqualified names: @1") FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored") +FB_IMPL_MSG(JRD, 999, distinct_order_by_err, -208, "42", "000", "Sort-key of the ORDER BY specification must match the argument list") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 7e6e69de77f..9b7449ee4c4 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5839,6 +5839,7 @@ IProfilerStatsImpl = class(IProfilerStats) isc_invalid_name = 335545316; isc_invalid_unqualified_name_list = 335545317; isc_no_user_att_while_restore = 335545318; + isc_distinct_order_by_err = 335545319; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; 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 8ab77f2373f..57144ba26db 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1418,7 +1418,120 @@ void Optimizer::generateAggregateDistincts(MapNode* map) 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); + + auto const* direction = aggNode->sort->direction.begin(); + auto const* 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; } diff --git a/src/jrd/optimizer/Optimizer.h b/src/jrd/optimizer/Optimizer.h index a4d415ae4f9..e6c7c9c5460 100644 --- a/src/jrd/optimizer/Optimizer.h +++ b/src/jrd/optimizer/Optimizer.h @@ -478,6 +478,7 @@ class Optimizer : 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,