From 398a9f5f42573a44e87d60c3f8178c474bce7de8 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Thu, 20 Nov 2025 19:54:15 +0300 Subject: [PATCH 01/71] Percentile functions --- .../README.percentile_disc_cont.md | 195 +++++++++++ src/common/ParserTokens.h | 2 + src/dsql/AggNodes.cpp | 331 ++++++++++++++++++ src/dsql/AggNodes.h | 46 +++ src/dsql/Nodes.h | 2 + src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 17 + src/include/firebird/impl/msg/jrd.h | 2 + src/include/gen/Firebird.pas | 2 + src/jrd/optimizer/Optimizer.cpp | 2 +- 10 files changed, 599 insertions(+), 2 deletions(-) create mode 100644 doc/sql.extensions/README.percentile_disc_cont.md diff --git a/doc/sql.extensions/README.percentile_disc_cont.md b/doc/sql.extensions/README.percentile_disc_cont.md new file mode 100644 index 00000000000..d07044e2ec2 --- /dev/null +++ b/doc/sql.extensions/README.percentile_disc_cont.md @@ -0,0 +1,195 @@ +# PERCENTILE_DISC and PERCENTILE_CONT functions + +The `PERCENTILE_CONT` and `PERCENTILE_DISC` functions are known as inverse distribution functions. +These functions operate on an ordered set. Both functions can be used as aggregate or window functions. + +## PERCENTILE_DISC + +`PERCENTILE_DISC` is an inverse distribution function that assumes a discrete distribution model. +It takes a percentile value and a sort specification and returns an element from the set. +Nulls are ignored in the calculation. + +Syntax for the `PERCENTILE_DISC` function as an aggregate function. + +``` +PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) + + ::= numeric_literal | | +``` + +Syntax for the `PERCENTILE_DISC` function as an window function. + +``` +PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) + OVER (PARTITION BY ) + + ::= numeric_literal | | +``` + +The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. +This expression must be constant within each aggregate group. +The `ORDER BY` clause takes a single expression that can be of any type that can be sorted. + +The function `PERCENTILE_DISC` returns a value of the same type as the argument in `ORDER BY`. + +For a given percentile value `P`, `PERCENTILE_DISC` sorts the values of the expression in the `ORDER BY` clause and +returns the value with the smallest `CUME_DIST` value (with respect to the same sort specification) +that is greater than or equal to `P`. + +### Analytic Example + +```sql +SELECT + DEPT_NO, + SALARY, + CUME_DIST() OVER(PARTITION BY DEPT_NO ORDER BY SALARY) AS "CUME_DIST", + PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SALARY) + OVER(PARTITION BY DEPT_NO) AS MEDIAN_DISC +FROM EMPLOYEE +WHERE DEPT_NO < 600 +ORDER BY 1, 2; +``` + +``` +DEPT_NO SALARY CUME_DIST MEDIAN_DISC +======= ===================== ======================= ===================== +000 53793.00 0.5000000000000000 53793.00 +000 212850.00 1.000000000000000 53793.00 +100 44000.00 0.5000000000000000 44000.00 +100 111262.50 1.000000000000000 44000.00 +110 61637.81 0.5000000000000000 61637.81 +110 68805.00 1.000000000000000 61637.81 +115 6000000.00 0.5000000000000000 6000000.00 +115 7480000.00 1.000000000000000 6000000.00 +120 22935.00 0.3333333333333333 33620.63 +120 33620.63 0.6666666666666666 33620.63 +120 39224.06 1.000000000000000 33620.63 +121 110000.00 1.000000000000000 110000.00 +123 38500.00 1.000000000000000 38500.00 +125 33000.00 1.000000000000000 33000.00 +130 86292.94 0.5000000000000000 86292.94 +130 102750.00 1.000000000000000 86292.94 +140 100914.00 1.000000000000000 100914.00 +180 42742.50 0.5000000000000000 42742.50 +180 64635.00 1.000000000000000 42742.50 +``` + +## PERCENTILE_CONT + +`PERCENTILE_CONT` is an inverse distribution function that assumes a continuous distribution model. +It takes a percentile value and a sort specification and returns an element from the set. +Nulls are ignored in the calculation. + +Syntax for the `PERCENTILE_CONT` function as an aggregate function. + +``` +PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) + + ::= numeric_literal | | +``` + +Syntax for the `PERCENTILE_CONT` function as an window function. + +``` +PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) + OVER (PARTITION BY ) + + ::= numeric_literal | | +``` + +The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. +This expression must be constant within each aggregate group. +The `ORDER BY` clause takes a single expression, which must be of numeric type to perform interpolation. + +The `PERCENTILE_CONT` function returns a value of type `DOUBLE PRECISION` or `DECFLOAT(34)` depending on the type +of the argument in the `ORDER BY` clause. A value of type `DECFLOAT(34)` is returned if `ORDER BY` contains +an expression of one of the types `INT128`, `NUMERIC(38, x)` or `DECFLOAT(16 | 34)`, otherwise - `DOUBLE PRECISION`. + +The result of `PERCENTILE_CONT` is computed by linear interpolation between values after ordering them. +Using the percentile value (`P`) and the number of rows (`N`) in the aggregation group, you can compute +the row number you are interested in after ordering the rows with respect to the sort specification. +This row number (`RN`) is computed according to the formula `RN = (1 + (P * (N - 1))`. +The final result of the aggregate function is computed by linear interpolation between the values from rows +at row numbers `CRN = CEILING(RN)` and `FRN = FLOOR(RN)`. + +``` +function f(N) ::= value of expression from row at N + +if (CRN = FRN = RN) then + return f(RN) +else + return (CRN - RN) * f(FRN) + (RN - FRN) * f(CRN) +``` + +### Analytic Example + +```sql +SELECT + DEPT_NO, + SALARY, + PERCENT_RANK() OVER(PARTITION BY DEPT_NO ORDER BY SALARY) AS "PERCENT_RANK", + PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY SALARY) + OVER(PARTITION BY DEPT_NO) AS MEDIAN_CONT +FROM EMPLOYEE +WHERE DEPT_NO < 600 +ORDER BY 1, 2; +``` + +``` +DEPT_NO SALARY PERCENT_RANK MEDIAN_CONT +======= ===================== ======================= ======================= +000 53793.00 0.000000000000000 133321.5000000000 +000 212850.00 1.000000000000000 133321.5000000000 +100 44000.00 0.000000000000000 77631.25000000000 +100 111262.50 1.000000000000000 77631.25000000000 +110 61637.81 0.000000000000000 65221.40500000000 +110 68805.00 1.000000000000000 65221.40500000000 +115 6000000.00 0.000000000000000 6740000.000000000 +115 7480000.00 1.000000000000000 6740000.000000000 +120 22935.00 0.000000000000000 33620.63000000000 +120 33620.63 0.5000000000000000 33620.63000000000 +120 39224.06 0.2500000000000000 33620.63000000000 +121 110000.00 0.000000000000000 110000.0000000000 +123 38500.00 0.000000000000000 38500.00000000000 +125 33000.00 0.000000000000000 33000.00000000000 +130 86292.94 0.000000000000000 94521.47000000000 +130 102750.00 1.000000000000000 94521.47000000000 +140 100914.00 0.000000000000000 100914.0000000000 +180 42742.50 0.000000000000000 53688.75000000000 +180 64635.00 1.000000000000000 53688.75000000000 +``` + +## An example of using both aggregate functions + +```sql +SELECT + DEPT_NO, + PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY SALARY) AS MEDIAN_CONT, + PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SALARY) AS MEDIAN_DISC +FROM EMPLOYEE +GROUP BY DEPT_NO; +``` + +``` +DEPT_NO MEDIAN_CONT MEDIAN_DISC +======= ======================= ===================== +000 133321.5000000000 53793.00 +100 77631.25000000000 44000.00 +110 65221.40500000000 61637.81 +115 6740000.000000000 6000000.00 +120 33620.63000000000 33620.63 +121 110000.0000000000 110000.00 +123 38500.00000000000 38500.00 +125 33000.00000000000 33000.00 +130 94521.47000000000 86292.94 +140 100914.0000000000 100914.00 +180 53688.75000000000 42742.50 +600 66450.00000000000 27000.00 +621 71619.75000000000 62550.00 +622 53167.50000000000 53167.50 +623 60000.00000000000 60000.00 +670 71268.75000000000 31275.00 +671 81810.19000000000 81810.19 +672 45647.50000000000 35000.00 +900 92791.31500000000 69482.63 +``` diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 588252b429c..4440ccf55ff 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -373,6 +373,8 @@ PARSER_TOKEN(TOK_PARAMETER, "PARAMETER", false) PARSER_TOKEN(TOK_PARTITION, "PARTITION", true) PARSER_TOKEN(TOK_PASSWORD, "PASSWORD", true) PARSER_TOKEN(TOK_PERCENT_RANK, "PERCENT_RANK", true) +PARSER_TOKEN(TOK_PERCENTILE_CONT, "PERCENTILE_CONT", true) +PARSER_TOKEN(TOK_PERCENTILE_DISC, "PERCENTILE_DISC", true) PARSER_TOKEN(TOK_PI, "PI", true) PARSER_TOKEN(TOK_PKCS_1_5, "PKCS_1_5", true) PARSER_TOKEN(TOK_PLACING, "PLACING", true) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 4d86f3c82d7..681d62f1391 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -359,6 +359,11 @@ AggNode* AggNode::pass2(thread_db* tdbb, CompilerScratch* csb) return this; } +void AggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + arg->getDesc(tdbb, csb, desc); +} + void AggNode::aggInit(thread_db* tdbb, Request* request) const { impure_value_ex* impure = request->getImpure(impureOffset); @@ -1094,6 +1099,332 @@ AggNode* ListAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ return node; } +//-------------------- + + +static AggNode::RegisterFactory1 percentileContAggInfo( + "PERCENTILE_CONT", PercentileAggNode::TYPE_PERCENTILE_CONT); +static AggNode::RegisterFactory1 percentileDiscAggInfo( + "PERCENTILE_DISC", PercentileAggNode::TYPE_PERCENTILE_DISC); + +PercentileAggNode::PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg, + ValueListNode* aOrderClause) + : AggNode(pool, + (aType == PercentileAggNode::TYPE_PERCENTILE_CONT ? percentileContAggInfo : percentileDiscAggInfo), + false, false, aArg), + type(aType), + valueArg(nullptr), + dsqlOrderClause(aOrderClause) +{ + if (dsqlOrderClause) + valueArg = nodeAs(dsqlOrderClause->items[0])->value; +} + +void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned /*count*/) +{ + arg = PAR_parse_value(tdbb, csb); + if (csb->csb_blr_reader.peekByte() == blr_sort) + { + sort = PAR_sort(tdbb, csb, blr_sort, true); + valueArg = sort->expressions[0]; + } +} + +bool PercentileAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const +{ + if (!AggNode::dsqlMatch(dsqlScratch, other, ignoreMapCast)) + return false; + + const PercentileAggNode* o = nodeAs(other); + fb_assert(o); + return PASS1_node_match(dsqlScratch, dsqlOrderClause, o->dsqlOrderClause, ignoreMapCast); +} + +void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) +{ + fb_assert(dsqlOrderClause); + if (dsqlOrderClause->items.getCount() != 1) + { + ERR_post(Arg::Gds(isc_wronumarg)); // WITHIN GROUP should only 1 ORDER item + } + + + if (type == TYPE_PERCENTILE_DISC) + { + // same type as order by argument + DsqlDescMaker::fromNode(dsqlScratch, desc, valueArg, true); + } + else + { + DsqlDescMaker::fromNode(dsqlScratch, desc, valueArg, true); + if (desc->isDecOrInt128()) + { + desc->makeDecimal128(); + desc->setNullable(true); + } + else + { + desc->makeDouble(); + desc->setNullable(true); + } + } +} + +void PercentileAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + AggNode::genBlr(dsqlScratch); + GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); +} + +void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + valueArg->getDesc(tdbb, csb, desc); +} + +void PercentileAggNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + arg->getDesc(tdbb, csb, desc); + desc->makeDouble(); +} + +ValueExprNode* PercentileAggNode::copy(thread_db* tdbb, NodeCopier& copier) const +{ + PercentileAggNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) PercentileAggNode(*tdbb->getDefaultPool(), type); + + node->nodScale = nodScale; + node->arg = copier.copy(tdbb, arg); + node->valueArg = copier.copy(tdbb, valueArg); + node->sort = sort->copy(tdbb, copier); + + return node; +} + +AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) +{ + AggNode::pass2(tdbb, csb); + + // We need a second descriptor in the impure area for PERCENTILE. + impure2Offset = csb->allocImpure(); + + return this; +} + +string PercentileAggNode::internalPrint(NodePrinter& printer) const +{ + AggNode::internalPrint(printer); + + NODE_PRINT(printer, type); + + return "PercentileAggNode"; +} + + +void PercentileAggNode::aggInit(thread_db* tdbb, Request* request) const +{ + AggNode::aggInit(tdbb, request); + + impure_value_ex* impure = request->getImpure(impureOffset); + impure->vlu_desc.dsc_dtype = 0; + impure->vlux_count = 0; + + impure_value_ex* impure2 = request->getImpure(impure2Offset); + impure2->vlu_desc.dsc_dtype = 0; + impure2->vlux_count = 0; +} + +bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const +{ + dsc* percenteDesc = nullptr; + percenteDesc = EVL_expr(tdbb, request, arg); + if (!percenteDesc) + return false; + + dsc* desc = nullptr; + desc = EVL_expr(tdbb, request, valueArg); + if (!desc) + return false; + + if (desc->isNull()) + return false; + + impure_value_ex* impure = request->getImpure(impureOffset); + if (impure->vlux_count++ == 0) // first call to aggPass() + { + if ((type == TYPE_PERCENTILE_CONT) && !desc->isNumeric()) + ERRD_post(Arg::Gds(isc_argmustbe_numeric_function) << Arg::Str("PERCENTILE_CONT")); + + if (desc->isBlob()) + ERRD_post(Arg::Gds(isc_blobnotsup) << Arg::Str("ORDER BY")); + + const auto percentile_value = MOV_get_double(tdbb, percenteDesc); + if ((percentile_value < 0) || (percentile_value > 1)) + { + if (type == TYPE_PERCENTILE_DISC) + ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_DISC")); + else + ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_CONT")); + } + + impure->vlu_misc.vlu_double = percentile_value; + } + + 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; + } + + return true; +} + +void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) const +{ + impure_value_ex* impure = request->getImpure(impureOffset); + impure_value_ex* impure2 = request->getImpure(impure2Offset); + + if (type == TYPE_PERCENTILE_DISC) + { + // TODO: calculate only ones + const double rn = impure->vlu_misc.vlu_double * impure->vlux_count; + const auto crn = static_cast(ceil(rn)); + + impure2->vlux_count++; + + if (impure2->vlux_count == crn) + { + EVL_make_value(tdbb, desc, impure2); + } + } + else { + // TODO: calculate only ones + const double rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); + const auto crn = static_cast(ceil(rn)); + const auto frn = static_cast(floor(rn)); + + if (impure2->vlux_count++ == 0) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + Firebird::Decimal128 d128; + d128.set(0, decSt, 0); + impure2->make_decimal128(d128); + } + else + impure2->make_double(0); + } + + if (crn == frn) + { + if (impure2->vlux_count == frn) + { + if (desc->isDecOrInt128()) + { + const auto value = MOV_get_dec128(tdbb, desc); + impure2->make_decimal128(value); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure2->make_double(value); + } + } + } + else + { + if (impure2->vlux_count == frn) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + const auto value = MOV_get_dec128(tdbb, desc); + Firebird::Decimal128 d128; + d128.set(crn - rn, decSt); + const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure2->make_decimal128(part); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure2->vlu_misc.vlu_double += value * (crn - rn); + } + } + if (impure2->vlux_count == crn) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + const auto value = MOV_get_dec128(tdbb, desc); + Firebird::Decimal128 d128; + d128.set(rn - frn, decSt); + const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure2->make_decimal128(part); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure2->vlu_misc.vlu_double += value * (rn - frn); + } + } + } + } +} + +dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const +{ + impure_value_ex* impure2 = request->getImpure(impure2Offset); + + if (!impure2->vlux_count || !impure2->vlu_desc.dsc_dtype) + return nullptr; + + + return &impure2->vlu_desc; +} + +AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ +{ + AggNode* node = FB_NEW_POOL(dsqlScratch->getPool()) PercentileAggNode(dsqlScratch->getPool(), type, + doDsqlPass(dsqlScratch, arg), + doDsqlPass(dsqlScratch, dsqlOrderClause) ); + + return node; +} + //-------------------- diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index b9a186b698e..e036f54ef26 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -134,6 +134,52 @@ class ListAggNode final : public AggNode NestConst dsqlOrderClause; }; +class PercentileAggNode final : public AggNode +{ +public: + enum PercentileType : UCHAR + { + TYPE_PERCENTILE_CONT, + TYPE_PERCENTILE_DISC + }; + + explicit PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg = nullptr, + ValueListNode* aOrderClause = nullptr); + + void parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned count) override; + + unsigned getCapabilities() const override + { + return CAP_WANTS_AGG_CALLS; + } + + 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; + + void makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; + + void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; + ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier) const override; + AggNode* pass2(thread_db* tdbb, CompilerScratch* csb) override; + + void aggInit(thread_db* tdbb, Request* request) const override; + bool aggPass(thread_db* tdbb, Request* request) const override; + void aggPass(thread_db* tdbb, Request* request, dsc* desc) const override; + dsc* aggExecute(thread_db* tdbb, Request* request) const override; + +protected: + AggNode* dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ override; + +private: + const PercentileType type; + NestConst valueArg; + NestConst dsqlOrderClause; + ULONG impure2Offset = 0; +}; + class CountAggNode final : public AggNode { public: diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index f769b4b33ed..8c0baa6ebda 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -1092,6 +1092,8 @@ class AggNode : public TypedNode return NULL; } + virtual void makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc); + virtual void aggInit(thread_db* tdbb, Request* request) const = 0; // pure, but defined virtual void aggFinish(thread_db* tdbb, Request* request) const; virtual bool aggPass(thread_db* tdbb, Request* request) const; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index d18086526cc..b03e9a7c4fa 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -134 shift/reduce conflicts, 13 reduce/reduce conflicts. +134 shift/reduce conflicts, 25 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 28fc1e12163..b48564a104a 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -715,6 +715,8 @@ using namespace Firebird; %token LEAST %token LTRIM %token NAMED_ARG_ASSIGN +%token PERCENTILE_CONT +%token PERCENTILE_DISC %token RTRIM %token SCHEMA %token SEARCH_PATH @@ -4726,6 +4728,8 @@ keyword_or_column | WITHIN | LISTAGG | TRUNCATE + | PERCENTILE_CONT + | PERCENTILE_DISC ; col_opt @@ -8618,6 +8622,10 @@ aggregate_function_prefix { $$ = newNode(BinAggNode::TYPE_BIN_XOR, $4); } | BIN_XOR_AGG '(' DISTINCT value ')' { $$ = newNode(BinAggNode::TYPE_BIN_XOR_DISTINCT, $4); } + | PERCENTILE_CONT '(' percentile_arg ')' within_group_specification + { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_CONT, $3, $5); } + | PERCENTILE_DISC '(' percentile_arg ')' within_group_specification + { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_DISC, $3, $5); } ; %type listagg_set_function @@ -8692,6 +8700,13 @@ within_group_specification : WITHIN GROUP '(' order_clause ')' { $$ = $4; } ; +%type percentile_arg +percentile_arg + : u_numeric_constant + | variable + | parameter + ; + %type window_function window_function : DENSE_RANK '(' ')' @@ -10064,6 +10079,8 @@ non_reserved_word | FORMAT | GENERATE_SERIES | OWNER + | PERCENTILE_CONT + | PERCENTILE_DISC | SEARCH_PATH | SCHEMA | UNLIST diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 7ba26ca0ac2..2ee23a99c49 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1000,3 +1000,5 @@ FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid 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, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument STEP must be different than zero for function @1") FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types") +FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument for @1 must be in the range [0, 1]") +FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 4fdd5a676e3..72e0b55921a 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5848,6 +5848,8 @@ IProfilerStatsImpl = class(IProfilerStats) isc_no_user_att_while_restore = 335545318; isc_genseq_stepmustbe_nonzero = 335545319; isc_argmustbe_exact_function = 335545320; + isc_sysf_argmustbe_range_inc0_1 = 335545321; + isc_argmustbe_numeric_function = 335545322; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index c7ae78e32f0..c513a1adb51 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1525,7 +1525,7 @@ void Optimizer::generateAggregateSort(AggNode* aggNode) fb_assert(prevKey); ULONG length = prevKey ? ROUNDUP(prevKey->getSkdOffset() + prevKey->getSkdLength(), sizeof(SLONG)) : 0; - aggNode->arg->getDesc(tdbb, csb, desc); + aggNode->makeSortDesc(tdbb, csb, desc); if (desc->dsc_dtype >= dtype_aligned) length = FB_ALIGN(length, type_alignments[desc->dsc_dtype]); From 732db5ab433fb1b06f5483e973ecc8c46a1c3643 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Fri, 21 Nov 2025 19:40:44 +0300 Subject: [PATCH 02/71] Optimization. Record numbers are calculated only once. --- src/dsql/AggNodes.cpp | 47 ++++++++++++++++++++++++++----------------- src/dsql/AggNodes.h | 8 ++++++++ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 681d62f1391..133947323c2 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1205,6 +1205,8 @@ AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) // We need a second descriptor in the impure area for PERCENTILE. impure2Offset = csb->allocImpure(); + // impure area for calculate border + percentileImpureOffset = csb->allocImpure(); return this; } @@ -1230,6 +1232,11 @@ void PercentileAggNode::aggInit(thread_db* tdbb, Request* request) const impure_value_ex* impure2 = request->getImpure(impure2Offset); impure2->vlu_desc.dsc_dtype = 0; impure2->vlux_count = 0; + + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + percentileImpure->rn = 0; + percentileImpure->crn = 0; + percentileImpure->frn = 0; } bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const @@ -1316,28 +1323,30 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co { impure_value_ex* impure = request->getImpure(impureOffset); impure_value_ex* impure2 = request->getImpure(impure2Offset); + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); if (type == TYPE_PERCENTILE_DISC) { - // TODO: calculate only ones - const double rn = impure->vlu_misc.vlu_double * impure->vlux_count; - const auto crn = static_cast(ceil(rn)); - - impure2->vlux_count++; + if (impure2->vlux_count++ == 0) + { + // calculate only ones + percentileImpure->rn = impure->vlu_misc.vlu_double * impure->vlux_count; + percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); + } - if (impure2->vlux_count == crn) + if (impure2->vlux_count == percentileImpure->crn) { EVL_make_value(tdbb, desc, impure2); } } else { - // TODO: calculate only ones - const double rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); - const auto crn = static_cast(ceil(rn)); - const auto frn = static_cast(floor(rn)); - if (impure2->vlux_count++ == 0) { + // calculate only ones + percentileImpure->rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); + percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); + percentileImpure->frn = static_cast(floor(percentileImpure->rn)); + if (desc->isDecOrInt128()) { DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; @@ -1349,9 +1358,9 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co impure2->make_double(0); } - if (crn == frn) + if (percentileImpure->crn == percentileImpure->frn) { - if (impure2->vlux_count == frn) + if (impure2->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { @@ -1367,38 +1376,38 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co } else { - if (impure2->vlux_count == frn) + if (impure2->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; - d128.set(crn - rn, decSt); + d128.set(percentileImpure->crn - percentileImpure->rn, decSt); const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); impure2->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (crn - rn); + impure2->vlu_misc.vlu_double += value * (percentileImpure->crn - percentileImpure->rn); } } - if (impure2->vlux_count == crn) + if (impure2->vlux_count == percentileImpure->crn) { if (desc->isDecOrInt128()) { DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; - d128.set(rn - frn, decSt); + d128.set(percentileImpure->rn - percentileImpure->frn, decSt); const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); impure2->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (rn - frn); + impure2->vlu_misc.vlu_double += value * (percentileImpure->rn - percentileImpure->frn); } } } diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index e036f54ef26..ed6da539e63 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -143,6 +143,13 @@ class PercentileAggNode final : public AggNode TYPE_PERCENTILE_DISC }; + struct PercentileImpure + { + double rn; + SINT64 crn; + SINT64 frn; + }; + explicit PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg = nullptr, ValueListNode* aOrderClause = nullptr); @@ -178,6 +185,7 @@ class PercentileAggNode final : public AggNode NestConst valueArg; NestConst dsqlOrderClause; ULONG impure2Offset = 0; + ULONG percentileImpureOffset = 0; }; class CountAggNode final : public AggNode From 4241c388805723ad7128ea3129d77e2a93bad625 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Mon, 24 Nov 2025 08:32:09 -0300 Subject: [PATCH 03/71] Test ICU in Android --- .../arch-specific/android/BuildFinalPackage.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/builds/install/arch-specific/android/BuildFinalPackage.sh b/builds/install/arch-specific/android/BuildFinalPackage.sh index 94518b77e20..c07c9d37a1d 100755 --- a/builds/install/arch-specific/android/BuildFinalPackage.sh +++ b/builds/install/arch-specific/android/BuildFinalPackage.sh @@ -45,6 +45,23 @@ $ANDROID_HOME/platform-tools/adb -s $AndroidDeviceName push gen/$InitialDebugTar $ANDROID_HOME/platform-tools/adb -s $AndroidDeviceName shell "(cd $AndroidDir && tar xvf $InitialDebugTar)" $ANDROID_HOME/platform-tools/adb -s $AndroidDeviceName shell "(cd $AndroidDir/firebird && ./common_test --log_level=error && ./libEngine14_test --log_level=error && ./isql_test --log_level=error)" $ANDROID_HOME/platform-tools/adb -s $AndroidDeviceName shell "(cd $AndroidDir/firebird && ./AfterUntar.sh)" + +# Verify ICU works +$ANDROID_HOME/platform-tools/adb -s $AndroidDeviceName shell < Date: Mon, 24 Nov 2025 20:28:38 +0300 Subject: [PATCH 04/71] Fixed PERCENTILE_DISC with non-numeric argument --- src/dsql/AggNodes.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 133947323c2..07eaf568a52 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1183,8 +1183,25 @@ void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* void PercentileAggNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) { - arg->getDesc(tdbb, csb, desc); - desc->makeDouble(); + if (type == TYPE_PERCENTILE_DISC) + { + // same type as order by argument + valueArg->getDesc(tdbb, csb, desc); + } + else + { + valueArg->getDesc(tdbb, csb, desc); + if (desc->isDecOrInt128()) + { + desc->makeDecimal128(); + desc->setNullable(true); + } + else + { + desc->makeDouble(); + desc->setNullable(true); + } + } } ValueExprNode* PercentileAggNode::copy(thread_db* tdbb, NodeCopier& copier) const @@ -1331,7 +1348,7 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co { // calculate only ones percentileImpure->rn = impure->vlu_misc.vlu_double * impure->vlux_count; - percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); + percentileImpure->crn = MAX(static_cast(ceil(percentileImpure->rn)), 1); } if (impure2->vlux_count == percentileImpure->crn) @@ -1421,7 +1438,6 @@ dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const if (!impure2->vlux_count || !impure2->vlu_desc.dsc_dtype) return nullptr; - return &impure2->vlu_desc; } From 312ccbb4f19ac7396d489acabf9454a8e74b181b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 24 Nov 2025 20:21:15 +0000 Subject: [PATCH 05/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 4617dd51df6..6783b04a648 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1357 + FORMAL BUILD NUMBER:1358 */ -#define PRODUCT_VER_STRING "6.0.0.1357" -#define FILE_VER_STRING "WI-T6.0.0.1357" -#define LICENSE_VER_STRING "WI-T6.0.0.1357" -#define FILE_VER_NUMBER 6, 0, 0, 1357 +#define PRODUCT_VER_STRING "6.0.0.1358" +#define FILE_VER_STRING "WI-T6.0.0.1358" +#define LICENSE_VER_STRING "WI-T6.0.0.1358" +#define FILE_VER_NUMBER 6, 0, 0, 1358 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1357" +#define FB_BUILD_NO "1358" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index aa39c7255f2..713b139c921 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1357 +BuildNum=1358 NowAt=`pwd` cd `dirname $0` From a1f25a53dc79792d4576c4fca89298ea946a70eb Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Tue, 25 Nov 2025 20:26:31 +0300 Subject: [PATCH 06/71] Getting the parameter type when preparing a query for percentile functions. --- src/dsql/AggNodes.cpp | 4 ++++ src/dsql/parse.y | 11 ++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 07eaf568a52..e5fe31bcdb4 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1447,6 +1447,10 @@ AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, dsqlOrderClause) ); + PASS1_set_parameter_type(dsqlScratch, node->arg, + [&](dsc* desc) { desc->makeDouble(); }, + false); + return node; } diff --git a/src/dsql/parse.y b/src/dsql/parse.y index b48564a104a..9965bbfee3c 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -8622,9 +8622,9 @@ aggregate_function_prefix { $$ = newNode(BinAggNode::TYPE_BIN_XOR, $4); } | BIN_XOR_AGG '(' DISTINCT value ')' { $$ = newNode(BinAggNode::TYPE_BIN_XOR_DISTINCT, $4); } - | PERCENTILE_CONT '(' percentile_arg ')' within_group_specification + | PERCENTILE_CONT '(' value ')' within_group_specification { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_CONT, $3, $5); } - | PERCENTILE_DISC '(' percentile_arg ')' within_group_specification + | PERCENTILE_DISC '(' value ')' within_group_specification { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_DISC, $3, $5); } ; @@ -8700,13 +8700,6 @@ within_group_specification : WITHIN GROUP '(' order_clause ')' { $$ = $4; } ; -%type percentile_arg -percentile_arg - : u_numeric_constant - | variable - | parameter - ; - %type window_function window_function : DENSE_RANK '(' ')' From 5179bfeabb873e9c3a73b398318beda01f7087cf Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Tue, 25 Nov 2025 20:33:41 +0300 Subject: [PATCH 07/71] update doc --- doc/sql.extensions/README.percentile_disc_cont.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/sql.extensions/README.percentile_disc_cont.md b/doc/sql.extensions/README.percentile_disc_cont.md index d07044e2ec2..47997023665 100644 --- a/doc/sql.extensions/README.percentile_disc_cont.md +++ b/doc/sql.extensions/README.percentile_disc_cont.md @@ -13,8 +13,6 @@ Syntax for the `PERCENTILE_DISC` function as an aggregate function. ``` PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) - - ::= numeric_literal | | ``` Syntax for the `PERCENTILE_DISC` function as an window function. @@ -22,8 +20,6 @@ Syntax for the `PERCENTILE_DISC` function as an window function. ``` PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) OVER (PARTITION BY ) - - ::= numeric_literal | | ``` The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. @@ -84,8 +80,6 @@ Syntax for the `PERCENTILE_CONT` function as an aggregate function. ``` PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) - - ::= numeric_literal | | ``` Syntax for the `PERCENTILE_CONT` function as an window function. @@ -93,8 +87,6 @@ Syntax for the `PERCENTILE_CONT` function as an window function. ``` PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) OVER (PARTITION BY ) - - ::= numeric_literal | | ``` The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. From be4359f9768d96f312f4317e49de28ecc7c636f7 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Wed, 26 Nov 2025 20:42:08 +0300 Subject: [PATCH 08/71] Changes according to asfernandes --- src/dsql/AggNodes.cpp | 83 +++++++++++++---------------- src/dsql/AggNodes.h | 9 +++- src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index e5fe31bcdb4..808e9c7e1b0 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1123,11 +1123,9 @@ PercentileAggNode::PercentileAggNode(MemoryPool& pool, PercentileType aType, Val void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned /*count*/) { arg = PAR_parse_value(tdbb, csb); + valueArg = PAR_parse_value(tdbb, csb); if (csb->csb_blr_reader.peekByte() == blr_sort) - { sort = PAR_sort(tdbb, csb, blr_sort, true); - valueArg = sort->expressions[0]; - } } bool PercentileAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const @@ -1145,10 +1143,9 @@ void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) fb_assert(dsqlOrderClause); if (dsqlOrderClause->items.getCount() != 1) { - ERR_post(Arg::Gds(isc_wronumarg)); // WITHIN GROUP should only 1 ORDER item + ERR_post(Arg::Gds(isc_percetile_only_one_sort_item)); } - if (type == TYPE_PERCENTILE_DISC) { // same type as order by argument @@ -1220,8 +1217,6 @@ AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) { AggNode::pass2(tdbb, csb); - // We need a second descriptor in the impure area for PERCENTILE. - impure2Offset = csb->allocImpure(); // impure area for calculate border percentileImpureOffset = csb->allocImpure(); @@ -1246,11 +1241,8 @@ void PercentileAggNode::aggInit(thread_db* tdbb, Request* request) const impure->vlu_desc.dsc_dtype = 0; impure->vlux_count = 0; - impure_value_ex* impure2 = request->getImpure(impure2Offset); - impure2->vlu_desc.dsc_dtype = 0; - impure2->vlux_count = 0; - PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + percentileImpure->vlux_count = 0; percentileImpure->rn = 0; percentileImpure->crn = 0; percentileImpure->frn = 0; @@ -1268,11 +1260,8 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const if (!desc) return false; - if (desc->isNull()) - return false; - - impure_value_ex* impure = request->getImpure(impureOffset); - if (impure->vlux_count++ == 0) // first call to aggPass() + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + if (percentileImpure->vlux_count++ == 0) // first call to aggPass() { if ((type == TYPE_PERCENTILE_CONT) && !desc->isNumeric()) ERRD_post(Arg::Gds(isc_argmustbe_numeric_function) << Arg::Str("PERCENTILE_CONT")); @@ -1280,8 +1269,8 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const if (desc->isBlob()) ERRD_post(Arg::Gds(isc_blobnotsup) << Arg::Str("ORDER BY")); - const auto percentile_value = MOV_get_double(tdbb, percenteDesc); - if ((percentile_value < 0) || (percentile_value > 1)) + const auto percentileValue = MOV_get_double(tdbb, percenteDesc); + if ((percentileValue < 0) || (percentileValue > 1)) { if (type == TYPE_PERCENTILE_DISC) ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_DISC")); @@ -1289,7 +1278,7 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_CONT")); } - impure->vlu_misc.vlu_double = percentile_value; + percentileImpure->percentile = percentileValue; } if (sort) @@ -1308,7 +1297,7 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const for (auto& nodeOrder : sort->expressions) { dsc toDesc = *(descOrder++); - toDesc.dsc_address = data + (IPTR)toDesc.dsc_address; + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; if (const auto fromDsc = EVL_expr(tdbb, request, nodeOrder)) { if (IS_INTL_DATA(fromDsc)) @@ -1327,7 +1316,7 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const } dsc toDesc = asb->desc; - toDesc.dsc_address = data + (IPTR)toDesc.dsc_address; + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; MOV_move(tdbb, desc, &toDesc); return true; @@ -1339,28 +1328,27 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) const { impure_value_ex* impure = request->getImpure(impureOffset); - impure_value_ex* impure2 = request->getImpure(impure2Offset); PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); if (type == TYPE_PERCENTILE_DISC) { - if (impure2->vlux_count++ == 0) + if (impure->vlux_count++ == 0) { // calculate only ones - percentileImpure->rn = impure->vlu_misc.vlu_double * impure->vlux_count; + percentileImpure->rn = percentileImpure->percentile * percentileImpure->vlux_count; percentileImpure->crn = MAX(static_cast(ceil(percentileImpure->rn)), 1); } - if (impure2->vlux_count == percentileImpure->crn) - { - EVL_make_value(tdbb, desc, impure2); - } + if (impure->vlux_count == percentileImpure->crn) + EVL_make_value(tdbb, desc, impure); + } - else { - if (impure2->vlux_count++ == 0) + else + { + if (impure->vlux_count++ == 0) { // calculate only ones - percentileImpure->rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); + percentileImpure->rn = 1 + percentileImpure->percentile * (percentileImpure->vlux_count - 1); percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); percentileImpure->frn = static_cast(floor(percentileImpure->rn)); @@ -1369,31 +1357,31 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; Firebird::Decimal128 d128; d128.set(0, decSt, 0); - impure2->make_decimal128(d128); + impure->make_decimal128(d128); } else - impure2->make_double(0); + impure->make_double(0); } if (percentileImpure->crn == percentileImpure->frn) { - if (impure2->vlux_count == percentileImpure->frn) + if (impure->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { const auto value = MOV_get_dec128(tdbb, desc); - impure2->make_decimal128(value); + impure->make_decimal128(value); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->make_double(value); + impure->make_double(value); } } } else { - if (impure2->vlux_count == percentileImpure->frn) + if (impure->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { @@ -1401,16 +1389,17 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; d128.set(percentileImpure->crn - percentileImpure->rn, decSt); - const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); - impure2->make_decimal128(part); + const auto part = impure->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (percentileImpure->crn - percentileImpure->rn); + impure->vlu_misc.vlu_double += value * (percentileImpure->crn - percentileImpure->rn); } } - if (impure2->vlux_count == percentileImpure->crn) + + if (impure->vlux_count == percentileImpure->crn) { if (desc->isDecOrInt128()) { @@ -1418,13 +1407,13 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; d128.set(percentileImpure->rn - percentileImpure->frn, decSt); - const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); - impure2->make_decimal128(part); + const auto part = impure->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (percentileImpure->rn - percentileImpure->frn); + impure->vlu_misc.vlu_double += value * (percentileImpure->rn - percentileImpure->frn); } } } @@ -1433,12 +1422,12 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const { - impure_value_ex* impure2 = request->getImpure(impure2Offset); + impure_value_ex* impure = request->getImpure(impureOffset); - if (!impure2->vlux_count || !impure2->vlu_desc.dsc_dtype) + if (!impure->vlux_count || !impure->vlu_desc.dsc_dtype) return nullptr; - return &impure2->vlu_desc; + return &impure->vlu_desc; } AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index ed6da539e63..929c08e04dc 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -145,6 +145,8 @@ class PercentileAggNode final : public AggNode struct PercentileImpure { + SINT64 vlux_count; + double percentile; double rn; SINT64 crn; SINT64 frn; @@ -162,6 +164,12 @@ class PercentileAggNode final : public AggNode bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const override; + void getChildren(NodeRefsHolder& holder, bool dsql) const override + { + AggNode::getChildren(holder, dsql); + holder.add(valueArg); + } + Firebird::string internalPrint(NodePrinter& printer) const override; void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; void genBlr(DsqlCompilerScratch* dsqlScratch) override; @@ -184,7 +192,6 @@ class PercentileAggNode final : public AggNode const PercentileType type; NestConst valueArg; NestConst dsqlOrderClause; - ULONG impure2Offset = 0; ULONG percentileImpureOffset = 0; }; diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 2ee23a99c49..be4dd4cde4e 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1002,3 +1002,4 @@ FB_IMPL_MSG(JRD, 999, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument ST FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types") FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument for @1 must be in the range [0, 1]") FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types") +FB_IMPL_MSG(JRD, 1003, percetile_only_one_sort_item, -833, "42", "000", "The PERCENTILE_DISC and PERENTILE_CONT functions support only one sort item in WITHIN GROUP") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 72e0b55921a..61bbb789f60 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5850,6 +5850,7 @@ IProfilerStatsImpl = class(IProfilerStats) isc_argmustbe_exact_function = 335545320; isc_sysf_argmustbe_range_inc0_1 = 335545321; isc_argmustbe_numeric_function = 335545322; + isc_percetile_only_one_sort_item = 335545323; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; From 4af373f6b367186d329ae8166961222f55ff9146 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Sat, 29 Nov 2025 09:35:14 +0300 Subject: [PATCH 09/71] Totally misc --- src/jrd/align.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jrd/align.h b/src/jrd/align.h index bda7d99bece..44a380ce854 100644 --- a/src/jrd/align.h +++ b/src/jrd/align.h @@ -143,11 +143,11 @@ static inline constexpr USHORT type_lengths[DTYPE_TYPE_MAX] = sizeof(ISC_QUAD), /* dtype_blob */ sizeof(ISC_QUAD), /* dtype_array */ sizeof(SINT64), /* dtype_int64 */ - sizeof(RecordNumber::Packed), /*dtype_dbkey */ + sizeof(RecordNumber::Packed), /* dtype_dbkey */ sizeof(UCHAR), /* dtype_boolean */ sizeof(Firebird::Decimal64), /* dtype_dec64 */ - sizeof(Firebird::Decimal128), /*dtype_dec128 */ - sizeof(Firebird::Int128), /* dtype_int128 */ + sizeof(Firebird::Decimal128), /* dtype_dec128 */ + sizeof(Firebird::Int128), /* dtype_int128 */ sizeof(ISC_TIME_TZ), /* dtype_sql_time_tz */ sizeof(ISC_TIMESTAMP_TZ), /* dtype_timestamp_tz */ sizeof(ISC_TIME_TZ_EX), /* dtype_ex_time_tz */ From 8048d14f45110851f7ee82637b06aa2ff05a8865 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Sat, 29 Nov 2025 12:14:57 +0300 Subject: [PATCH 10/71] A few more checks for a valid database file, see also #8450. This prevents crashes for files >= 1KB but < PAGE_SIZE. --- src/jrd/pag.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index f5b0ee8e2de..7ae70dbf9cb 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -1151,6 +1151,16 @@ void PAG_header_init(thread_db* tdbb) if (header->hdr_page_size < MIN_PAGE_SIZE || header->hdr_page_size > MAX_PAGE_SIZE) ERR_post(Arg::Gds(isc_bad_db_format) << Arg::Str(attachment->att_filename)); + if (header->hdr_page_size % MIN_PAGE_SIZE != 0) + ERR_post(Arg::Gds(isc_bad_db_format) << Arg::Str(attachment->att_filename)); + + // Pagespace is already created at this point, so validate the database file + // to contain at least one full page + const auto pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE); + fb_assert(pageSpace && pageSpace->file); + if (!PIO_get_number_of_pages(pageSpace->file, header->hdr_page_size)) + ERR_post(Arg::Gds(isc_bad_db_format) << Arg::Str(attachment->att_filename)); + dbb->dbb_ods_version = ods_version; dbb->dbb_minor_version = header->hdr_ods_minor; From 8b3c3d5a3c02e40f48ec6df9029b3a48be689084 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 29 Nov 2025 20:19:31 +0000 Subject: [PATCH 11/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 6783b04a648..df570eba3c0 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1358 + FORMAL BUILD NUMBER:1360 */ -#define PRODUCT_VER_STRING "6.0.0.1358" -#define FILE_VER_STRING "WI-T6.0.0.1358" -#define LICENSE_VER_STRING "WI-T6.0.0.1358" -#define FILE_VER_NUMBER 6, 0, 0, 1358 +#define PRODUCT_VER_STRING "6.0.0.1360" +#define FILE_VER_STRING "WI-T6.0.0.1360" +#define LICENSE_VER_STRING "WI-T6.0.0.1360" +#define FILE_VER_NUMBER 6, 0, 0, 1360 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1358" +#define FB_BUILD_NO "1360" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 713b139c921..093a4b44683 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1358 +BuildNum=1360 NowAt=`pwd` cd `dirname $0` From 0c1f862d2ab260cc74a411709a2255308fe1e8ed Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Mon, 1 Dec 2025 12:40:42 +0300 Subject: [PATCH 12/71] More accurate calculation of the average page fill factor (#8816) * More accurate (methinks) calculation of the average page fill factor * Correction --- src/utilities/gstat/dba.epp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/utilities/gstat/dba.epp b/src/utilities/gstat/dba.epp index c9e08be3a34..c37f9891be2 100644 --- a/src/utilities/gstat/dba.epp +++ b/src/utilities/gstat/dba.epp @@ -1072,9 +1072,10 @@ int gstat(Firebird::UtilSvc* uSvc) uSvc->printf(false, " Pointer pages: %ld, data page slots: %ld\n", relation->rel_pointer_pages, relation->rel_slots); - const double average = (relation->rel_data_pages) ? + const auto nonEmptyPages = relation->rel_data_pages - relation->rel_empty_pages; + const double average = nonEmptyPages ? (double) relation->rel_total_space * 100 / - ((double) relation->rel_data_pages * (tddba->page_size - DPG_SIZE)) : 0.0; + ((double) nonEmptyPages * (tddba->page_size - DPG_SIZE)) : 0.0; uSvc->printf(false, " Data pages: %ld, average fill: %.0f%%\n", relation->rel_data_pages, average); @@ -1379,7 +1380,6 @@ static void analyze_data( dba_rel* relation, bool sw_record) ++relation->rel_slots; if (*ptr) { - ++relation->rel_data_pages; if (!analyze_data_page(relation, (const data_page*) db_read(*ptr), sw_record)) { dba_print(false, 18, SafeArg() << *ptr); @@ -1419,6 +1419,8 @@ static bool analyze_data_page( dba_rel* relation, const data_page* page, bool sw if (page->dpg_header.pag_type != pag_data) return false; + ++relation->rel_data_pages; + if (sw_record) { memcpy(tddba->buffer2, (const SCHAR*) page, tddba->page_size); @@ -1432,7 +1434,7 @@ static bool analyze_data_page( dba_rel* relation, const data_page* page, bool sw { if (tail->dpg_offset && tail->dpg_length) { - space += tail->dpg_length; + space += FB_ALIGN(tail->dpg_length, ODS_ALIGNMENT); if (sw_record) { From 56dfbc2e969111ea1c3518aeba81afa53e02a0dc Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 1 Dec 2025 20:19:28 +0000 Subject: [PATCH 13/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index df570eba3c0..1a6c415f3bd 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1360 + FORMAL BUILD NUMBER:1361 */ -#define PRODUCT_VER_STRING "6.0.0.1360" -#define FILE_VER_STRING "WI-T6.0.0.1360" -#define LICENSE_VER_STRING "WI-T6.0.0.1360" -#define FILE_VER_NUMBER 6, 0, 0, 1360 +#define PRODUCT_VER_STRING "6.0.0.1361" +#define FILE_VER_STRING "WI-T6.0.0.1361" +#define LICENSE_VER_STRING "WI-T6.0.0.1361" +#define FILE_VER_NUMBER 6, 0, 0, 1361 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1360" +#define FB_BUILD_NO "1361" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 093a4b44683..f3b884a8c74 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1360 +BuildNum=1361 NowAt=`pwd` cd `dirname $0` From 579ff5c7f67928e34e651839cc66b40b76792ee6 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Wed, 3 Dec 2025 08:27:15 +0300 Subject: [PATCH 14/71] Restore the broken record layout optimization by gbak and extend it to the new datatypes (#8815) * Add new 128-bit types to the record layout optimization attempted by gbak * Given the backup file already contains fields in the optimized order, insist on it and prevent the engine from generating field IDs in a different order. This restores the original record layout optimization accidentally broken by my commit #2ed48a6. --- src/burp/backup.epp | 16 +++++++++++++++- src/burp/restore.epp | 13 ++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/burp/backup.epp b/src/burp/backup.epp index 2cbb866d51d..b69e1b55f72 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -1940,6 +1940,7 @@ void put_relation( burp_rel* relation) burp_fld* unaligned = NULL; burp_fld* aligned4 = NULL; burp_fld* aligned8 = NULL; + burp_fld* aligned16 = NULL; burp_fld* fields = get_fields(relation); @@ -1951,7 +1952,13 @@ void put_relation( burp_rel* relation) USHORT l = field->fld_length; if (field->fld_type == blr_varying) l += sizeof(USHORT); - if (!(l & 7)) + + if (!(l & 15)) + { + field->fld_next = aligned16; + aligned16 = field; + } + else if (!(l & 7)) { field->fld_next = aligned8; aligned8 = field; @@ -2005,6 +2012,13 @@ void put_relation( burp_rel* relation) relation->rel_fields = field; } + while ((field = aligned16)) + { + aligned16 = field->fld_next; + field->fld_next = relation->rel_fields; + relation->rel_fields = field; + } + // Now write the fields in what will become physical backup order for (field = relation->rel_fields; field; field = field->fld_next) diff --git a/src/burp/restore.epp b/src/burp/restore.epp index 1aecf4f1cb0..b78afb757e6 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -132,7 +132,7 @@ bool get_collation(BurpGlobals* tdgbl); SLONG get_compressed(BurpGlobals* tdgbl, UCHAR* buffer, SLONG length); void get_data(BurpGlobals* tdgbl, burp_rel*, WriteRelationReq* req); bool get_exception(BurpGlobals* tdgbl); -burp_fld* get_field(BurpGlobals* tdgbl, burp_rel*); +burp_fld* get_field(BurpGlobals* tdgbl, burp_rel*, USHORT id); bool get_field_dimensions(BurpGlobals* tdgbl); bool get_files(BurpGlobals* tdgbl); bool get_filter(BurpGlobals* tdgbl); @@ -3806,7 +3806,7 @@ bool get_exception(BurpGlobals* tdgbl) } -burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation) +burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation, USHORT id) { /************************************** * @@ -3865,6 +3865,9 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation) // ODS 14 X.RDB$FIELD_SOURCE_SCHEMA_NAME.NULL = TRUE; + X.RDB$FIELD_ID.NULL = FALSE; + X.RDB$FIELD_ID = id; + if (relation->rel_name.schema.hasData()) { strcpy(X.RDB$SCHEMA_NAME, relation->rel_name.schema.c_str()); @@ -4084,6 +4087,9 @@ burp_fld* get_field(BurpGlobals* tdgbl, burp_rel* relation) X.RDB$NULL_FLAG.NULL = TRUE; X.RDB$COLLATION_ID.NULL = TRUE; + X.RDB$FIELD_ID.NULL = FALSE; + X.RDB$FIELD_ID = id; + skip_init(&scan_next_attr); while (get_attribute(&attribute, tdgbl) != att_end) { @@ -8249,6 +8255,7 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t // Eat up misc. records burp_fld* field = NULL; burp_fld** ptr = &relation->rel_fields; + USHORT id = 0; rec_type record; while (get_record(&record, tdgbl) != rec_data) @@ -8277,7 +8284,7 @@ bool get_relation(BurpGlobals* tdgbl, Coordinator* coord, RestoreRelationTask* t return true; case rec_field: - *ptr = field = get_field (tdgbl, relation); + *ptr = field = get_field(tdgbl, relation, id++); if (!field) return false; ptr = &field->fld_next; From a2e406ce8651cf6b377804d4e7cb7f8169f1b647 Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Tue, 2 Dec 2025 18:01:36 +0200 Subject: [PATCH 15/71] Merge pull request #8818 from FirebirdSQL/work/gh-8817 This should fix bug #8817 : Fatal lock manager error: invalid lock id --- src/jrd/Relation.h | 1 + src/jrd/dfw.epp | 9 ++++++++- src/jrd/met.epp | 45 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index 63dd4256e86..cfd6a93cdc9 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -404,6 +404,7 @@ inline constexpr ULONG REL_jrd_view = 0x8000; // relation is VIEW inline constexpr ULONG REL_gc_blocking = 0x10000; // request to downgrade\release gc lock inline constexpr ULONG REL_gc_disabled = 0x20000; // gc is disabled temporarily inline constexpr ULONG REL_gc_lockneed = 0x40000; // gc lock should be acquired +inline constexpr ULONG REL_rescan = 0x100000; // rescan request was submitted while relation being scanning /// class jrd_rel diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index 8c0cf546351..b76345b78c4 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -6275,8 +6275,15 @@ static bool make_version(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ DFW_post_work(transaction, dfw_scan_relation, nullptr, nullptr, relation->rel_id); // signal others about new format presence - LCK_lock(tdbb, relation->rel_rescan_lock, LCK_EX, LCK_WAIT); LCK_release(tdbb, relation->rel_rescan_lock); + { + // Use temp lock to avoid AST call + Lock tmpLock(tdbb, sizeof(SLONG), LCK_rel_rescan); + tmpLock.setKey(relation->rel_id); + + LCK_lock(tdbb, &tmpLock, LCK_EX, LCK_WAIT); + LCK_release(tdbb, &tmpLock); + } break; } diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 09c38b34d04..3756718b9f9 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -127,6 +127,7 @@ static void save_trigger_data(thread_db*, TrigVector**, jrd_rel*, Statement*, bl const QualifiedName*, FB_UINT64, SSHORT, USHORT, const MetaName&, const string&, const bid*, TriState ssDefiner); static void scan_partners(thread_db*, jrd_rel*); +static void scan_relation(thread_db*, jrd_rel*); static bool verify_TRG_ignore_perm(thread_db*, const QualifiedName&); @@ -3917,6 +3918,27 @@ void MET_scan_relation(thread_db* tdbb, jrd_rel* relation) * Scan a relation for view RecordSelExpr, computed by expressions, missing * expressions, and validation expressions. * + **************************************/ + + while (!(relation->rel_flags & (REL_scanned | REL_deleted))) + { + scan_relation(tdbb, relation); + } +} + + +static void scan_relation(thread_db* tdbb, jrd_rel* relation) +{ +/************************************** + * + * s c a n _ r e l a t i o n + * + ************************************** + * + * Functional description + * Scan a relation for view RecordSelExpr, computed by expressions, missing + * expressions, and validation expressions. + * **************************************/ SET_TDBB(tdbb); TrigVector* triggers[TRIGGER_MAX]; @@ -3935,10 +3957,13 @@ void MET_scan_relation(thread_db* tdbb, jrd_rel* relation) try { - if (relation->rel_flags & (REL_scanned | REL_deleted)) - return; + fb_assert(!(relation->rel_flags & (REL_scanned | REL_deleted))); relation->rel_flags |= REL_being_scanned; + + LCK_lock(tdbb, relation->rel_rescan_lock, LCK_SR, LCK_WAIT); + relation->rel_flags &= ~REL_rescan; + dependencies = (relation->rel_flags & REL_get_dependencies) ? true : false; relation->rel_flags &= ~REL_get_dependencies; @@ -4232,9 +4257,14 @@ void MET_scan_relation(thread_db* tdbb, jrd_rel* relation) // It's now time to place them at their rightful place inside the relation block. relation->replaceTriggers(tdbb, triggers); - LCK_lock(tdbb, relation->rel_rescan_lock, LCK_SR, LCK_WAIT); relation->rel_flags &= ~REL_being_scanned; + if (relation->rel_flags & REL_rescan) + { + LCK_release(tdbb, relation->rel_rescan_lock); + relation->rel_flags &= ~(REL_scanned | REL_rescan); + } + relation->rel_current_format = NULL; } // try @@ -4542,8 +4572,13 @@ static int rescan_ast_relation(void* ast_object) AsyncContextHolder tdbb(dbb, FB_FUNCTION, relation->rel_rescan_lock); - LCK_release(tdbb, relation->rel_rescan_lock); - relation->rel_flags &= ~REL_scanned; + if (relation->rel_flags & REL_being_scanned) + relation->rel_flags |= REL_rescan; + else + { + LCK_release(tdbb, relation->rel_rescan_lock); + relation->rel_flags &= ~REL_scanned; + } } catch (const Firebird::Exception&) {} // no-op From ead6d5907db1fc1aea5e2a5616bf2442e4988746 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 3 Dec 2025 20:22:05 +0000 Subject: [PATCH 16/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 1a6c415f3bd..b5e6be76363 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1361 + FORMAL BUILD NUMBER:1363 */ -#define PRODUCT_VER_STRING "6.0.0.1361" -#define FILE_VER_STRING "WI-T6.0.0.1361" -#define LICENSE_VER_STRING "WI-T6.0.0.1361" -#define FILE_VER_NUMBER 6, 0, 0, 1361 +#define PRODUCT_VER_STRING "6.0.0.1363" +#define FILE_VER_STRING "WI-T6.0.0.1363" +#define LICENSE_VER_STRING "WI-T6.0.0.1363" +#define FILE_VER_NUMBER 6, 0, 0, 1363 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1361" +#define FB_BUILD_NO "1363" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index f3b884a8c74..14038d7464b 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1361 +BuildNum=1363 NowAt=`pwd` cd `dirname $0` From ba0d7b25804c3d01185cd87972d5e527d3e015ef Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Thu, 4 Dec 2025 07:40:54 -0300 Subject: [PATCH 17/71] Fix #8820 - Missing a column name for boolean expression --- src/dsql/ExprNodes.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dsql/ExprNodes.h b/src/dsql/ExprNodes.h index f23be19b0c2..01ad4e853c2 100644 --- a/src/dsql/ExprNodes.h +++ b/src/dsql/ExprNodes.h @@ -228,8 +228,9 @@ class BoolAsValueNode final : public TypedNodepar_name = parameter->par_alias = "BOOL"; } void genBlr(DsqlCompilerScratch* dsqlScratch) override; From 9048844a2e069d81e269a6ae8996d462a3b129f8 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 4 Dec 2025 20:21:26 +0000 Subject: [PATCH 18/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index b5e6be76363..3954ca22c44 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1363 + FORMAL BUILD NUMBER:1364 */ -#define PRODUCT_VER_STRING "6.0.0.1363" -#define FILE_VER_STRING "WI-T6.0.0.1363" -#define LICENSE_VER_STRING "WI-T6.0.0.1363" -#define FILE_VER_NUMBER 6, 0, 0, 1363 +#define PRODUCT_VER_STRING "6.0.0.1364" +#define FILE_VER_STRING "WI-T6.0.0.1364" +#define LICENSE_VER_STRING "WI-T6.0.0.1364" +#define FILE_VER_NUMBER 6, 0, 0, 1364 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1363" +#define FB_BUILD_NO "1364" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 14038d7464b..3ebd55bcaf9 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1363 +BuildNum=1364 NowAt=`pwd` cd `dirname $0` From 4ae039f9ac33b316c71dcd3780500ed685f208aa Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Sun, 7 Dec 2025 09:17:47 +0300 Subject: [PATCH 19/71] On Windows, return number of valid (full-sized) pages rather than rounded up number. This is how it works on Linux since v3. And this fixes validation of database file < one full page in size. --- src/jrd/os/posix/unix.cpp | 2 +- src/jrd/os/win32/winnt.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jrd/os/posix/unix.cpp b/src/jrd/os/posix/unix.cpp index 538e7436383..b2573bf5020 100644 --- a/src/jrd/os/posix/unix.cpp +++ b/src/jrd/os/posix/unix.cpp @@ -470,7 +470,7 @@ ULONG PIO_get_number_of_pages(const jrd_file* file, const USHORT pagesize) ************************************** * * Functional description - * Compute number of pages in file, based only on file size. + * Compute number of full-size pages in file, based only on file size. * **************************************/ diff --git a/src/jrd/os/win32/winnt.cpp b/src/jrd/os/win32/winnt.cpp index a7733d59891..5b1898d7a96 100644 --- a/src/jrd/os/win32/winnt.cpp +++ b/src/jrd/os/win32/winnt.cpp @@ -740,7 +740,7 @@ ULONG PIO_get_number_of_pages(const jrd_file* file, const USHORT pagesize) ************************************** * * Functional description - * Compute number of pages in file, based only on file size. + * Compute number of full-size pages in file, based only on file size. * **************************************/ HANDLE hFile = file->fil_desc; @@ -752,7 +752,7 @@ ULONG PIO_get_number_of_pages(const jrd_file* file, const USHORT pagesize) nt_error("GetFileSize", file, isc_io_access_err, 0); const ULONGLONG ullFileSize = (((ULONGLONG) dwFileSizeHigh) << 32) + dwFileSizeLow; - return (ULONG) ((ullFileSize + pagesize - 1) / pagesize); + return ullFileSize / pagesize; } From e878785ac6e011b3d1b0b3bf1a956c3b1972d158 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 7 Dec 2025 20:19:15 +0000 Subject: [PATCH 20/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 3954ca22c44..a9e315fd9a9 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1364 + FORMAL BUILD NUMBER:1365 */ -#define PRODUCT_VER_STRING "6.0.0.1364" -#define FILE_VER_STRING "WI-T6.0.0.1364" -#define LICENSE_VER_STRING "WI-T6.0.0.1364" -#define FILE_VER_NUMBER 6, 0, 0, 1364 +#define PRODUCT_VER_STRING "6.0.0.1365" +#define FILE_VER_STRING "WI-T6.0.0.1365" +#define LICENSE_VER_STRING "WI-T6.0.0.1365" +#define FILE_VER_NUMBER 6, 0, 0, 1365 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1364" +#define FB_BUILD_NO "1365" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 3ebd55bcaf9..fe2ffff5bb6 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1364 +BuildNum=1365 NowAt=`pwd` cd `dirname $0` From 62a2a1511c2a85842685cd6063fa788e454193aa Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Mon, 8 Dec 2025 08:45:07 +0300 Subject: [PATCH 21/71] Per pagespace I/O statistics and new trace API interfaces to allow extendable statistics (#8808) * Add support of grouping page-level I/O counters per pagespace * Add support for per-pagespace I/O statistics. Deprecate non-extendable PerformanceInfo struct in favor of the new PerformanceCounters/PerformanceStats interfaces. Adjust the trace implementation to the new API. * Better names for interface methods. Add the basic docs. Get rid of the separate global counters. Misc renaming. * Add the docs weirdly escaped from the last commit * Follow Dimitry Sibiryakov's suggestion to unify get*Counters methods. * Rename the method --- doc/Using_OO_API.md | 41 ++ src/include/firebird/FirebirdInterface.idl | 65 +++ src/include/firebird/IdlFbInterfaces.h | 517 ++++++++++++++++++++- src/include/gen/Firebird.pas | 439 ++++++++++++++++- src/jrd/Monitoring.cpp | 39 +- src/jrd/RuntimeStatistics.cpp | 188 +++----- src/jrd/RuntimeStatistics.h | 305 +++++++----- src/jrd/cch.cpp | 33 +- src/jrd/inf.cpp | 6 +- src/jrd/jrd.h | 45 +- src/jrd/tra.cpp | 4 +- src/jrd/trace/TraceDSQLHelpers.h | 4 +- src/jrd/trace/TraceJrdHelpers.h | 12 +- src/jrd/trace/TraceObjects.cpp | 66 ++- src/jrd/trace/TraceObjects.h | 353 ++++++++++---- src/utilities/ntrace/TracePluginImpl.cpp | 196 +++++--- src/utilities/ntrace/TracePluginImpl.h | 4 +- 17 files changed, 1783 insertions(+), 534 deletions(-) diff --git a/doc/Using_OO_API.md b/doc/Using_OO_API.md index 4347c488fc4..cf543d3a84a 100644 --- a/doc/Using_OO_API.md +++ b/doc/Using_OO_API.md @@ -2101,3 +2101,44 @@ struct FbVarChar This document is currently missing 2 types of plugins – ExternalEngine and Trace. Information about them will be made available in next release of it. + +# Trace plugin + +_TODO_ + +# Trace objects + +_TODO_ + +# Trace performance statistics + +Trace plugin may retrieve various performance statistics available using the `getPerfStats()` method of the trace object, which returns a pointer to the `IPerformanceStats` interface. + +```cpp +IPerformanceStats* stats = statement->getPerfStats(); +``` + +The returned pointer may be `nullptr` if the corresponding trace object is not in the terminate state yet (i.e. still active / being executed). + + PerformanceStats interface: + +- ISC_UINT64 getElapsedTime() - returns the elapsed time, in milliseconds +- ISC_UINT64 getFetchedRecords() - returns number of records fetched during execution +- IPerformanceCounters* getCounters(unsigned group) - returns the requested performance counters group + +The following groups of performance counters are currently supported: + +- COUNTER_GROUP_PAGES - per-pagespace counters +- COUNTER_GROUP_TABLES - per-table counters + +If `getCounters()` is called with a counter group not supported by the implementation, `nullptr` is returned. + + PerformanceCounters interface: + +- unsigned getObjectCount() - returns number of objects (e.g. tables) containing non-zero performance counters +- unsigned getMaxCounterIndex() - returns maximum index number of the performance counters supported by the implementation (it's the same for all objects of the same group) +- unsigned getObjectId(unsigned index) - returns ID of the specified object +- const char* getObjectName(unsigned index) - returns name of the specified object +- const ISC_INT64* getObjectCounters(unsigned index) - returns pointer to the vector of performance counters (containing getMaxCounterIndex() + 1 elements) of the specified object + +The returned pointer to the vector (as well as the whole instance of `PerformanceStats`) is valid until the object used to obtain the statistics (using the `getPerfStats()` method) is destroyed. diff --git a/src/include/firebird/FirebirdInterface.idl b/src/include/firebird/FirebirdInterface.idl index 06601ffa6ef..4bce83e70a4 100644 --- a/src/include/firebird/FirebirdInterface.idl +++ b/src/include/firebird/FirebirdInterface.idl @@ -1364,6 +1364,9 @@ interface TraceTransaction : Versioned version: // 3.0.4 -> 3.0.5 int64 getInitialID(); int64 getPreviousID(); + +version: // 5.0 -> 6.0 + PerformanceStats getPerfStats(); } interface TraceParams : Versioned @@ -1379,6 +1382,9 @@ interface TraceStatement : Versioned { int64 getStmtID(); PerformanceInfo* getPerf(); + +version: // 5.0 -> 6.0 + PerformanceStats getPerfStats(); } interface TraceSQLStatement : TraceStatement @@ -1421,6 +1427,9 @@ version: // 4.0 -> 5.0 int64 getStmtID(); const string getPlan(); const string getExplainedPlan(); + +version: // 5.0 -> 6.0 + PerformanceStats getPerfStats(); } interface TraceFunction : Versioned @@ -1434,6 +1443,9 @@ version: // 4.0 -> 5.0 int64 getStmtID(); const string getPlan(); const string getExplainedPlan(); + +version: // 5.0 -> 6.0 + PerformanceStats getPerfStats(); } interface TraceTrigger : Versioned @@ -1456,6 +1468,9 @@ version: // 4.0 -> 5.0 int64 getStmtID(); const string getPlan(); const string getExplainedPlan(); + +version: // 5.0 -> 6.0 + PerformanceStats getPerfStats(); } interface TraceServiceConnection : TraceConnection @@ -1480,6 +1495,9 @@ interface TraceSweepInfo : Versioned int64 getOAT(); int64 getNext(); PerformanceInfo* getPerf(); + +version: // 5.0 -> 6.0 + PerformanceStats getPerfStats(); } interface TraceLogWriter : ReferenceCounted @@ -1876,3 +1894,50 @@ interface ProfilerStats : Versioned { uint64 getElapsedTicks(); } + +// Extendable replacement for struct PerformanceInfo + +interface PerformanceCounters : Versioned +{ + // Page-level performance counters (grouped per tablespace) + const uint PAGE_FETCHES = 0; + const uint PAGE_READS = 1; + const uint PAGE_MARKS = 2; + const uint PAGE_WRITES = 3; + + // Record-level performance counters (grouped per table) + const uint RECORD_SEQ_READS = 0; + const uint RECORD_IDX_READS = 1; + const uint RECORD_UPDATES = 2; + const uint RECORD_INSERTS = 3; + const uint RECORD_DELETES = 4; + const uint RECORD_BACKOUTS = 5; + const uint RECORD_PURGES = 6; + const uint RECORD_EXPUNGES = 7; + const uint RECORD_LOCKS = 8; + const uint RECORD_WAITS = 9; + const uint RECORD_CONFLICTS = 10; + const uint RECORD_BACK_READS = 11; + const uint RECORD_FRAGMENT_READS = 12; + const uint RECORD_RPT_READS = 13; + const uint RECORD_IMGC = 14; + + uint getObjectCount(); + uint getMaxCounterIndex(); + + uint getObjectId(uint index); + const string getObjectName(uint index); + const int64* getObjectCounters(uint index); +} + +interface PerformanceStats : Versioned +{ + const uint COUNTER_GROUP_PAGES = 0; + const uint COUNTER_GROUP_TABLES = 1; + + uint64 getElapsedTime(); // in milliseconds + uint64 getFetchedRecords(); + + PerformanceCounters getCounters(uint group); +} + diff --git a/src/include/firebird/IdlFbInterfaces.h b/src/include/firebird/IdlFbInterfaces.h index 43f3041054d..ea71d25e6bc 100644 --- a/src/include/firebird/IdlFbInterfaces.h +++ b/src/include/firebird/IdlFbInterfaces.h @@ -143,6 +143,8 @@ namespace Firebird class IProfilerPlugin; class IProfilerSession; class IProfilerStats; + class IPerformanceCounters; + class IPerformanceStats; // Interfaces declarations @@ -5458,7 +5460,7 @@ namespace Firebird } }; -#define FIREBIRD_ITRACE_TRANSACTION_VERSION 3u +#define FIREBIRD_ITRACE_TRANSACTION_VERSION 4u class ITraceTransaction : public IVersioned { @@ -5472,6 +5474,7 @@ namespace Firebird PerformanceInfo* (CLOOP_CARG *getPerf)(ITraceTransaction* self) CLOOP_NOEXCEPT; ISC_INT64 (CLOOP_CARG *getInitialID)(ITraceTransaction* self) CLOOP_NOEXCEPT; ISC_INT64 (CLOOP_CARG *getPreviousID)(ITraceTransaction* self) CLOOP_NOEXCEPT; + IPerformanceStats* (CLOOP_CARG *getPerfStats)(ITraceTransaction* self) CLOOP_NOEXCEPT; }; protected: @@ -5542,6 +5545,16 @@ namespace Firebird ISC_INT64 ret = static_cast(this->cloopVTable)->getPreviousID(this); return ret; } + + IPerformanceStats* getPerfStats() + { + if (cloopVTable->version < 4) + { + return 0; + } + IPerformanceStats* ret = static_cast(this->cloopVTable)->getPerfStats(this); + return ret; + } }; #define FIREBIRD_ITRACE_PARAMS_VERSION 3u @@ -5596,7 +5609,7 @@ namespace Firebird } }; -#define FIREBIRD_ITRACE_STATEMENT_VERSION 2u +#define FIREBIRD_ITRACE_STATEMENT_VERSION 3u class ITraceStatement : public IVersioned { @@ -5605,6 +5618,7 @@ namespace Firebird { ISC_INT64 (CLOOP_CARG *getStmtID)(ITraceStatement* self) CLOOP_NOEXCEPT; PerformanceInfo* (CLOOP_CARG *getPerf)(ITraceStatement* self) CLOOP_NOEXCEPT; + IPerformanceStats* (CLOOP_CARG *getPerfStats)(ITraceStatement* self) CLOOP_NOEXCEPT; }; protected: @@ -5631,9 +5645,19 @@ namespace Firebird PerformanceInfo* ret = static_cast(this->cloopVTable)->getPerf(this); return ret; } + + IPerformanceStats* getPerfStats() + { + if (cloopVTable->version < 3) + { + return 0; + } + IPerformanceStats* ret = static_cast(this->cloopVTable)->getPerfStats(this); + return ret; + } }; -#define FIREBIRD_ITRACE_SQLSTATEMENT_VERSION 3u +#define FIREBIRD_ITRACE_SQLSTATEMENT_VERSION 4u class ITraceSQLStatement : public ITraceStatement { @@ -5691,7 +5715,7 @@ namespace Firebird } }; -#define FIREBIRD_ITRACE_BLRSTATEMENT_VERSION 3u +#define FIREBIRD_ITRACE_BLRSTATEMENT_VERSION 4u class ITraceBLRStatement : public ITraceStatement { @@ -5823,7 +5847,7 @@ namespace Firebird } }; -#define FIREBIRD_ITRACE_PROCEDURE_VERSION 3u +#define FIREBIRD_ITRACE_PROCEDURE_VERSION 4u class ITraceProcedure : public IVersioned { @@ -5836,6 +5860,7 @@ namespace Firebird ISC_INT64 (CLOOP_CARG *getStmtID)(ITraceProcedure* self) CLOOP_NOEXCEPT; const char* (CLOOP_CARG *getPlan)(ITraceProcedure* self) CLOOP_NOEXCEPT; const char* (CLOOP_CARG *getExplainedPlan)(ITraceProcedure* self) CLOOP_NOEXCEPT; + IPerformanceStats* (CLOOP_CARG *getPerfStats)(ITraceProcedure* self) CLOOP_NOEXCEPT; }; protected: @@ -5898,9 +5923,19 @@ namespace Firebird const char* ret = static_cast(this->cloopVTable)->getExplainedPlan(this); return ret; } + + IPerformanceStats* getPerfStats() + { + if (cloopVTable->version < 4) + { + return 0; + } + IPerformanceStats* ret = static_cast(this->cloopVTable)->getPerfStats(this); + return ret; + } }; -#define FIREBIRD_ITRACE_FUNCTION_VERSION 3u +#define FIREBIRD_ITRACE_FUNCTION_VERSION 4u class ITraceFunction : public IVersioned { @@ -5914,6 +5949,7 @@ namespace Firebird ISC_INT64 (CLOOP_CARG *getStmtID)(ITraceFunction* self) CLOOP_NOEXCEPT; const char* (CLOOP_CARG *getPlan)(ITraceFunction* self) CLOOP_NOEXCEPT; const char* (CLOOP_CARG *getExplainedPlan)(ITraceFunction* self) CLOOP_NOEXCEPT; + IPerformanceStats* (CLOOP_CARG *getPerfStats)(ITraceFunction* self) CLOOP_NOEXCEPT; }; protected: @@ -5982,9 +6018,19 @@ namespace Firebird const char* ret = static_cast(this->cloopVTable)->getExplainedPlan(this); return ret; } + + IPerformanceStats* getPerfStats() + { + if (cloopVTable->version < 4) + { + return 0; + } + IPerformanceStats* ret = static_cast(this->cloopVTable)->getPerfStats(this); + return ret; + } }; -#define FIREBIRD_ITRACE_TRIGGER_VERSION 3u +#define FIREBIRD_ITRACE_TRIGGER_VERSION 4u class ITraceTrigger : public IVersioned { @@ -5999,6 +6045,7 @@ namespace Firebird ISC_INT64 (CLOOP_CARG *getStmtID)(ITraceTrigger* self) CLOOP_NOEXCEPT; const char* (CLOOP_CARG *getPlan)(ITraceTrigger* self) CLOOP_NOEXCEPT; const char* (CLOOP_CARG *getExplainedPlan)(ITraceTrigger* self) CLOOP_NOEXCEPT; + IPerformanceStats* (CLOOP_CARG *getPerfStats)(ITraceTrigger* self) CLOOP_NOEXCEPT; }; protected: @@ -6077,6 +6124,16 @@ namespace Firebird const char* ret = static_cast(this->cloopVTable)->getExplainedPlan(this); return ret; } + + IPerformanceStats* getPerfStats() + { + if (cloopVTable->version < 4) + { + return 0; + } + IPerformanceStats* ret = static_cast(this->cloopVTable)->getPerfStats(this); + return ret; + } }; #define FIREBIRD_ITRACE_SERVICE_CONNECTION_VERSION 3u @@ -6174,7 +6231,7 @@ namespace Firebird } }; -#define FIREBIRD_ITRACE_SWEEP_INFO_VERSION 2u +#define FIREBIRD_ITRACE_SWEEP_INFO_VERSION 3u class ITraceSweepInfo : public IVersioned { @@ -6186,6 +6243,7 @@ namespace Firebird ISC_INT64 (CLOOP_CARG *getOAT)(ITraceSweepInfo* self) CLOOP_NOEXCEPT; ISC_INT64 (CLOOP_CARG *getNext)(ITraceSweepInfo* self) CLOOP_NOEXCEPT; PerformanceInfo* (CLOOP_CARG *getPerf)(ITraceSweepInfo* self) CLOOP_NOEXCEPT; + IPerformanceStats* (CLOOP_CARG *getPerfStats)(ITraceSweepInfo* self) CLOOP_NOEXCEPT; }; protected: @@ -6230,6 +6288,16 @@ namespace Firebird PerformanceInfo* ret = static_cast(this->cloopVTable)->getPerf(this); return ret; } + + IPerformanceStats* getPerfStats() + { + if (cloopVTable->version < 3) + { + return 0; + } + IPerformanceStats* ret = static_cast(this->cloopVTable)->getPerfStats(this); + return ret; + } }; #define FIREBIRD_ITRACE_LOG_WRITER_VERSION 4u @@ -7538,6 +7606,131 @@ namespace Firebird } }; +#define FIREBIRD_IPERFORMANCE_COUNTERS_VERSION 2u + + class IPerformanceCounters : public IVersioned + { + public: + struct VTable : public IVersioned::VTable + { + unsigned (CLOOP_CARG *getObjectCount)(IPerformanceCounters* self) CLOOP_NOEXCEPT; + unsigned (CLOOP_CARG *getMaxCounterIndex)(IPerformanceCounters* self) CLOOP_NOEXCEPT; + unsigned (CLOOP_CARG *getObjectId)(IPerformanceCounters* self, unsigned index) CLOOP_NOEXCEPT; + const char* (CLOOP_CARG *getObjectName)(IPerformanceCounters* self, unsigned index) CLOOP_NOEXCEPT; + const ISC_INT64* (CLOOP_CARG *getObjectCounters)(IPerformanceCounters* self, unsigned index) CLOOP_NOEXCEPT; + }; + + protected: + IPerformanceCounters(DoNotInherit) + : IVersioned(DoNotInherit()) + { + } + + ~IPerformanceCounters() + { + } + + public: + static CLOOP_CONSTEXPR unsigned VERSION = FIREBIRD_IPERFORMANCE_COUNTERS_VERSION; + + static CLOOP_CONSTEXPR unsigned PAGE_FETCHES = 0; + static CLOOP_CONSTEXPR unsigned PAGE_READS = 1; + static CLOOP_CONSTEXPR unsigned PAGE_MARKS = 2; + static CLOOP_CONSTEXPR unsigned PAGE_WRITES = 3; + static CLOOP_CONSTEXPR unsigned RECORD_SEQ_READS = 0; + static CLOOP_CONSTEXPR unsigned RECORD_IDX_READS = 1; + static CLOOP_CONSTEXPR unsigned RECORD_UPDATES = 2; + static CLOOP_CONSTEXPR unsigned RECORD_INSERTS = 3; + static CLOOP_CONSTEXPR unsigned RECORD_DELETES = 4; + static CLOOP_CONSTEXPR unsigned RECORD_BACKOUTS = 5; + static CLOOP_CONSTEXPR unsigned RECORD_PURGES = 6; + static CLOOP_CONSTEXPR unsigned RECORD_EXPUNGES = 7; + static CLOOP_CONSTEXPR unsigned RECORD_LOCKS = 8; + static CLOOP_CONSTEXPR unsigned RECORD_WAITS = 9; + static CLOOP_CONSTEXPR unsigned RECORD_CONFLICTS = 10; + static CLOOP_CONSTEXPR unsigned RECORD_BACK_READS = 11; + static CLOOP_CONSTEXPR unsigned RECORD_FRAGMENT_READS = 12; + static CLOOP_CONSTEXPR unsigned RECORD_RPT_READS = 13; + static CLOOP_CONSTEXPR unsigned RECORD_IMGC = 14; + + unsigned getObjectCount() + { + unsigned ret = static_cast(this->cloopVTable)->getObjectCount(this); + return ret; + } + + unsigned getMaxCounterIndex() + { + unsigned ret = static_cast(this->cloopVTable)->getMaxCounterIndex(this); + return ret; + } + + unsigned getObjectId(unsigned index) + { + unsigned ret = static_cast(this->cloopVTable)->getObjectId(this, index); + return ret; + } + + const char* getObjectName(unsigned index) + { + const char* ret = static_cast(this->cloopVTable)->getObjectName(this, index); + return ret; + } + + const ISC_INT64* getObjectCounters(unsigned index) + { + const ISC_INT64* ret = static_cast(this->cloopVTable)->getObjectCounters(this, index); + return ret; + } + }; + +#define FIREBIRD_IPERFORMANCE_STATS_VERSION 2u + + class IPerformanceStats : public IVersioned + { + public: + struct VTable : public IVersioned::VTable + { + ISC_UINT64 (CLOOP_CARG *getElapsedTime)(IPerformanceStats* self) CLOOP_NOEXCEPT; + ISC_UINT64 (CLOOP_CARG *getFetchedRecords)(IPerformanceStats* self) CLOOP_NOEXCEPT; + IPerformanceCounters* (CLOOP_CARG *getCounters)(IPerformanceStats* self, unsigned group) CLOOP_NOEXCEPT; + }; + + protected: + IPerformanceStats(DoNotInherit) + : IVersioned(DoNotInherit()) + { + } + + ~IPerformanceStats() + { + } + + public: + static CLOOP_CONSTEXPR unsigned VERSION = FIREBIRD_IPERFORMANCE_STATS_VERSION; + + static CLOOP_CONSTEXPR unsigned COUNTER_GROUP_PAGES = 0; + static CLOOP_CONSTEXPR unsigned COUNTER_GROUP_TABLES = 1; + + ISC_UINT64 getElapsedTime() + { + ISC_UINT64 ret = static_cast(this->cloopVTable)->getElapsedTime(this); + return ret; + } + + ISC_UINT64 getFetchedRecords() + { + ISC_UINT64 ret = static_cast(this->cloopVTable)->getFetchedRecords(this); + return ret; + } + + IPerformanceCounters* getCounters(unsigned group) + { + IPerformanceCounters* ret = static_cast(this->cloopVTable)->getCounters(this, group); + return ret; + } + }; + // Interfaces implementations template @@ -17637,6 +17830,7 @@ namespace Firebird this->getPerf = &Name::cloopgetPerfDispatcher; this->getInitialID = &Name::cloopgetInitialIDDispatcher; this->getPreviousID = &Name::cloopgetPreviousIDDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; } } vTable; @@ -17733,6 +17927,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceTransaction* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > @@ -17755,6 +17962,7 @@ namespace Firebird virtual PerformanceInfo* getPerf() = 0; virtual ISC_INT64 getInitialID() = 0; virtual ISC_INT64 getPreviousID() = 0; + virtual IPerformanceStats* getPerfStats() = 0; }; template @@ -17854,6 +18062,7 @@ namespace Firebird this->version = Base::VERSION; this->getStmtID = &Name::cloopgetStmtIDDispatcher; this->getPerf = &Name::cloopgetPerfDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; } } vTable; @@ -17885,6 +18094,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceStatement* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > @@ -17902,6 +18124,7 @@ namespace Firebird virtual ISC_INT64 getStmtID() = 0; virtual PerformanceInfo* getPerf() = 0; + virtual IPerformanceStats* getPerfStats() = 0; }; template @@ -17919,6 +18142,7 @@ namespace Firebird this->version = Base::VERSION; this->getStmtID = &Name::cloopgetStmtIDDispatcher; this->getPerf = &Name::cloopgetPerfDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; this->getText = &Name::cloopgetTextDispatcher; this->getPlan = &Name::cloopgetPlanDispatcher; this->getInputs = &Name::cloopgetInputsDispatcher; @@ -18020,6 +18244,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceStatement* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > > > @@ -18057,6 +18294,7 @@ namespace Firebird this->version = Base::VERSION; this->getStmtID = &Name::cloopgetStmtIDDispatcher; this->getPerf = &Name::cloopgetPerfDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; this->getData = &Name::cloopgetDataDispatcher; this->getDataLength = &Name::cloopgetDataLengthDispatcher; this->getText = &Name::cloopgetTextDispatcher; @@ -18130,6 +18368,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceStatement* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > > > @@ -18329,6 +18580,7 @@ namespace Firebird this->getStmtID = &Name::cloopgetStmtIDDispatcher; this->getPlan = &Name::cloopgetPlanDispatcher; this->getExplainedPlan = &Name::cloopgetExplainedPlanDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; } } vTable; @@ -18412,6 +18664,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceProcedure* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > @@ -18433,6 +18698,7 @@ namespace Firebird virtual ISC_INT64 getStmtID() = 0; virtual const char* getPlan() = 0; virtual const char* getExplainedPlan() = 0; + virtual IPerformanceStats* getPerfStats() = 0; }; template @@ -18455,6 +18721,7 @@ namespace Firebird this->getStmtID = &Name::cloopgetStmtIDDispatcher; this->getPlan = &Name::cloopgetPlanDispatcher; this->getExplainedPlan = &Name::cloopgetExplainedPlanDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; } } vTable; @@ -18551,6 +18818,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceFunction* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > @@ -18573,6 +18853,7 @@ namespace Firebird virtual ISC_INT64 getStmtID() = 0; virtual const char* getPlan() = 0; virtual const char* getExplainedPlan() = 0; + virtual IPerformanceStats* getPerfStats() = 0; }; template @@ -18596,6 +18877,7 @@ namespace Firebird this->getStmtID = &Name::cloopgetStmtIDDispatcher; this->getPlan = &Name::cloopgetPlanDispatcher; this->getExplainedPlan = &Name::cloopgetExplainedPlanDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; } } vTable; @@ -18705,6 +18987,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceTrigger* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > @@ -18728,6 +19023,7 @@ namespace Firebird virtual ISC_INT64 getStmtID() = 0; virtual const char* getPlan() = 0; virtual const char* getExplainedPlan() = 0; + virtual IPerformanceStats* getPerfStats() = 0; }; template @@ -19049,6 +19345,7 @@ namespace Firebird this->getOAT = &Name::cloopgetOATDispatcher; this->getNext = &Name::cloopgetNextDispatcher; this->getPerf = &Name::cloopgetPerfDispatcher; + this->getPerfStats = &Name::cloopgetPerfStatsDispatcher; } } vTable; @@ -19119,6 +19416,19 @@ namespace Firebird return static_cast(0); } } + + static IPerformanceStats* CLOOP_CARG cloopgetPerfStatsDispatcher(ITraceSweepInfo* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getPerfStats(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } }; template > > @@ -19139,6 +19449,7 @@ namespace Firebird virtual ISC_INT64 getOAT() = 0; virtual ISC_INT64 getNext() = 0; virtual PerformanceInfo* getPerf() = 0; + virtual IPerformanceStats* getPerfStats() = 0; }; template @@ -21698,6 +22009,196 @@ namespace Firebird virtual ISC_UINT64 getElapsedTicks() = 0; }; + + template + class IPerformanceCountersBaseImpl : public Base + { + public: + typedef IPerformanceCounters Declaration; + + IPerformanceCountersBaseImpl(DoNotInherit = DoNotInherit()) + { + static struct VTableImpl : Base::VTable + { + VTableImpl() + { + this->version = Base::VERSION; + this->getObjectCount = &Name::cloopgetObjectCountDispatcher; + this->getMaxCounterIndex = &Name::cloopgetMaxCounterIndexDispatcher; + this->getObjectId = &Name::cloopgetObjectIdDispatcher; + this->getObjectName = &Name::cloopgetObjectNameDispatcher; + this->getObjectCounters = &Name::cloopgetObjectCountersDispatcher; + } + } vTable; + + this->cloopVTable = &vTable; + } + + static unsigned CLOOP_CARG cloopgetObjectCountDispatcher(IPerformanceCounters* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getObjectCount(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static unsigned CLOOP_CARG cloopgetMaxCounterIndexDispatcher(IPerformanceCounters* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getMaxCounterIndex(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static unsigned CLOOP_CARG cloopgetObjectIdDispatcher(IPerformanceCounters* self, unsigned index) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getObjectId(index); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static const char* CLOOP_CARG cloopgetObjectNameDispatcher(IPerformanceCounters* self, unsigned index) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getObjectName(index); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static const ISC_INT64* CLOOP_CARG cloopgetObjectCountersDispatcher(IPerformanceCounters* self, unsigned index) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getObjectCounters(index); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + }; + + template > > + class IPerformanceCountersImpl : public IPerformanceCountersBaseImpl + { + protected: + IPerformanceCountersImpl(DoNotInherit = DoNotInherit()) + { + } + + public: + virtual ~IPerformanceCountersImpl() + { + } + + virtual unsigned getObjectCount() = 0; + virtual unsigned getMaxCounterIndex() = 0; + virtual unsigned getObjectId(unsigned index) = 0; + virtual const char* getObjectName(unsigned index) = 0; + virtual const ISC_INT64* getObjectCounters(unsigned index) = 0; + }; + + template + class IPerformanceStatsBaseImpl : public Base + { + public: + typedef IPerformanceStats Declaration; + + IPerformanceStatsBaseImpl(DoNotInherit = DoNotInherit()) + { + static struct VTableImpl : Base::VTable + { + VTableImpl() + { + this->version = Base::VERSION; + this->getElapsedTime = &Name::cloopgetElapsedTimeDispatcher; + this->getFetchedRecords = &Name::cloopgetFetchedRecordsDispatcher; + this->getCounters = &Name::cloopgetCountersDispatcher; + } + } vTable; + + this->cloopVTable = &vTable; + } + + static ISC_UINT64 CLOOP_CARG cloopgetElapsedTimeDispatcher(IPerformanceStats* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getElapsedTime(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static ISC_UINT64 CLOOP_CARG cloopgetFetchedRecordsDispatcher(IPerformanceStats* self) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getFetchedRecords(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static IPerformanceCounters* CLOOP_CARG cloopgetCountersDispatcher(IPerformanceStats* self, unsigned group) CLOOP_NOEXCEPT + { + try + { + return static_cast(self)->Name::getCounters(group); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + }; + + template > > + class IPerformanceStatsImpl : public IPerformanceStatsBaseImpl + { + protected: + IPerformanceStatsImpl(DoNotInherit = DoNotInherit()) + { + } + + public: + virtual ~IPerformanceStatsImpl() + { + } + + virtual ISC_UINT64 getElapsedTime() = 0; + virtual ISC_UINT64 getFetchedRecords() = 0; + virtual IPerformanceCounters* getCounters(unsigned group) = 0; + }; }; diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 4fdd5a676e3..eb0cfeae5a0 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -114,6 +114,8 @@ IReplicatedSession = class; IProfilerPlugin = class; IProfilerSession = class; IProfilerStats = class; + IPerformanceCounters = class; + IPerformanceStats = class; FbException = class(Exception) public @@ -611,11 +613,13 @@ ISC_TIMESTAMP_TZ_EX = record ITraceTransaction_getPerfPtr = function(this: ITraceTransaction): PerformanceInfoPtr; cdecl; ITraceTransaction_getInitialIDPtr = function(this: ITraceTransaction): Int64; cdecl; ITraceTransaction_getPreviousIDPtr = function(this: ITraceTransaction): Int64; cdecl; + ITraceTransaction_getPerfStatsPtr = function(this: ITraceTransaction): IPerformanceStats; cdecl; ITraceParams_getCountPtr = function(this: ITraceParams): Cardinal; cdecl; ITraceParams_getParamPtr = function(this: ITraceParams; idx: Cardinal): paramdscPtr; cdecl; ITraceParams_getTextUTF8Ptr = function(this: ITraceParams; status: IStatus; idx: Cardinal): PAnsiChar; cdecl; ITraceStatement_getStmtIDPtr = function(this: ITraceStatement): Int64; cdecl; ITraceStatement_getPerfPtr = function(this: ITraceStatement): PerformanceInfoPtr; cdecl; + ITraceStatement_getPerfStatsPtr = function(this: ITraceStatement): IPerformanceStats; cdecl; ITraceSQLStatement_getTextPtr = function(this: ITraceSQLStatement): PAnsiChar; cdecl; ITraceSQLStatement_getPlanPtr = function(this: ITraceSQLStatement): PAnsiChar; cdecl; ITraceSQLStatement_getInputsPtr = function(this: ITraceSQLStatement): ITraceParams; cdecl; @@ -636,6 +640,7 @@ ISC_TIMESTAMP_TZ_EX = record ITraceProcedure_getStmtIDPtr = function(this: ITraceProcedure): Int64; cdecl; ITraceProcedure_getPlanPtr = function(this: ITraceProcedure): PAnsiChar; cdecl; ITraceProcedure_getExplainedPlanPtr = function(this: ITraceProcedure): PAnsiChar; cdecl; + ITraceProcedure_getPerfStatsPtr = function(this: ITraceProcedure): IPerformanceStats; cdecl; ITraceFunction_getFuncNamePtr = function(this: ITraceFunction): PAnsiChar; cdecl; ITraceFunction_getInputsPtr = function(this: ITraceFunction): ITraceParams; cdecl; ITraceFunction_getResultPtr = function(this: ITraceFunction): ITraceParams; cdecl; @@ -643,6 +648,7 @@ ISC_TIMESTAMP_TZ_EX = record ITraceFunction_getStmtIDPtr = function(this: ITraceFunction): Int64; cdecl; ITraceFunction_getPlanPtr = function(this: ITraceFunction): PAnsiChar; cdecl; ITraceFunction_getExplainedPlanPtr = function(this: ITraceFunction): PAnsiChar; cdecl; + ITraceFunction_getPerfStatsPtr = function(this: ITraceFunction): IPerformanceStats; cdecl; ITraceTrigger_getTriggerNamePtr = function(this: ITraceTrigger): PAnsiChar; cdecl; ITraceTrigger_getRelationNamePtr = function(this: ITraceTrigger): PAnsiChar; cdecl; ITraceTrigger_getActionPtr = function(this: ITraceTrigger): Integer; cdecl; @@ -651,6 +657,7 @@ ISC_TIMESTAMP_TZ_EX = record ITraceTrigger_getStmtIDPtr = function(this: ITraceTrigger): Int64; cdecl; ITraceTrigger_getPlanPtr = function(this: ITraceTrigger): PAnsiChar; cdecl; ITraceTrigger_getExplainedPlanPtr = function(this: ITraceTrigger): PAnsiChar; cdecl; + ITraceTrigger_getPerfStatsPtr = function(this: ITraceTrigger): IPerformanceStats; cdecl; ITraceServiceConnection_getServiceIDPtr = function(this: ITraceServiceConnection): Pointer; cdecl; ITraceServiceConnection_getServiceMgrPtr = function(this: ITraceServiceConnection): PAnsiChar; cdecl; ITraceServiceConnection_getServiceNamePtr = function(this: ITraceServiceConnection): PAnsiChar; cdecl; @@ -663,6 +670,7 @@ ISC_TIMESTAMP_TZ_EX = record ITraceSweepInfo_getOATPtr = function(this: ITraceSweepInfo): Int64; cdecl; ITraceSweepInfo_getNextPtr = function(this: ITraceSweepInfo): Int64; cdecl; ITraceSweepInfo_getPerfPtr = function(this: ITraceSweepInfo): PerformanceInfoPtr; cdecl; + ITraceSweepInfo_getPerfStatsPtr = function(this: ITraceSweepInfo): IPerformanceStats; cdecl; ITraceLogWriter_writePtr = function(this: ITraceLogWriter; buf: Pointer; size: Cardinal): Cardinal; cdecl; ITraceLogWriter_write_sPtr = function(this: ITraceLogWriter; status: IStatus; buf: Pointer; size: Cardinal): Cardinal; cdecl; ITraceInitInfo_getConfigTextPtr = function(this: ITraceInitInfo): PAnsiChar; cdecl; @@ -770,6 +778,14 @@ ISC_TIMESTAMP_TZ_EX = record IProfilerSession_afterRecordSourceGetRecordPtr = procedure(this: IProfilerSession; statementId: Int64; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; stats: IProfilerStats); cdecl; IProfilerSession_defineStatement2Ptr = procedure(this: IProfilerSession; status: IStatus; statementId: Int64; parentStatementId: Int64; type_: PAnsiChar; schemaName: PAnsiChar; packageName: PAnsiChar; routineName: PAnsiChar; sqlText: PAnsiChar); cdecl; IProfilerStats_getElapsedTicksPtr = function(this: IProfilerStats): QWord; cdecl; + IPerformanceCounters_getObjectCountPtr = function(this: IPerformanceCounters): Cardinal; cdecl; + IPerformanceCounters_getMaxCounterIndexPtr = function(this: IPerformanceCounters): Cardinal; cdecl; + IPerformanceCounters_getObjectIdPtr = function(this: IPerformanceCounters; index: Cardinal): Cardinal; cdecl; + IPerformanceCounters_getObjectNamePtr = function(this: IPerformanceCounters; index: Cardinal): PAnsiChar; cdecl; + IPerformanceCounters_getObjectCountersPtr = function(this: IPerformanceCounters; index: Cardinal): Int64Ptr; cdecl; + IPerformanceStats_getElapsedTimePtr = function(this: IPerformanceStats): QWord; cdecl; + IPerformanceStats_getFetchedRecordsPtr = function(this: IPerformanceStats): QWord; cdecl; + IPerformanceStats_getCountersPtr = function(this: IPerformanceStats; group: Cardinal): IPerformanceCounters; cdecl; VersionedVTable = class version: NativeInt; @@ -3052,10 +3068,11 @@ TraceTransactionVTable = class(VersionedVTable) getPerf: ITraceTransaction_getPerfPtr; getInitialID: ITraceTransaction_getInitialIDPtr; getPreviousID: ITraceTransaction_getPreviousIDPtr; + getPerfStats: ITraceTransaction_getPerfStatsPtr; end; ITraceTransaction = class(IVersioned) - const VERSION = 3; + const VERSION = 4; const ISOLATION_CONSISTENCY = Cardinal(1); const ISOLATION_CONCURRENCY = Cardinal(2); const ISOLATION_READ_COMMITTED_RECVER = Cardinal(3); @@ -3069,6 +3086,7 @@ ITraceTransaction = class(IVersioned) function getPerf(): PerformanceInfoPtr; function getInitialID(): Int64; function getPreviousID(): Int64; + function getPerfStats(): IPerformanceStats; end; ITraceTransactionImpl = class(ITraceTransaction) @@ -3081,6 +3099,7 @@ ITraceTransactionImpl = class(ITraceTransaction) function getPerf(): PerformanceInfoPtr; virtual; abstract; function getInitialID(): Int64; virtual; abstract; function getPreviousID(): Int64; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; end; TraceParamsVTable = class(VersionedVTable) @@ -3108,13 +3127,15 @@ ITraceParamsImpl = class(ITraceParams) TraceStatementVTable = class(VersionedVTable) getStmtID: ITraceStatement_getStmtIDPtr; getPerf: ITraceStatement_getPerfPtr; + getPerfStats: ITraceStatement_getPerfStatsPtr; end; ITraceStatement = class(IVersioned) - const VERSION = 2; + const VERSION = 3; function getStmtID(): Int64; function getPerf(): PerformanceInfoPtr; + function getPerfStats(): IPerformanceStats; end; ITraceStatementImpl = class(ITraceStatement) @@ -3122,6 +3143,7 @@ ITraceStatementImpl = class(ITraceStatement) function getStmtID(): Int64; virtual; abstract; function getPerf(): PerformanceInfoPtr; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; end; TraceSQLStatementVTable = class(TraceStatementVTable) @@ -3133,7 +3155,7 @@ TraceSQLStatementVTable = class(TraceStatementVTable) end; ITraceSQLStatement = class(ITraceStatement) - const VERSION = 3; + const VERSION = 4; function getText(): PAnsiChar; function getPlan(): PAnsiChar; @@ -3147,6 +3169,7 @@ ITraceSQLStatementImpl = class(ITraceSQLStatement) function getStmtID(): Int64; virtual; abstract; function getPerf(): PerformanceInfoPtr; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; function getText(): PAnsiChar; virtual; abstract; function getPlan(): PAnsiChar; virtual; abstract; function getInputs(): ITraceParams; virtual; abstract; @@ -3161,7 +3184,7 @@ TraceBLRStatementVTable = class(TraceStatementVTable) end; ITraceBLRStatement = class(ITraceStatement) - const VERSION = 3; + const VERSION = 4; function getData(): BytePtr; function getDataLength(): Cardinal; @@ -3173,6 +3196,7 @@ ITraceBLRStatementImpl = class(ITraceBLRStatement) function getStmtID(): Int64; virtual; abstract; function getPerf(): PerformanceInfoPtr; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; function getData(): BytePtr; virtual; abstract; function getDataLength(): Cardinal; virtual; abstract; function getText(): PAnsiChar; virtual; abstract; @@ -3229,10 +3253,11 @@ TraceProcedureVTable = class(VersionedVTable) getStmtID: ITraceProcedure_getStmtIDPtr; getPlan: ITraceProcedure_getPlanPtr; getExplainedPlan: ITraceProcedure_getExplainedPlanPtr; + getPerfStats: ITraceProcedure_getPerfStatsPtr; end; ITraceProcedure = class(IVersioned) - const VERSION = 3; + const VERSION = 4; function getProcName(): PAnsiChar; function getInputs(): ITraceParams; @@ -3240,6 +3265,7 @@ ITraceProcedure = class(IVersioned) function getStmtID(): Int64; function getPlan(): PAnsiChar; function getExplainedPlan(): PAnsiChar; + function getPerfStats(): IPerformanceStats; end; ITraceProcedureImpl = class(ITraceProcedure) @@ -3251,6 +3277,7 @@ ITraceProcedureImpl = class(ITraceProcedure) function getStmtID(): Int64; virtual; abstract; function getPlan(): PAnsiChar; virtual; abstract; function getExplainedPlan(): PAnsiChar; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; end; TraceFunctionVTable = class(VersionedVTable) @@ -3261,10 +3288,11 @@ TraceFunctionVTable = class(VersionedVTable) getStmtID: ITraceFunction_getStmtIDPtr; getPlan: ITraceFunction_getPlanPtr; getExplainedPlan: ITraceFunction_getExplainedPlanPtr; + getPerfStats: ITraceFunction_getPerfStatsPtr; end; ITraceFunction = class(IVersioned) - const VERSION = 3; + const VERSION = 4; function getFuncName(): PAnsiChar; function getInputs(): ITraceParams; @@ -3273,6 +3301,7 @@ ITraceFunction = class(IVersioned) function getStmtID(): Int64; function getPlan(): PAnsiChar; function getExplainedPlan(): PAnsiChar; + function getPerfStats(): IPerformanceStats; end; ITraceFunctionImpl = class(ITraceFunction) @@ -3285,6 +3314,7 @@ ITraceFunctionImpl = class(ITraceFunction) function getStmtID(): Int64; virtual; abstract; function getPlan(): PAnsiChar; virtual; abstract; function getExplainedPlan(): PAnsiChar; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; end; TraceTriggerVTable = class(VersionedVTable) @@ -3296,10 +3326,11 @@ TraceTriggerVTable = class(VersionedVTable) getStmtID: ITraceTrigger_getStmtIDPtr; getPlan: ITraceTrigger_getPlanPtr; getExplainedPlan: ITraceTrigger_getExplainedPlanPtr; + getPerfStats: ITraceTrigger_getPerfStatsPtr; end; ITraceTrigger = class(IVersioned) - const VERSION = 3; + const VERSION = 4; const TYPE_ALL = Cardinal(0); const TYPE_BEFORE = Cardinal(1); const TYPE_AFTER = Cardinal(2); @@ -3312,6 +3343,7 @@ ITraceTrigger = class(IVersioned) function getStmtID(): Int64; function getPlan(): PAnsiChar; function getExplainedPlan(): PAnsiChar; + function getPerfStats(): IPerformanceStats; end; ITraceTriggerImpl = class(ITraceTrigger) @@ -3325,6 +3357,7 @@ ITraceTriggerImpl = class(ITraceTrigger) function getStmtID(): Int64; virtual; abstract; function getPlan(): PAnsiChar; virtual; abstract; function getExplainedPlan(): PAnsiChar; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; end; TraceServiceConnectionVTable = class(TraceConnectionVTable) @@ -3389,16 +3422,18 @@ TraceSweepInfoVTable = class(VersionedVTable) getOAT: ITraceSweepInfo_getOATPtr; getNext: ITraceSweepInfo_getNextPtr; getPerf: ITraceSweepInfo_getPerfPtr; + getPerfStats: ITraceSweepInfo_getPerfStatsPtr; end; ITraceSweepInfo = class(IVersioned) - const VERSION = 2; + const VERSION = 3; function getOIT(): Int64; function getOST(): Int64; function getOAT(): Int64; function getNext(): Int64; function getPerf(): PerformanceInfoPtr; + function getPerfStats(): IPerformanceStats; end; ITraceSweepInfoImpl = class(ITraceSweepInfo) @@ -3409,6 +3444,7 @@ ITraceSweepInfoImpl = class(ITraceSweepInfo) function getOAT(): Int64; virtual; abstract; function getNext(): Int64; virtual; abstract; function getPerf(): PerformanceInfoPtr; virtual; abstract; + function getPerfStats(): IPerformanceStats; virtual; abstract; end; TraceLogWriterVTable = class(ReferenceCountedVTable) @@ -4025,6 +4061,77 @@ IProfilerStatsImpl = class(IProfilerStats) function getElapsedTicks(): QWord; virtual; abstract; end; + PerformanceCountersVTable = class(VersionedVTable) + getObjectCount: IPerformanceCounters_getObjectCountPtr; + getMaxCounterIndex: IPerformanceCounters_getMaxCounterIndexPtr; + getObjectId: IPerformanceCounters_getObjectIdPtr; + getObjectName: IPerformanceCounters_getObjectNamePtr; + getObjectCounters: IPerformanceCounters_getObjectCountersPtr; + end; + + IPerformanceCounters = class(IVersioned) + const VERSION = 2; + const PAGE_FETCHES = Cardinal(0); + const PAGE_READS = Cardinal(1); + const PAGE_MARKS = Cardinal(2); + const PAGE_WRITES = Cardinal(3); + const RECORD_SEQ_READS = Cardinal(0); + const RECORD_IDX_READS = Cardinal(1); + const RECORD_UPDATES = Cardinal(2); + const RECORD_INSERTS = Cardinal(3); + const RECORD_DELETES = Cardinal(4); + const RECORD_BACKOUTS = Cardinal(5); + const RECORD_PURGES = Cardinal(6); + const RECORD_EXPUNGES = Cardinal(7); + const RECORD_LOCKS = Cardinal(8); + const RECORD_WAITS = Cardinal(9); + const RECORD_CONFLICTS = Cardinal(10); + const RECORD_BACK_READS = Cardinal(11); + const RECORD_FRAGMENT_READS = Cardinal(12); + const RECORD_RPT_READS = Cardinal(13); + const RECORD_IMGC = Cardinal(14); + + function getObjectCount(): Cardinal; + function getMaxCounterIndex(): Cardinal; + function getObjectId(index: Cardinal): Cardinal; + function getObjectName(index: Cardinal): PAnsiChar; + function getObjectCounters(index: Cardinal): Int64Ptr; + end; + + IPerformanceCountersImpl = class(IPerformanceCounters) + constructor create; + + function getObjectCount(): Cardinal; virtual; abstract; + function getMaxCounterIndex(): Cardinal; virtual; abstract; + function getObjectId(index: Cardinal): Cardinal; virtual; abstract; + function getObjectName(index: Cardinal): PAnsiChar; virtual; abstract; + function getObjectCounters(index: Cardinal): Int64Ptr; virtual; abstract; + end; + + PerformanceStatsVTable = class(VersionedVTable) + getElapsedTime: IPerformanceStats_getElapsedTimePtr; + getFetchedRecords: IPerformanceStats_getFetchedRecordsPtr; + getCounters: IPerformanceStats_getCountersPtr; + end; + + IPerformanceStats = class(IVersioned) + const VERSION = 2; + const COUNTER_GROUP_PAGES = Cardinal(0); + const COUNTER_GROUP_TABLES = Cardinal(1); + + function getElapsedTime(): QWord; + function getFetchedRecords(): QWord; + function getCounters(group: Cardinal): IPerformanceCounters; + end; + + IPerformanceStatsImpl = class(IPerformanceStats) + constructor create; + + function getElapsedTime(): QWord; virtual; abstract; + function getFetchedRecords(): QWord; virtual; abstract; + function getCounters(group: Cardinal): IPerformanceCounters; virtual; abstract; + end; + {$IFNDEF NO_FBCLIENT} function fb_get_master_interface : IMaster; cdecl; external 'fbclient'; {$ENDIF} @@ -9100,6 +9207,16 @@ function ITraceTransaction.getPreviousID(): Int64; end; end; +function ITraceTransaction.getPerfStats(): IPerformanceStats; +begin + if (vTable.version < 4) then begin + Result := nil; + end + else begin + Result := TraceTransactionVTable(vTable).getPerfStats(Self); + end; +end; + function ITraceParams.getCount(): Cardinal; begin Result := TraceParamsVTable(vTable).getCount(Self); @@ -9132,6 +9249,16 @@ function ITraceStatement.getPerf(): PerformanceInfoPtr; Result := TraceStatementVTable(vTable).getPerf(Self); end; +function ITraceStatement.getPerfStats(): IPerformanceStats; +begin + if (vTable.version < 3) then begin + Result := nil; + end + else begin + Result := TraceStatementVTable(vTable).getPerfStats(Self); + end; +end; + function ITraceSQLStatement.getText(): PAnsiChar; begin Result := TraceSQLStatementVTable(vTable).getText(Self); @@ -9247,6 +9374,16 @@ function ITraceProcedure.getExplainedPlan(): PAnsiChar; end; end; +function ITraceProcedure.getPerfStats(): IPerformanceStats; +begin + if (vTable.version < 4) then begin + Result := nil; + end + else begin + Result := TraceProcedureVTable(vTable).getPerfStats(Self); + end; +end; + function ITraceFunction.getFuncName(): PAnsiChar; begin Result := TraceFunctionVTable(vTable).getFuncName(Self); @@ -9297,6 +9434,16 @@ function ITraceFunction.getExplainedPlan(): PAnsiChar; end; end; +function ITraceFunction.getPerfStats(): IPerformanceStats; +begin + if (vTable.version < 4) then begin + Result := nil; + end + else begin + Result := TraceFunctionVTable(vTable).getPerfStats(Self); + end; +end; + function ITraceTrigger.getTriggerName(): PAnsiChar; begin Result := TraceTriggerVTable(vTable).getTriggerName(Self); @@ -9352,6 +9499,16 @@ function ITraceTrigger.getExplainedPlan(): PAnsiChar; end; end; +function ITraceTrigger.getPerfStats(): IPerformanceStats; +begin + if (vTable.version < 4) then begin + Result := nil; + end + else begin + Result := TraceTriggerVTable(vTable).getPerfStats(Self); + end; +end; + function ITraceServiceConnection.getServiceID(): Pointer; begin Result := TraceServiceConnectionVTable(vTable).getServiceID(Self); @@ -9412,6 +9569,16 @@ function ITraceSweepInfo.getPerf(): PerformanceInfoPtr; Result := TraceSweepInfoVTable(vTable).getPerf(Self); end; +function ITraceSweepInfo.getPerfStats(): IPerformanceStats; +begin + if (vTable.version < 3) then begin + Result := nil; + end + else begin + Result := TraceSweepInfoVTable(vTable).getPerfStats(Self); + end; +end; + function ITraceLogWriter.write(buf: Pointer; size: Cardinal): Cardinal; begin Result := TraceLogWriterVTable(vTable).write(Self, buf, size); @@ -10049,6 +10216,46 @@ function IProfilerStats.getElapsedTicks(): QWord; Result := ProfilerStatsVTable(vTable).getElapsedTicks(Self); end; +function IPerformanceCounters.getObjectCount(): Cardinal; +begin + Result := PerformanceCountersVTable(vTable).getObjectCount(Self); +end; + +function IPerformanceCounters.getMaxCounterIndex(): Cardinal; +begin + Result := PerformanceCountersVTable(vTable).getMaxCounterIndex(Self); +end; + +function IPerformanceCounters.getObjectId(index: Cardinal): Cardinal; +begin + Result := PerformanceCountersVTable(vTable).getObjectId(Self, index); +end; + +function IPerformanceCounters.getObjectName(index: Cardinal): PAnsiChar; +begin + Result := PerformanceCountersVTable(vTable).getObjectName(Self, index); +end; + +function IPerformanceCounters.getObjectCounters(index: Cardinal): Int64Ptr; +begin + Result := PerformanceCountersVTable(vTable).getObjectCounters(Self, index); +end; + +function IPerformanceStats.getElapsedTime(): QWord; +begin + Result := PerformanceStatsVTable(vTable).getElapsedTime(Self); +end; + +function IPerformanceStats.getFetchedRecords(): QWord; +begin + Result := PerformanceStatsVTable(vTable).getFetchedRecords(Self); +end; + +function IPerformanceStats.getCounters(group: Cardinal): IPerformanceCounters; +begin + Result := PerformanceStatsVTable(vTable).getCounters(Self, group); +end; + var IVersionedImpl_vTable: VersionedVTable; @@ -15428,6 +15635,16 @@ function ITraceTransactionImpl_getPreviousIDDispatcher(this: ITraceTransaction): end end; +function ITraceTransactionImpl_getPerfStatsDispatcher(this: ITraceTransaction): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceTransactionImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + var ITraceTransactionImpl_vTable: TraceTransactionVTable; @@ -15494,6 +15711,16 @@ function ITraceStatementImpl_getPerfDispatcher(this: ITraceStatement): Performan end end; +function ITraceStatementImpl_getPerfStatsDispatcher(this: ITraceStatement): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceStatementImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + var ITraceStatementImpl_vTable: TraceStatementVTable; @@ -15522,6 +15749,16 @@ function ITraceSQLStatementImpl_getPerfDispatcher(this: ITraceSQLStatement): Per end end; +function ITraceSQLStatementImpl_getPerfStatsDispatcher(this: ITraceSQLStatement): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceSQLStatementImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + function ITraceSQLStatementImpl_getTextDispatcher(this: ITraceSQLStatement): PAnsiChar; cdecl; begin Result := nil; @@ -15600,6 +15837,16 @@ function ITraceBLRStatementImpl_getPerfDispatcher(this: ITraceBLRStatement): Per end end; +function ITraceBLRStatementImpl_getPerfStatsDispatcher(this: ITraceBLRStatement): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceBLRStatementImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + function ITraceBLRStatementImpl_getDataDispatcher(this: ITraceBLRStatement): BytePtr; cdecl; begin Result := nil; @@ -15774,6 +16021,16 @@ function ITraceProcedureImpl_getExplainedPlanDispatcher(this: ITraceProcedure): end end; +function ITraceProcedureImpl_getPerfStatsDispatcher(this: ITraceProcedure): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceProcedureImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + var ITraceProcedureImpl_vTable: TraceProcedureVTable; @@ -15852,6 +16109,16 @@ function ITraceFunctionImpl_getExplainedPlanDispatcher(this: ITraceFunction): PA end end; +function ITraceFunctionImpl_getPerfStatsDispatcher(this: ITraceFunction): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceFunctionImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + var ITraceFunctionImpl_vTable: TraceFunctionVTable; @@ -15940,6 +16207,16 @@ function ITraceTriggerImpl_getExplainedPlanDispatcher(this: ITraceTrigger): PAns end end; +function ITraceTriggerImpl_getPerfStatsDispatcher(this: ITraceTrigger): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceTriggerImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + var ITraceTriggerImpl_vTable: TraceTriggerVTable; @@ -16174,6 +16451,16 @@ function ITraceSweepInfoImpl_getPerfDispatcher(this: ITraceSweepInfo): Performan end end; +function ITraceSweepInfoImpl_getPerfStatsDispatcher(this: ITraceSweepInfo): IPerformanceStats; cdecl; +begin + Result := nil; + try + Result := ITraceSweepInfoImpl(this).getPerfStats(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + var ITraceSweepInfoImpl_vTable: TraceSweepInfoVTable; @@ -17543,6 +17830,102 @@ constructor IProfilerStatsImpl.create; vTable := IProfilerStatsImpl_vTable; end; +function IPerformanceCountersImpl_getObjectCountDispatcher(this: IPerformanceCounters): Cardinal; cdecl; +begin + Result := 0; + try + Result := IPerformanceCountersImpl(this).getObjectCount(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IPerformanceCountersImpl_getMaxCounterIndexDispatcher(this: IPerformanceCounters): Cardinal; cdecl; +begin + Result := 0; + try + Result := IPerformanceCountersImpl(this).getMaxCounterIndex(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IPerformanceCountersImpl_getObjectIdDispatcher(this: IPerformanceCounters; index: Cardinal): Cardinal; cdecl; +begin + Result := 0; + try + Result := IPerformanceCountersImpl(this).getObjectId(index); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IPerformanceCountersImpl_getObjectNameDispatcher(this: IPerformanceCounters; index: Cardinal): PAnsiChar; cdecl; +begin + Result := nil; + try + Result := IPerformanceCountersImpl(this).getObjectName(index); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IPerformanceCountersImpl_getObjectCountersDispatcher(this: IPerformanceCounters; index: Cardinal): Int64Ptr; cdecl; +begin + Result := nil; + try + Result := IPerformanceCountersImpl(this).getObjectCounters(index); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +var + IPerformanceCountersImpl_vTable: PerformanceCountersVTable; + +constructor IPerformanceCountersImpl.create; +begin + vTable := IPerformanceCountersImpl_vTable; +end; + +function IPerformanceStatsImpl_getElapsedTimeDispatcher(this: IPerformanceStats): QWord; cdecl; +begin + Result := 0; + try + Result := IPerformanceStatsImpl(this).getElapsedTime(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IPerformanceStatsImpl_getFetchedRecordsDispatcher(this: IPerformanceStats): QWord; cdecl; +begin + Result := 0; + try + Result := IPerformanceStatsImpl(this).getFetchedRecords(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IPerformanceStatsImpl_getCountersDispatcher(this: IPerformanceStats; group: Cardinal): IPerformanceCounters; cdecl; +begin + Result := nil; + try + Result := IPerformanceStatsImpl(this).getCounters(group); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +var + IPerformanceStatsImpl_vTable: PerformanceStatsVTable; + +constructor IPerformanceStatsImpl.create; +begin + vTable := IPerformanceStatsImpl_vTable; +end; + constructor FbException.create(status: IStatus); begin inherited Create('FbException'); @@ -18308,7 +18691,7 @@ initialization ITraceDatabaseConnectionImpl_vTable.getDatabaseName := @ITraceDatabaseConnectionImpl_getDatabaseNameDispatcher; ITraceTransactionImpl_vTable := TraceTransactionVTable.create; - ITraceTransactionImpl_vTable.version := 3; + ITraceTransactionImpl_vTable.version := 4; ITraceTransactionImpl_vTable.getTransactionID := @ITraceTransactionImpl_getTransactionIDDispatcher; ITraceTransactionImpl_vTable.getReadOnly := @ITraceTransactionImpl_getReadOnlyDispatcher; ITraceTransactionImpl_vTable.getWait := @ITraceTransactionImpl_getWaitDispatcher; @@ -18316,6 +18699,7 @@ initialization ITraceTransactionImpl_vTable.getPerf := @ITraceTransactionImpl_getPerfDispatcher; ITraceTransactionImpl_vTable.getInitialID := @ITraceTransactionImpl_getInitialIDDispatcher; ITraceTransactionImpl_vTable.getPreviousID := @ITraceTransactionImpl_getPreviousIDDispatcher; + ITraceTransactionImpl_vTable.getPerfStats := @ITraceTransactionImpl_getPerfStatsDispatcher; ITraceParamsImpl_vTable := TraceParamsVTable.create; ITraceParamsImpl_vTable.version := 3; @@ -18324,14 +18708,16 @@ initialization ITraceParamsImpl_vTable.getTextUTF8 := @ITraceParamsImpl_getTextUTF8Dispatcher; ITraceStatementImpl_vTable := TraceStatementVTable.create; - ITraceStatementImpl_vTable.version := 2; + ITraceStatementImpl_vTable.version := 3; ITraceStatementImpl_vTable.getStmtID := @ITraceStatementImpl_getStmtIDDispatcher; ITraceStatementImpl_vTable.getPerf := @ITraceStatementImpl_getPerfDispatcher; + ITraceStatementImpl_vTable.getPerfStats := @ITraceStatementImpl_getPerfStatsDispatcher; ITraceSQLStatementImpl_vTable := TraceSQLStatementVTable.create; - ITraceSQLStatementImpl_vTable.version := 3; + ITraceSQLStatementImpl_vTable.version := 4; ITraceSQLStatementImpl_vTable.getStmtID := @ITraceSQLStatementImpl_getStmtIDDispatcher; ITraceSQLStatementImpl_vTable.getPerf := @ITraceSQLStatementImpl_getPerfDispatcher; + ITraceSQLStatementImpl_vTable.getPerfStats := @ITraceSQLStatementImpl_getPerfStatsDispatcher; ITraceSQLStatementImpl_vTable.getText := @ITraceSQLStatementImpl_getTextDispatcher; ITraceSQLStatementImpl_vTable.getPlan := @ITraceSQLStatementImpl_getPlanDispatcher; ITraceSQLStatementImpl_vTable.getInputs := @ITraceSQLStatementImpl_getInputsDispatcher; @@ -18339,9 +18725,10 @@ initialization ITraceSQLStatementImpl_vTable.getExplainedPlan := @ITraceSQLStatementImpl_getExplainedPlanDispatcher; ITraceBLRStatementImpl_vTable := TraceBLRStatementVTable.create; - ITraceBLRStatementImpl_vTable.version := 3; + ITraceBLRStatementImpl_vTable.version := 4; ITraceBLRStatementImpl_vTable.getStmtID := @ITraceBLRStatementImpl_getStmtIDDispatcher; ITraceBLRStatementImpl_vTable.getPerf := @ITraceBLRStatementImpl_getPerfDispatcher; + ITraceBLRStatementImpl_vTable.getPerfStats := @ITraceBLRStatementImpl_getPerfStatsDispatcher; ITraceBLRStatementImpl_vTable.getData := @ITraceBLRStatementImpl_getDataDispatcher; ITraceBLRStatementImpl_vTable.getDataLength := @ITraceBLRStatementImpl_getDataLengthDispatcher; ITraceBLRStatementImpl_vTable.getText := @ITraceBLRStatementImpl_getTextDispatcher; @@ -18359,16 +18746,17 @@ initialization ITraceContextVariableImpl_vTable.getVarValue := @ITraceContextVariableImpl_getVarValueDispatcher; ITraceProcedureImpl_vTable := TraceProcedureVTable.create; - ITraceProcedureImpl_vTable.version := 3; + ITraceProcedureImpl_vTable.version := 4; ITraceProcedureImpl_vTable.getProcName := @ITraceProcedureImpl_getProcNameDispatcher; ITraceProcedureImpl_vTable.getInputs := @ITraceProcedureImpl_getInputsDispatcher; ITraceProcedureImpl_vTable.getPerf := @ITraceProcedureImpl_getPerfDispatcher; ITraceProcedureImpl_vTable.getStmtID := @ITraceProcedureImpl_getStmtIDDispatcher; ITraceProcedureImpl_vTable.getPlan := @ITraceProcedureImpl_getPlanDispatcher; ITraceProcedureImpl_vTable.getExplainedPlan := @ITraceProcedureImpl_getExplainedPlanDispatcher; + ITraceProcedureImpl_vTable.getPerfStats := @ITraceProcedureImpl_getPerfStatsDispatcher; ITraceFunctionImpl_vTable := TraceFunctionVTable.create; - ITraceFunctionImpl_vTable.version := 3; + ITraceFunctionImpl_vTable.version := 4; ITraceFunctionImpl_vTable.getFuncName := @ITraceFunctionImpl_getFuncNameDispatcher; ITraceFunctionImpl_vTable.getInputs := @ITraceFunctionImpl_getInputsDispatcher; ITraceFunctionImpl_vTable.getResult := @ITraceFunctionImpl_getResultDispatcher; @@ -18376,9 +18764,10 @@ initialization ITraceFunctionImpl_vTable.getStmtID := @ITraceFunctionImpl_getStmtIDDispatcher; ITraceFunctionImpl_vTable.getPlan := @ITraceFunctionImpl_getPlanDispatcher; ITraceFunctionImpl_vTable.getExplainedPlan := @ITraceFunctionImpl_getExplainedPlanDispatcher; + ITraceFunctionImpl_vTable.getPerfStats := @ITraceFunctionImpl_getPerfStatsDispatcher; ITraceTriggerImpl_vTable := TraceTriggerVTable.create; - ITraceTriggerImpl_vTable.version := 3; + ITraceTriggerImpl_vTable.version := 4; ITraceTriggerImpl_vTable.getTriggerName := @ITraceTriggerImpl_getTriggerNameDispatcher; ITraceTriggerImpl_vTable.getRelationName := @ITraceTriggerImpl_getRelationNameDispatcher; ITraceTriggerImpl_vTable.getAction := @ITraceTriggerImpl_getActionDispatcher; @@ -18387,6 +18776,7 @@ initialization ITraceTriggerImpl_vTable.getStmtID := @ITraceTriggerImpl_getStmtIDDispatcher; ITraceTriggerImpl_vTable.getPlan := @ITraceTriggerImpl_getPlanDispatcher; ITraceTriggerImpl_vTable.getExplainedPlan := @ITraceTriggerImpl_getExplainedPlanDispatcher; + ITraceTriggerImpl_vTable.getPerfStats := @ITraceTriggerImpl_getPerfStatsDispatcher; ITraceServiceConnectionImpl_vTable := TraceServiceConnectionVTable.create; ITraceServiceConnectionImpl_vTable.version := 3; @@ -18411,12 +18801,13 @@ initialization ITraceStatusVectorImpl_vTable.getText := @ITraceStatusVectorImpl_getTextDispatcher; ITraceSweepInfoImpl_vTable := TraceSweepInfoVTable.create; - ITraceSweepInfoImpl_vTable.version := 2; + ITraceSweepInfoImpl_vTable.version := 3; ITraceSweepInfoImpl_vTable.getOIT := @ITraceSweepInfoImpl_getOITDispatcher; ITraceSweepInfoImpl_vTable.getOST := @ITraceSweepInfoImpl_getOSTDispatcher; ITraceSweepInfoImpl_vTable.getOAT := @ITraceSweepInfoImpl_getOATDispatcher; ITraceSweepInfoImpl_vTable.getNext := @ITraceSweepInfoImpl_getNextDispatcher; ITraceSweepInfoImpl_vTable.getPerf := @ITraceSweepInfoImpl_getPerfDispatcher; + ITraceSweepInfoImpl_vTable.getPerfStats := @ITraceSweepInfoImpl_getPerfStatsDispatcher; ITraceLogWriterImpl_vTable := TraceLogWriterVTable.create; ITraceLogWriterImpl_vTable.version := 4; @@ -18600,6 +18991,20 @@ initialization IProfilerStatsImpl_vTable.version := 2; IProfilerStatsImpl_vTable.getElapsedTicks := @IProfilerStatsImpl_getElapsedTicksDispatcher; + IPerformanceCountersImpl_vTable := PerformanceCountersVTable.create; + IPerformanceCountersImpl_vTable.version := 2; + IPerformanceCountersImpl_vTable.getObjectCount := @IPerformanceCountersImpl_getObjectCountDispatcher; + IPerformanceCountersImpl_vTable.getMaxCounterIndex := @IPerformanceCountersImpl_getMaxCounterIndexDispatcher; + IPerformanceCountersImpl_vTable.getObjectId := @IPerformanceCountersImpl_getObjectIdDispatcher; + IPerformanceCountersImpl_vTable.getObjectName := @IPerformanceCountersImpl_getObjectNameDispatcher; + IPerformanceCountersImpl_vTable.getObjectCounters := @IPerformanceCountersImpl_getObjectCountersDispatcher; + + IPerformanceStatsImpl_vTable := PerformanceStatsVTable.create; + IPerformanceStatsImpl_vTable.version := 2; + IPerformanceStatsImpl_vTable.getElapsedTime := @IPerformanceStatsImpl_getElapsedTimeDispatcher; + IPerformanceStatsImpl_vTable.getFetchedRecords := @IPerformanceStatsImpl_getFetchedRecordsDispatcher; + IPerformanceStatsImpl_vTable.getCounters := @IPerformanceStatsImpl_getCountersDispatcher; + finalization IVersionedImpl_vTable.destroy; IReferenceCountedImpl_vTable.destroy; @@ -18699,5 +19104,7 @@ finalization IProfilerPluginImpl_vTable.destroy; IProfilerSessionImpl_vTable.destroy; IProfilerStatsImpl_vTable.destroy; + IPerformanceCountersImpl_vTable.destroy; + IPerformanceStatsImpl_vTable.destroy; end. diff --git a/src/jrd/Monitoring.cpp b/src/jrd/Monitoring.cpp index ea09c3daa32..07f64418679 100644 --- a/src/jrd/Monitoring.cpp +++ b/src/jrd/Monitoring.cpp @@ -1451,36 +1451,39 @@ void Monitoring::putStatistics(SnapshotData::DumpRecord& record, const RuntimeSt // logical I/O statistics (table wise) - for (auto iter(statistics.getRelCounters()); iter; ++iter) + for (const auto& counts : statistics.getTableCounters()) { + if (counts.isEmpty()) + continue; + const auto rec_stat_id = getGlobalId(fb_utils::genUniqueId()); record.reset(rel_mon_tab_stats); record.storeGlobalId(f_mon_tab_stat_id, id); record.storeInteger(f_mon_tab_stat_group, stat_group); - record.storeTableIdSchemaName(f_mon_tab_sch_name, (*iter).getRelationId()); - record.storeTableIdObjectName(f_mon_tab_name, (*iter).getRelationId()); + record.storeTableIdSchemaName(f_mon_tab_sch_name, counts.getGroupId()); + record.storeTableIdObjectName(f_mon_tab_name, counts.getGroupId()); record.storeGlobalId(f_mon_tab_rec_stat_id, rec_stat_id); record.write(); record.reset(rel_mon_rec_stats); record.storeGlobalId(f_mon_rec_stat_id, rec_stat_id); record.storeInteger(f_mon_rec_stat_group, stat_group); - record.storeInteger(f_mon_rec_seq_reads, (*iter)[RecordStatType::SEQ_READS]); - record.storeInteger(f_mon_rec_idx_reads, (*iter)[RecordStatType::IDX_READS]); - record.storeInteger(f_mon_rec_inserts, (*iter)[RecordStatType::INSERTS]); - record.storeInteger(f_mon_rec_updates, (*iter)[RecordStatType::UPDATES]); - record.storeInteger(f_mon_rec_deletes, (*iter)[RecordStatType::DELETES]); - record.storeInteger(f_mon_rec_backouts, (*iter)[RecordStatType::BACKOUTS]); - record.storeInteger(f_mon_rec_purges, (*iter)[RecordStatType::PURGES]); - record.storeInteger(f_mon_rec_expunges, (*iter)[RecordStatType::EXPUNGES]); - record.storeInteger(f_mon_rec_locks, (*iter)[RecordStatType::LOCKS]); - record.storeInteger(f_mon_rec_waits, (*iter)[RecordStatType::WAITS]); - record.storeInteger(f_mon_rec_conflicts, (*iter)[RecordStatType::CONFLICTS]); - record.storeInteger(f_mon_rec_bkver_reads, (*iter)[RecordStatType::BACK_READS]); - record.storeInteger(f_mon_rec_frg_reads, (*iter)[RecordStatType::FRAGMENT_READS]); - record.storeInteger(f_mon_rec_rpt_reads, (*iter)[RecordStatType::RPT_READS]); - record.storeInteger(f_mon_rec_imgc, (*iter)[RecordStatType::IMGC]); + record.storeInteger(f_mon_rec_seq_reads, counts[RecordStatType::SEQ_READS]); + record.storeInteger(f_mon_rec_idx_reads, counts[RecordStatType::IDX_READS]); + record.storeInteger(f_mon_rec_inserts, counts[RecordStatType::INSERTS]); + record.storeInteger(f_mon_rec_updates, counts[RecordStatType::UPDATES]); + record.storeInteger(f_mon_rec_deletes, counts[RecordStatType::DELETES]); + record.storeInteger(f_mon_rec_backouts, counts[RecordStatType::BACKOUTS]); + record.storeInteger(f_mon_rec_purges, counts[RecordStatType::PURGES]); + record.storeInteger(f_mon_rec_expunges, counts[RecordStatType::EXPUNGES]); + record.storeInteger(f_mon_rec_locks, counts[RecordStatType::LOCKS]); + record.storeInteger(f_mon_rec_waits, counts[RecordStatType::WAITS]); + record.storeInteger(f_mon_rec_conflicts, counts[RecordStatType::CONFLICTS]); + record.storeInteger(f_mon_rec_bkver_reads, counts[RecordStatType::BACK_READS]); + record.storeInteger(f_mon_rec_frg_reads, counts[RecordStatType::FRAGMENT_READS]); + record.storeInteger(f_mon_rec_rpt_reads, counts[RecordStatType::RPT_READS]); + record.storeInteger(f_mon_rec_imgc, counts[RecordStatType::IMGC]); record.write(); } } diff --git a/src/jrd/RuntimeStatistics.cpp b/src/jrd/RuntimeStatistics.cpp index 95630662623..d4ce6474d6f 100644 --- a/src/jrd/RuntimeStatistics.cpp +++ b/src/jrd/RuntimeStatistics.cpp @@ -33,52 +33,75 @@ namespace Jrd { GlobalPtr RuntimeStatistics::dummy; -void RuntimeStatistics::adjustRelStats(const RuntimeStatistics& baseStats, const RuntimeStatistics& newStats) +void RuntimeStatistics::adjust(const RuntimeStatistics& baseStats, const RuntimeStatistics& newStats) { - if (baseStats.relChgNumber == newStats.relChgNumber) + if (baseStats.allChgNumber == newStats.allChgNumber) return; - relChgNumber++; + allChgNumber++; + for (size_t i = 0; i < GLOBAL_ITEMS; ++i) + values[i] += newStats.values[i] - baseStats.values[i]; + + if (baseStats.pageChgNumber != newStats.pageChgNumber) + { + pageChgNumber++; + pageCounters.adjust(baseStats.pageCounters, newStats.pageCounters); + } + + if (baseStats.tabChgNumber != newStats.tabChgNumber) + { + tabChgNumber++; + tableCounters.adjust(baseStats.tableCounters, newStats.tableCounters); + } +} - auto locate = [this](SLONG relId) -> FB_SIZE_T +void RuntimeStatistics::adjustPageStats(RuntimeStatistics& baseStats, const RuntimeStatistics& newStats) +{ + if (baseStats.allChgNumber == newStats.allChgNumber) + return; + + allChgNumber++; + for (size_t i = 0; i < PAGE_TOTAL_ITEMS; ++i) { - FB_SIZE_T pos; - if (!rel_counts.find(relId, pos)) - rel_counts.insert(pos, RelationCounts(relId)); - return pos; - }; + const SINT64 delta = newStats.values[i] - baseStats.values[i]; - auto baseIter = baseStats.rel_counts.begin(), newIter = newStats.rel_counts.begin(); - const auto baseEnd = baseStats.rel_counts.end(), newEnd = newStats.rel_counts.end(); + values[i] += delta; + baseStats.values[i] += delta; + } +} + +template +void RuntimeStatistics::GroupedCountsArray::adjust(const GroupedCountsArray& baseStats, const GroupedCountsArray& newStats) +{ + auto baseIter = baseStats.m_counts.begin(), newIter = newStats.m_counts.begin(); + const auto baseEnd = baseStats.m_counts.end(), newEnd = newStats.m_counts.end(); - // The loop below assumes that newStats cannot miss relations existing in baseStats, + // The loop below assumes that newStats cannot miss objects existing in baseStats, // this must be always the case as long as newStats is an incremented version of baseStats while (newIter != newEnd || baseIter != baseEnd) { if (baseIter == baseEnd) { - // Relation exists in newStats but missing in baseStats - const auto newRelId = newIter->getRelationId(); - rel_counts[locate(newRelId)] += *newIter++; + // Object exists in newStats but missing in baseStats + const auto newId = newIter->getGroupId(); + (*this)[newId] += *newIter++; } else if (newIter != newEnd) { - const auto baseRelId = baseIter->getRelationId(); - const auto newRelId = newIter->getRelationId(); + const auto baseId = baseIter->getGroupId(); + const auto newId = newIter->getGroupId(); - if (newRelId == baseRelId) + if (newId == baseId) { - // Relation exists in both newStats and baseStats - fb_assert(baseRelId == newRelId); - const auto pos = locate(baseRelId); - rel_counts[pos] -= *baseIter++; - rel_counts[pos] += *newIter++; + // Object exists in both newStats and baseStats + (*this)[newId] += *newIter++; + (*this)[newId] -= *baseIter++; } - else if (newRelId < baseRelId) + else if (newId < baseId) { - // Relation exists in newStats but missing in baseStats - rel_counts[locate(newRelId)] += *newIter++; + // Object exists in newStats but missing in baseStats + (*this)[newId] += *newIter++; } else fb_assert(false); // should never happen @@ -88,114 +111,23 @@ void RuntimeStatistics::adjustRelStats(const RuntimeStatistics& baseStats, const } } -PerformanceInfo* RuntimeStatistics::computeDifference(Attachment* att, - const RuntimeStatistics& new_stat, - PerformanceInfo& dest, - TraceCountsArray& temp, - ObjectsArray& tempNames) +void RuntimeStatistics::setToDiff(const RuntimeStatistics& newStats) { - // NOTE: we do not initialize dest.pin_time. This must be done by the caller - - // Calculate database-level statistics for (size_t i = 0; i < GLOBAL_ITEMS; i++) - values[i] = new_stat.values[i] - values[i]; + values[i] = newStats.values[i] - values[i]; - dest.pin_counters = values; - - // Calculate relation-level statistics - temp.clear(); - tempNames.clear(); - - // This loop assumes that base array is smaller than new one - RelCounters::iterator base_cnts = rel_counts.begin(); - bool base_found = (base_cnts != rel_counts.end()); - - for (const auto& new_cnts : new_stat.rel_counts) + for (const auto& newCounts : newStats.pageCounters) { - const SLONG rel_id = new_cnts.getRelationId(); - - if (base_found && base_cnts->getRelationId() == rel_id) - { - // Point TraceCounts to counts array from baseline object - if (base_cnts->setToDiff(new_cnts)) - { - jrd_rel* const relation = - rel_id < static_cast(att->att_relations->count()) ? - (*att->att_relations)[rel_id] : NULL; - - TraceCounts traceCounts; - traceCounts.trc_relation_id = rel_id; - traceCounts.trc_counters = base_cnts->getCounterVector(); - - if (relation) - { - auto& tempName = tempNames.add(); - tempName = relation->rel_name.toQuotedString(); - traceCounts.trc_relation_name = tempName.c_str(); - } - else - traceCounts.trc_relation_name = nullptr; - - temp.add(traceCounts); - } - - ++base_cnts; - base_found = (base_cnts != rel_counts.end()); - } - else - { - jrd_rel* const relation = - rel_id < static_cast(att->att_relations->count()) ? - (*att->att_relations)[rel_id] : NULL; - - // Point TraceCounts to counts array from object with updated counters - TraceCounts traceCounts; - traceCounts.trc_relation_id = rel_id; - traceCounts.trc_counters = new_cnts.getCounterVector(); - - if (relation) - { - auto& tempName = tempNames.add(); - tempName = relation->rel_name.toQuotedString(); - traceCounts.trc_relation_name = tempName.c_str(); - } - else - traceCounts.trc_relation_name = nullptr; - - temp.add(traceCounts); - } - }; - - dest.pin_count = temp.getCount(); - dest.pin_tables = temp.begin(); - - return &dest; -} - -void RuntimeStatistics::adjust(const RuntimeStatistics& baseStats, const RuntimeStatistics& newStats) -{ - if (baseStats.allChgNumber == newStats.allChgNumber) - return; - - allChgNumber++; - for (size_t i = 0; i < GLOBAL_ITEMS; ++i) - values[i] += newStats.values[i] - baseStats.values[i]; - - adjustRelStats(baseStats, newStats); -} - -void RuntimeStatistics::adjustPageStats(RuntimeStatistics& baseStats, const RuntimeStatistics& newStats) -{ - if (baseStats.allChgNumber == newStats.allChgNumber) - return; + const auto pageSpaceId = newCounts.getGroupId(); + if (!pageCounters[pageSpaceId].setToDiff(newCounts)) + pageCounters.remove(pageSpaceId); + } - allChgNumber++; - for (size_t i = 0; i < PAGE_TOTAL_ITEMS; ++i) + for (const auto& newCounts : newStats.tableCounters) { - const SINT64 delta = newStats.values[i] - baseStats.values[i]; - - values[i] += delta; - baseStats.values[i] += delta; + const auto relationId = newCounts.getGroupId(); + if (!tableCounters[relationId].setToDiff(newCounts)) + tableCounters.remove(relationId); } } diff --git a/src/jrd/RuntimeStatistics.h b/src/jrd/RuntimeStatistics.h index d340cd0d079..3db58932ab0 100644 --- a/src/jrd/RuntimeStatistics.h +++ b/src/jrd/RuntimeStatistics.h @@ -29,19 +29,11 @@ #include "../common/classes/init.h" #include "../common/classes/tree.h" #include "../common/classes/File.h" +#include "../jrd/ini.h" +#include "../jrd/pag.h" #include -namespace Firebird -{ - -// declared in firebird/Interface.h -struct TraceCounts; -struct PerformanceInfo; - -} // namespace Firebird - - namespace Jrd { class Attachment; @@ -49,7 +41,6 @@ class Database; class thread_db; class jrd_rel; -typedef Firebird::HalfStaticArray TraceCountsArray; // Runtime statistics @@ -92,7 +83,7 @@ class RuntimeStatistics : protected Firebird::AutoStorage // // dimitr: Currently, they include page-level and record-level counters. // However, this is not strictly required to maintain global record-level counters, - // as they may be aggregated from the rel_counts array on demand. This would slow down + // as they may be aggregated from the tableCounters array on demand. This would slow down // the retrieval of counters but save some CPU cycles inside tdbb->bumpStats(). // As long as public struct PerformanceInfo don't include record-level counters, // this is not going to affect any existing applications/plugins. @@ -135,6 +126,24 @@ class RuntimeStatistics : protected Firebird::AutoStorage return *this; } + bool setToDiff(const CountsVector& other) + { + bool ret = false; + + for (size_t i = 0; i < m_counters.size(); i++) + { + if ( (m_counters[i] = other.m_counters[i] - m_counters[i]) ) + ret = true; + } + + return ret; + } + + static unsigned getVectorCapacity() + { + return (unsigned) SIZE; + } + const SINT64* getCounterVector() const { return m_counters.data(); @@ -156,104 +165,187 @@ class RuntimeStatistics : protected Firebird::AutoStorage std::array m_counters = {}; }; - // Performance counters for individual table - - class RelationCounts : public CountsVector + template + class CountsGroup : public CountsVector { public: - explicit RelationCounts(SLONG relation_id) - : m_relation_id(relation_id) + typedef Key ID; + + explicit CountsGroup(ID id) + : m_id(id) + {} + + ID getGroupId() const { + return m_id; } - SLONG getRelationId() const + CountsGroup& operator+=(const CountsGroup& other) { - return m_relation_id; + fb_assert(m_id == other.m_id); + CountsVector::operator+=(other); + return *this; } - bool setToDiff(const RelationCounts& other) + CountsGroup& operator-=(const CountsGroup& other) { - fb_assert(m_relation_id == other.m_relation_id); + fb_assert(m_id == other.m_id); + CountsVector::operator-=(other); + return *this; + } - bool ret = false; + bool setToDiff(const CountsGroup& other) + { + fb_assert(m_id == other.m_id); + return CountsVector::setToDiff(other); + } - for (size_t i = 0; i < m_counters.size(); i++) + inline static const ID& generate(const CountsGroup& item) + { + return item.m_id; + } + + private: + ID m_id; + }; + + template + class GroupedCountsArray + { + typedef typename Counts::ID ID; + typedef Firebird::SortedArray< + Counts, Firebird::EmptyStorage, ID, Counts> SortedCountsArray; + typedef typename SortedCountsArray::const_iterator ConstIterator; + + public: + GroupedCountsArray(MemoryPool& pool, FB_SIZE_T capacity) + : m_counts(pool, capacity) + {} + + GroupedCountsArray(MemoryPool& pool, const GroupedCountsArray& other) + : m_counts(pool, other.m_counts.getCapacity()) + {} + + Counts& operator[](ID id) + { + if ((m_lastPos != (FB_SIZE_T) ~0 && m_counts[m_lastPos].getGroupId() == id) || + // if m_lastPos is mispositioned + m_counts.find(id, m_lastPos)) { - if ( (m_counters[i] = other.m_counters[i] - m_counters[i]) ) - ret = true; + return m_counts[m_lastPos]; } - return ret; + Counts counts(id); + m_counts.insert(m_lastPos, counts); + return m_counts[m_lastPos]; } - RelationCounts& operator+=(const RelationCounts& other) + unsigned getCount() const { - fb_assert(m_relation_id == other.m_relation_id); - CountsVector::operator+=(other); - return *this; + return m_counts.getCount(); } - RelationCounts& operator-=(const RelationCounts& other) + static unsigned getVectorCapacity() { - fb_assert(m_relation_id == other.m_relation_id); - CountsVector::operator-=(other); - return *this; + return Counts::getVectorCapacity(); + } + + void remove(ID id) + { + if ((m_lastPos != (FB_SIZE_T) ~0 && m_counts[m_lastPos].getGroupId() == id) || + // if m_lastPos is mispositioned + m_counts.find(id, m_lastPos)) + { + m_counts.remove(m_lastPos); + m_lastPos = (FB_SIZE_T) ~0; + } + } + + void reset() + { + m_counts.clear(); + m_lastPos = (FB_SIZE_T) ~0; } - inline static const SLONG& generate(const RelationCounts& item) + ConstIterator begin() const { - return item.m_relation_id; + return m_counts.begin(); } + ConstIterator end() const + { + return m_counts.end(); + } + + void adjust(const GroupedCountsArray& baseStats, const GroupedCountsArray& newStats); + private: - SLONG m_relation_id; + SortedCountsArray m_counts; + FB_SIZE_T m_lastPos = (FB_SIZE_T) ~0; }; - typedef Firebird::SortedArray, - SLONG, RelationCounts> RelCounters; - public: + typedef GroupedCountsArray > PageCounters; + typedef GroupedCountsArray > TableCounters; + RuntimeStatistics() - : Firebird::AutoStorage(), rel_counts(getPool()) + : Firebird::AutoStorage(), + pageCounters(getPool(), DB_PAGE_SPACE + 1), + tableCounters(getPool(), rel_MAX) { reset(); } explicit RuntimeStatistics(MemoryPool& pool) - : Firebird::AutoStorage(pool), rel_counts(getPool()) + : Firebird::AutoStorage(pool), + pageCounters(getPool(), DB_PAGE_SPACE + 1), + tableCounters(getPool(), rel_MAX) { reset(); } RuntimeStatistics(const RuntimeStatistics& other) - : Firebird::AutoStorage(), rel_counts(getPool()) + : Firebird::AutoStorage(), + pageCounters(getPool(), other.pageCounters), + tableCounters(getPool(), other.tableCounters) { memcpy(values, other.values, sizeof(values)); - rel_counts = other.rel_counts; + + pageCounters = other.pageCounters; + tableCounters = other.tableCounters; allChgNumber = other.allChgNumber; - relChgNumber = other.relChgNumber; + pageChgNumber = other.pageChgNumber; + tabChgNumber = other.tabChgNumber; } RuntimeStatistics(MemoryPool& pool, const RuntimeStatistics& other) - : Firebird::AutoStorage(pool), rel_counts(getPool()) + : Firebird::AutoStorage(pool), + pageCounters(getPool(), other.pageCounters), + tableCounters(getPool(), other.tableCounters) { memcpy(values, other.values, sizeof(values)); - rel_counts = other.rel_counts; + + pageCounters = other.pageCounters; + tableCounters = other.tableCounters; allChgNumber = other.allChgNumber; - relChgNumber = other.relChgNumber; + pageChgNumber = other.pageChgNumber; + tabChgNumber = other.tabChgNumber; } ~RuntimeStatistics() = default; void reset() { - memset(values, 0, sizeof values); - rel_counts.clear(); - rel_last_pos = (FB_SIZE_T) ~0; + memset(values, 0, sizeof(values)); + + pageCounters.reset(); + tableCounters.reset(); + allChgNumber = 0; - relChgNumber = 0; + pageChgNumber = 0; + tabChgNumber = 0; } const SINT64& operator[](const PageStatType type) const @@ -262,11 +354,17 @@ class RuntimeStatistics : protected Firebird::AutoStorage return values[index]; } - void bumpValue(const PageStatType type, SINT64 delta = 1) + void bumpValue(const PageStatType type, ULONG pageSpaceId, SINT64 delta = 1) { + ++allChgNumber; const auto index = static_cast(type); values[index] += delta; - ++allChgNumber; + + if (isValid()) // optimization for non-trivial data access + { + ++pageChgNumber; + pageCounters[pageSpaceId][type] += delta; + } } const SINT64& operator[](const RecordStatType type) const @@ -279,42 +377,28 @@ class RuntimeStatistics : protected Firebird::AutoStorage { SINT64 value = 0; - for (const auto& counts : rel_counts) + for (const auto& counts : tableCounters) value += counts[type]; return value; } - void bumpValue(const RecordStatType type, SINT64 delta = 1) + void bumpValue(const RecordStatType type, SLONG relationId, SINT64 delta = 1) { + ++allChgNumber; const auto index = static_cast(type); values[PAGE_TOTAL_ITEMS + index] += delta; - ++allChgNumber; - } - void bumpValue(const RecordStatType type, SLONG relation_id, SINT64 delta = 1) - { - ++allChgNumber; - ++relChgNumber; - - if ((rel_last_pos != (FB_SIZE_T)~0 && rel_counts[rel_last_pos].getRelationId() == relation_id) || - // if rel_last_pos is mispositioned - rel_counts.find(relation_id, rel_last_pos)) - { - rel_counts[rel_last_pos][type] += delta; - } - else + if (isValid()) // optimization for non-trivial data access { - RelationCounts counts(relation_id); - counts[type] += delta; - rel_counts.insert(rel_last_pos, counts); + ++tabChgNumber; + tableCounters[relationId][type] += delta; } } // Calculate difference between counts stored in this object and current // counts of given request. Counts stored in object are destroyed. - Firebird::PerformanceInfo* computeDifference(Attachment* att, const RuntimeStatistics& new_stat, - Firebird::PerformanceInfo& dest, TraceCountsArray& temp, Firebird::ObjectsArray& tempNames); + void setToDiff(const RuntimeStatistics& newStats); // Add difference between newStats and baseStats to our counters // (newStats and baseStats must be "in-sync") @@ -331,16 +415,27 @@ class RuntimeStatistics : protected Firebird::AutoStorage memcpy(values, other.values, sizeof(values)); allChgNumber = other.allChgNumber; - if (relChgNumber != other.relChgNumber) + if (pageChgNumber != other.pageChgNumber) { - rel_counts = other.rel_counts; - relChgNumber = other.relChgNumber; + pageCounters = other.pageCounters; + pageChgNumber = other.pageChgNumber; + } + + if (tabChgNumber != other.tabChgNumber) + { + tableCounters = other.tableCounters; + tabChgNumber = other.tabChgNumber; } } return *this; } + bool isValid() const + { + return (this != &dummy); + } + static RuntimeStatistics* getDummy() { return &dummy; @@ -364,62 +459,28 @@ class RuntimeStatistics : protected Firebird::AutoStorage SINT64 m_counter = 0; }; - template class Iterator + const PageCounters& getPageCounters() const { - public: - explicit Iterator(const T& counts) - : m_iter(counts.begin()), m_end(counts.end()) - { - advance(); - } - - void operator++() - { - m_iter++; - advance(); - } - - typename T::const_reference operator*() const - { - return *m_iter; - } - - operator bool() const - { - return (m_iter != m_end); - } - - private: - typename T::const_iterator m_iter; - const typename T::const_iterator m_end; - - void advance() - { - while (m_iter != m_end && m_iter->isEmpty()) - m_iter++; - } - }; - - typedef Iterator RelationIterator; + return pageCounters; + } - RelationIterator getRelCounters() const + const TableCounters& getTableCounters() const { - return RelationIterator(rel_counts); + return tableCounters; } private: - void adjustRelStats(const RuntimeStatistics& baseStats, const RuntimeStatistics& newStats); - SINT64 values[GLOBAL_ITEMS]; - RelCounters rel_counts; - FB_SIZE_T rel_last_pos; + PageCounters pageCounters; + TableCounters tableCounters; - // These three numbers are used in adjust() and assign() methods as "generation" + // These numbers are used in adjust() and assign() methods as "generation" // values in order to avoid costly operations when two instances of RuntimeStatistics // contain equal counters values. This is intended to use *only* with the // same pair of class instances, as in Request. ULONG allChgNumber; // incremented when any counter changes - ULONG relChgNumber; // incremented when relation counter changes + ULONG pageChgNumber; // incremented when page counter changes + ULONG tabChgNumber; // incremented when table counter changes // This dummy RuntimeStatistics is used instead of missing elements in tdbb, // helping us to avoid conditional checks in time-critical places of code. diff --git a/src/jrd/cch.cpp b/src/jrd/cch.cpp index d660ce17bf1..ecb7ed947bb 100644 --- a/src/jrd/cch.cpp +++ b/src/jrd/cch.cpp @@ -930,9 +930,10 @@ void CCH_fetch_page(thread_db* tdbb, WIN* window, const bool read_shadow) pag* page = bdb->bdb_buffer; bdb->bdb_incarnation = ++bcb->bcb_page_incarnation; - tdbb->bumpStats(PageStatType::READS); + const ULONG pageSpaceId = bdb->bdb_page.getPageSpaceID(); + tdbb->bumpStats(PageStatType::READS, pageSpaceId); - PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(bdb->bdb_page.getPageSpaceID()); + const auto pageSpace = dbb->dbb_page_manager.findPageSpace(pageSpaceId); fb_assert(pageSpace); jrd_file* file = pageSpace->file; @@ -1011,7 +1012,7 @@ void CCH_fetch_page(thread_db* tdbb, WIN* window, const bool read_shadow) { diff_page = bm->getPageIndex(tdbb, bdb->bdb_page.getPageNum()); NBAK_TRACE(("Reading page %d:%06d, state=%d, diff page=%d", - bdb->bdb_page.getPageSpaceID(), bdb->bdb_page.getPageNum(), (int) backupState, diff_page)); + pageSpaceId, bdb->bdb_page.getPageNum(), (int) backupState, diff_page)); } // In merge mode, if we are reading past beyond old end of file and page is in .delta file @@ -1021,7 +1022,7 @@ void CCH_fetch_page(thread_db* tdbb, WIN* window, const bool read_shadow) fb_assert(bdb->bdb_page == window->win_page); NBAK_TRACE(("Reading page %d:%06d, state=%d, diff page=%d from DISK", - bdb->bdb_page.getPageSpaceID(), bdb->bdb_page.getPageNum(), (int) backupState, diff_page)); + pageSpaceId, bdb->bdb_page.getPageNum(), (int) backupState, diff_page)); // Read page from disk as normal Pio io(file, bdb, isTempPage, read_shadow, pageSpace); @@ -1701,14 +1702,16 @@ void CCH_mark(thread_db* tdbb, WIN* window, bool mark_system, bool must_write) SET_TDBB(tdbb); Database* dbb = tdbb->getDatabase(); - tdbb->bumpStats(PageStatType::MARKS); + + const ULONG pageSpaceId = window->win_page.getPageSpaceID(); + tdbb->bumpStats(PageStatType::MARKS, pageSpaceId); BufferControl* bcb = dbb->dbb_bcb; if (!(bdb->bdb_flags & BDB_writer)) BUGCHECK(208); // msg 208 page not accessed for write - CCH_TRACE(("MARK %d:%06d", window->win_page.getPageSpaceID(), window->win_page.getPageNum())); + CCH_TRACE(("MARK %d:%06d", pageSpaceId, window->win_page.getPageNum())); // A LATCH_mark is needed before the BufferDesc can be marked. // This prevents a write while the page is being modified. @@ -3805,6 +3808,8 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s BufferControl* bcb = dbb->dbb_bcb; Attachment* att = tdbb->getAttachment(); + const ULONG pageSpaceId = page.getPageSpaceID(); + if (att && att->att_bdb_cache) { if (BufferDesc* bdb = att->att_bdb_cache->get(page)) @@ -3814,7 +3819,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s if (bdb->bdb_page == page) { recentlyUsed(bdb); - tdbb->bumpStats(PageStatType::FETCHES); + tdbb->bumpStats(PageStatType::FETCHES, pageSpaceId); return bdb; } @@ -3857,7 +3862,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s if (bdb->bdb_page == page) { recentlyUsed(bdb); - tdbb->bumpStats(PageStatType::FETCHES); + tdbb->bumpStats(PageStatType::FETCHES, pageSpaceId); cacheBuffer(att, bdb); return bdb; } @@ -3897,7 +3902,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s { bdb->downgrade(syncType); recentlyUsed(bdb); - tdbb->bumpStats(PageStatType::FETCHES); + tdbb->bumpStats(PageStatType::FETCHES, pageSpaceId); cacheBuffer(att, bdb); return bdb; } @@ -3941,7 +3946,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s else recentlyUsed(bdb); } - tdbb->bumpStats(PageStatType::FETCHES); + tdbb->bumpStats(PageStatType::FETCHES, pageSpaceId); cacheBuffer(att, bdb); return bdb; } @@ -3959,7 +3964,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s continue; } recentlyUsed(bdb2); - tdbb->bumpStats(PageStatType::FETCHES); + tdbb->bumpStats(PageStatType::FETCHES, pageSpaceId); cacheBuffer(att, bdb2); } else @@ -4914,7 +4919,8 @@ static bool write_page(thread_db* tdbb, BufferDesc* bdb, FbStatusVector* const s // I won't wipe out the if() itself to allow my changes be verified easily by others if (true) { - tdbb->bumpStats(PageStatType::WRITES); + const ULONG pageSpaceId = bdb->bdb_page.getPageSpaceID(); + tdbb->bumpStats(PageStatType::WRITES, pageSpaceId); // write out page to main database file, and to any // shadows, making a special case of the header page @@ -4952,8 +4958,7 @@ static bool write_page(thread_db* tdbb, BufferDesc* bdb, FbStatusVector* const s gds__trace(buffer); #endif - PageSpace* pageSpace = - dbb->dbb_page_manager.findPageSpace(bdb->bdb_page.getPageSpaceID()); + const auto pageSpace = dbb->dbb_page_manager.findPageSpace(pageSpaceId); fb_assert(pageSpace); const bool isTempPage = pageSpace->isTemporary(); diff --git a/src/jrd/inf.cpp b/src/jrd/inf.cpp index 7d0d42b9eb6..60f0b6a29bd 100644 --- a/src/jrd/inf.cpp +++ b/src/jrd/inf.cpp @@ -273,11 +273,11 @@ void INF_database_info(thread_db* tdbb, resultBuffer.clear(); FB_SIZE_T bufferLength = 0; - for (auto iter(recordStats->getRelCounters()); iter; ++iter) + for (const auto& counts : recordStats->getTableCounters()) { - if (const SINT64 n = (*iter)[type]) + if (const SINT64 n = counts[type]) { - const USHORT relationId = (*iter).getRelationId(); + const USHORT relationId = counts.getGroupId(); const USHORT length = INF_convert(n, numBuffer); const FB_SIZE_T newBufferLength = bufferLength + length + sizeof(USHORT); resultBuffer.grow(newBufferLength); diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index 2e3644c89cc..d6b2896c0db 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -612,44 +612,35 @@ class thread_db final : public Firebird::ThreadData tdbb_flags |= TDBB_sweeper; } - void bumpStats(const PageStatType type, SINT64 delta = 1) + void bumpStats(const PageStatType type, ULONG pageSpaceId, SINT64 delta = 1) { - reqStat->bumpValue(type, delta); - traStat->bumpValue(type, delta); - attStat->bumpValue(type, delta); + fb_assert(pageSpaceId != INVALID_PAGE_SPACE); + + // [0] element stores statistics for temporary page spaces + if (PageSpace::isTemporary(pageSpaceId)) + pageSpaceId = 0; + + reqStat->bumpValue(type, pageSpaceId, delta); + traStat->bumpValue(type, pageSpaceId, delta); + attStat->bumpValue(type, pageSpaceId, delta); if ((tdbb_flags & TDBB_async) && !attachment) - dbbStat->bumpValue(type, delta); + dbbStat->bumpValue(type, pageSpaceId, delta); - // else dbbStat is adjusted from attStat, see Attachment::mergeAsyncStats() + // else dbbStat is adjusted from attStat, see Attachment::mergeStats() } - void bumpStats(const RecordStatType type, SLONG relation_id, SINT64 delta = 1) + void bumpStats(const RecordStatType type, SLONG relationId, SINT64 delta = 1) { - // We don't bump counters for dbbStat here, they're merged from attStats on demand - - reqStat->bumpValue(type, delta); - traStat->bumpValue(type, delta); - attStat->bumpValue(type, delta); - - const RuntimeStatistics* const dummyStat = RuntimeStatistics::getDummy(); - // We expect that at least attStat is present (not a dummy object) - fb_assert(attStat != dummyStat); - - // Relation statistics is a quite complex beast, so a conditional check - // does not hurt. It also allows to avoid races while accessing the static - // dummy object concurrently. + fb_assert(attStat != RuntimeStatistics::getDummy()); - if (reqStat != dummyStat) - reqStat->bumpValue(type, relation_id, delta); + reqStat->bumpValue(type, relationId, delta); + traStat->bumpValue(type, relationId, delta); + attStat->bumpValue(type, relationId, delta); - if (traStat != dummyStat) - traStat->bumpValue(type, relation_id, delta); - - if (attStat != dummyStat) - attStat->bumpValue(type, relation_id, delta); + // We don't bump counters for dbbStat here, they're merged from attStats on demand } ISC_STATUS getCancelState(ISC_STATUS* secondary = NULL); diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index ba658f45dc2..b2844c4f8a2 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -4330,7 +4330,7 @@ void TraceSweepEvent::endSweepRelation(jrd_rel* relation) fb_utils::query_performance_counter() - m_relation_clock, 0); - m_sweep_info.setPerf(stats.getPerf()); + m_sweep_info.setStats(&stats); TraceConnectionImpl conn(att); TraceManager* trace_mgr = att->att_trace_manager; @@ -4377,7 +4377,7 @@ void TraceSweepEvent::report(ntrace_process_state_t state) TraceRuntimeStats stats(att, &m_base_stats, &att->att_stats, finiTime, 0); - m_sweep_info.setPerf(stats.getPerf()); + m_sweep_info.setStats(&stats); trace_mgr->event_sweep(&conn, &m_sweep_info, state); if (state == ITracePlugin::SWEEP_STATE_FAILED || state == ITracePlugin::SWEEP_STATE_FINISHED) diff --git a/src/jrd/trace/TraceDSQLHelpers.h b/src/jrd/trace/TraceDSQLHelpers.h index 3d3f0cd78cf..1941edcac42 100644 --- a/src/jrd/trace/TraceDSQLHelpers.h +++ b/src/jrd/trace/TraceDSQLHelpers.h @@ -173,7 +173,7 @@ class TraceDSQLExecute fb_utils::query_performance_counter() - m_start_clock, m_dsqlRequest->req_fetch_rowcount); - TraceSQLStatementImpl stmt(m_dsqlRequest, stats.getPerf(), m_data); + TraceSQLStatementImpl stmt(m_dsqlRequest, &stats, m_data); TraceManager::event_dsql_execute(m_attachment, m_dsqlRequest->req_transaction, &stmt, false, result); m_dsqlRequest->req_fetch_baseline = NULL; @@ -233,7 +233,7 @@ class TraceDSQLFetch &m_dsqlRequest->getRequest()->req_stats, m_dsqlRequest->req_fetch_elapsed, m_dsqlRequest->req_fetch_rowcount); - TraceSQLStatementImpl stmt(m_dsqlRequest, stats.getPerf(), nullptr); + TraceSQLStatementImpl stmt(m_dsqlRequest, &stats, nullptr); TraceManager::event_dsql_execute(m_attachment, m_dsqlRequest->req_transaction, &stmt, false, result); diff --git a/src/jrd/trace/TraceJrdHelpers.h b/src/jrd/trace/TraceJrdHelpers.h index 2dbbfdabd97..994116042ec 100644 --- a/src/jrd/trace/TraceJrdHelpers.h +++ b/src/jrd/trace/TraceJrdHelpers.h @@ -75,7 +75,7 @@ class TraceTransactionEnd fb_utils::query_performance_counter() - m_start_clock, 0); TraceConnectionImpl conn(attachment); - TraceTransactionImpl tran(m_transaction, stats.getPerf(), m_prevID); + TraceTransactionImpl tran(m_transaction, &stats, m_prevID); attachment->att_trace_manager->event_transaction_end(&conn, &tran, m_commit, m_retain, result); m_baseline = NULL; @@ -205,7 +205,7 @@ class TraceProcExecute TraceConnectionImpl conn(attachment); TraceTransactionImpl tran(transaction); - TraceProcedureImpl proc(m_request, stats.getPerf()); + TraceProcedureImpl proc(m_request, &stats); const auto trace_mgr = attachment->att_trace_manager; trace_mgr->event_proc_execute(&conn, &tran, &proc, false, result); @@ -268,7 +268,7 @@ class TraceProcFetch TraceConnectionImpl conn(attachment); TraceTransactionImpl tran(transaction); - TraceProcedureImpl proc(m_request, stats.getPerf()); + TraceProcedureImpl proc(m_request, &stats); const auto trace_mgr = attachment->att_trace_manager; trace_mgr->event_proc_execute(&conn, &tran, &proc, false, result); @@ -402,7 +402,7 @@ class TraceFuncExecute TraceTransactionImpl tran(transaction); TraceDscFromMsg inputs(m_request->getStatement()->function->getInputFormat(), m_inMsg, m_inMsgLength); - TraceFunctionImpl func(m_request, stats.getPerf(), inputs, value); + TraceFunctionImpl func(m_request, &stats, inputs, value); const auto trace_mgr = attachment->att_trace_manager; trace_mgr->event_func_execute(&conn, &tran, &func, false, result); @@ -566,7 +566,7 @@ class TraceTrigExecute TraceConnectionImpl conn(attachment); TraceTransactionImpl tran(transaction); - TraceTriggerImpl trig(m_which, m_request, stats.getPerf()); + TraceTriggerImpl trig(m_which, m_request, &stats); const auto trace_mgr = attachment->att_trace_manager; trace_mgr->event_trigger_execute(&conn, &tran, &trig, false, result); @@ -689,7 +689,7 @@ class TraceBlrExecute TraceConnectionImpl conn(m_tdbb->getAttachment()); TraceTransactionImpl tran(m_tdbb->getTransaction()); - TraceBLRStatementImpl stmt(m_request->getStatement(), stats.getPerf()); + TraceBLRStatementImpl stmt(m_request->getStatement(), &stats); TraceManager* trace_mgr = m_tdbb->getAttachment()->att_trace_manager; trace_mgr->event_blr_execute(&conn, &tran, &stmt, result); diff --git a/src/jrd/trace/TraceObjects.cpp b/src/jrd/trace/TraceObjects.cpp index 6b2d4f251df..86a503b922e 100644 --- a/src/jrd/trace/TraceObjects.cpp +++ b/src/jrd/trace/TraceObjects.cpp @@ -261,16 +261,6 @@ const char* TraceSQLStatementImpl::getTextUTF8() return m_textUTF8.c_str(); } -PerformanceInfo* TraceSQLStatementImpl::getPerf() -{ - return m_perf; -} - -ITraceParams* TraceSQLStatementImpl::getInputs() -{ - return &m_inputs; -} - /// TraceSQLStatementImpl::DSQLParamsImpl @@ -633,24 +623,62 @@ const char* TraceServiceImpl::getRemoteProcessName() /// TraceRuntimeStats -TraceRuntimeStats::TraceRuntimeStats(Attachment* att, RuntimeStatistics* baseline, RuntimeStatistics* stats, - SINT64 clock, SINT64 records_fetched) +TraceRuntimeStats::TraceRuntimeStats(Attachment* attachment, + RuntimeStatistics* baseline, RuntimeStatistics* stats, + SINT64 clock, SINT64 recordsFetched) { + memset(&m_info, 0, sizeof(m_info)); m_info.pin_time = clock * 1000 / fb_utils::query_performance_frequency(); - m_info.pin_records_fetched = records_fetched; + m_info.pin_records_fetched = recordsFetched; + m_info.pin_counters = m_globalCounters; if (baseline && stats) - baseline->computeDifference(att, *stats, m_info, m_counts, m_tempNames); + { + baseline->setToDiff(*stats); + + m_globalCounters[PerformanceInfo::FETCHES] = (*baseline)[PageStatType::FETCHES]; + m_globalCounters[PerformanceInfo::READS] = (*baseline)[PageStatType::READS]; + m_globalCounters[PerformanceInfo::MARKS] = (*baseline)[PageStatType::MARKS]; + m_globalCounters[PerformanceInfo::WRITES] = (*baseline)[PageStatType::WRITES]; + + auto getTablespaceName = [&](unsigned id) -> Firebird::string + { + return ""; // TODO + }; + + m_pageCounters.reset(&baseline->getPageCounters(), getTablespaceName); + + auto getTableName = [&](unsigned id) -> Firebird::string + { + if (attachment->att_relations && id < attachment->att_relations->count()) + { + if (const auto relation = (*attachment->att_relations)[id]) + return relation->rel_name.toQuotedString(); + } + + return ""; + }; + + m_tableCounters.reset(&baseline->getTableCounters(), getTableName); + + m_info.pin_count = m_tableCounters.getObjectCount(); + m_legacyCounts.resize(m_info.pin_count); + m_info.pin_tables = m_legacyCounts.begin(); + + for (unsigned i = 0; i < m_info.pin_count; i++) + { + m_info.pin_tables[i].trc_relation_id = m_tableCounters.getObjectId(i); + m_info.pin_tables[i].trc_relation_name = m_tableCounters.getObjectName(i); + m_info.pin_tables[i].trc_counters = m_tableCounters.getObjectCounters(i); + i++; + } + } else { - // Report all zero counts for the moment. - memset(&m_info, 0, sizeof(m_info)); - m_info.pin_counters = m_dummy_counts; + memset(m_globalCounters, 0, sizeof(m_globalCounters)); } } -SINT64 TraceRuntimeStats::m_dummy_counts[RuntimeStatistics::GLOBAL_ITEMS] = {0}; - /// TraceStatusVectorImpl diff --git a/src/jrd/trace/TraceObjects.h b/src/jrd/trace/TraceObjects.h index f20cd8b0038..36a7ec8ca64 100644 --- a/src/jrd/trace/TraceObjects.h +++ b/src/jrd/trace/TraceObjects.h @@ -96,6 +96,127 @@ class StatementHolder }; +class TraceRuntimeStats : + public Firebird::AutoIface > +{ + static constexpr unsigned GLOBAL_COUNTERS = 4; // PerformanceInfo::{FETCHES|READS|MARKS|WRITES} + + template + class GenericCounters : + public Firebird::AutoIface, Firebird::CheckStatusWrapper> > + { + public: + GenericCounters() = default; + ~GenericCounters() = default; + + void reset(const T* counters, std::function getName) + { + m_counts = counters; + m_names.clear(); + + for (const auto& counts : *m_counts) + m_names.add(getName(counts.getGroupId())); + } + + // PerformanceCounts implementation + unsigned getObjectCount() + { + return m_counts ? m_counts->getCount() : 0; + } + + unsigned getMaxCounterIndex() + { + return T::getVectorCapacity() - 1; + } + + unsigned getObjectId(unsigned index) + { + if (m_counts && index < m_counts->getCount()) + { + const auto iter = m_counts->begin() + index; + return iter->getGroupId(); + } + + return 0; + } + + const char* getObjectName(unsigned index) + { + if (index < m_names.getCount()) + return m_names[index].c_str(); + + return nullptr; + } + + const SINT64* getObjectCounters(unsigned index) + { + if (m_counts && index < m_counts->getCount()) + { + const auto iter = m_counts->begin() + index; + return iter->getCounterVector(); + } + + return nullptr; + } + + private: + Firebird::ObjectsArray m_names; + const T* m_counts = nullptr; + }; + + typedef GenericCounters PageCounters; + typedef GenericCounters TableCounters; + +public: + TraceRuntimeStats(Attachment* att, RuntimeStatistics* baseline, RuntimeStatistics* stats, + SINT64 clock, SINT64 recordsFetched); + + // PerformanceStats implementation + FB_UINT64 getElapsedTime() + { + return m_info.pin_time; + } + + FB_UINT64 getFetchedRecords() + { + return m_info.pin_records_fetched; + } + + Firebird::IPerformanceCounters* getCounters(unsigned group) + { + Firebird::IPerformanceCounters* counters = nullptr; + + switch (group) + { + case IPerformanceStats::COUNTER_GROUP_PAGES: + counters = &m_pageCounters; + break; + + case IPerformanceStats::COUNTER_GROUP_TABLES: + counters = &m_tableCounters; + break; + + default: + fb_assert(false); + } + + return counters; + } + + Firebird::PerformanceInfo* getInfo() + { + return &m_info; + } + +private: + Firebird::PerformanceInfo m_info; + PageCounters m_pageCounters; + TableCounters m_tableCounters; + SINT64 m_globalCounters[GLOBAL_COUNTERS]; + Firebird::HalfStaticArray m_legacyCounts; +}; + + class TraceConnectionImpl : public Firebird::AutoIface > { @@ -127,10 +248,10 @@ class TraceTransactionImpl : public Firebird::AutoIface > { public: - TraceTransactionImpl(const jrd_tra* tran, Firebird::PerformanceInfo* perf = NULL, ISC_INT64 prevID = 0) : + TraceTransactionImpl(const jrd_tra* tran, TraceRuntimeStats* stats = nullptr, ISC_INT64 prevID = 0) : m_tran(tran), - m_perf(perf), - m_prevID(prevID) + m_prevID(prevID), + m_stats(stats) {} // TraceTransaction implementation @@ -138,14 +259,27 @@ class TraceTransactionImpl : FB_BOOLEAN getReadOnly(); int getWait(); unsigned getIsolation(); - Firebird::PerformanceInfo* getPerf() { return m_perf; } ISC_INT64 getInitialID(); - ISC_INT64 getPreviousID() { return m_prevID; } + + ISC_INT64 getPreviousID() + { + return m_prevID; + } + + Firebird::PerformanceInfo* getPerf() + { + return m_stats ? m_stats->getInfo() : nullptr; + } + + Firebird::IPerformanceStats* getPerfStats() + { + return m_stats; + } private: const jrd_tra* const m_tran; - Firebird::PerformanceInfo* const m_perf; const ISC_INT64 m_prevID; + TraceRuntimeStats* const m_stats; }; @@ -161,8 +295,16 @@ class BLRPrinter : {} // TraceBLRStatement implementation - const unsigned char* getData() { return m_blr; } - unsigned getDataLength() { return m_length; } + const unsigned char* getData() + { + return m_blr; + } + + unsigned getDataLength() + { + return m_length; + } + const char* getText() { if (m_text.empty() && getDataLength()) @@ -189,18 +331,30 @@ class BLRPrinter : class TraceBLRStatementImpl : public BLRPrinter { public: - TraceBLRStatementImpl(const Statement* stmt, Firebird::PerformanceInfo* perf) : + TraceBLRStatementImpl(const Statement* stmt, TraceRuntimeStats* stats) : BLRPrinter(stmt->blr.begin(), stmt->blr.getCount()), m_stmt(stmt), - m_perf(perf) + m_stats(stats) {} - ISC_INT64 getStmtID() { return m_stmt->getStatementId(); } - Firebird::PerformanceInfo* getPerf() { return m_perf; } + ISC_INT64 getStmtID() + { + return m_stmt->getStatementId(); + } + + Firebird::PerformanceInfo* getPerf() + { + return m_stats ? m_stats->getInfo() : nullptr; + } + + Firebird::IPerformanceStats* getPerfStats() + { + return m_stats; + } private: const Statement* const m_stmt; - Firebird::PerformanceInfo* const m_perf; + TraceRuntimeStats* const m_stats; }; @@ -211,8 +365,9 @@ class TraceFailedBLRStatement : public BLRPrinter BLRPrinter(blr, length) {} - ISC_INT64 getStmtID() { return 0; } - Firebird::PerformanceInfo* getPerf() { return NULL; } + ISC_INT64 getStmtID() { return 0; } + Firebird::PerformanceInfo* getPerf() { return nullptr; } + Firebird::IPerformanceStats* getPerfStats() { return nullptr; } }; @@ -221,20 +376,23 @@ class TraceSQLStatementImpl : public StatementHolder { public: - TraceSQLStatementImpl(DsqlRequest* stmt, Firebird::PerformanceInfo* perf, const UCHAR* inputBuffer) : + TraceSQLStatementImpl(DsqlRequest* stmt, TraceRuntimeStats* stats, const UCHAR* inputBuffer) : StatementHolder(stmt ? stmt->getStatement() : nullptr), m_stmt(stmt), - m_perf(perf), - m_inputs(stmt, inputBuffer) + m_inputs(stmt, inputBuffer), + m_stats(stats) {} // TraceSQLStatement implementation ISC_INT64 getStmtID(); - Firebird::PerformanceInfo* getPerf(); - Firebird::ITraceParams* getInputs(); const char* getText(); const char* getTextUTF8(); + Firebird::ITraceParams* getInputs() + { + return &m_inputs; + } + const char* getPlan() { return ensurePlan(false); @@ -245,6 +403,16 @@ class TraceSQLStatementImpl : return ensurePlan(true); } + Firebird::PerformanceInfo* getPerf() + { + return m_stats ? m_stats->getInfo() : nullptr; + } + + Firebird::IPerformanceStats* getPerfStats() + { + return m_stats; + } + private: class DSQLParamsImpl : public Firebird::AutoIface > @@ -252,8 +420,7 @@ class TraceSQLStatementImpl : public: explicit DSQLParamsImpl(DsqlRequest* const stmt, const UCHAR* const inputBuffer) : m_stmt(stmt), m_buffer(inputBuffer) - { - } + {} FB_SIZE_T getCount(); const paramdsc* getParam(FB_SIZE_T idx); @@ -269,9 +436,9 @@ class TraceSQLStatementImpl : }; DsqlRequest* const m_stmt; - Firebird::PerformanceInfo* const m_perf; DSQLParamsImpl m_inputs; Firebird::string m_textUTF8; + TraceRuntimeStats* const m_stats; }; @@ -284,13 +451,14 @@ class TraceFailedSQLStatement : {} // TraceSQLStatement implementation - ISC_INT64 getStmtID() { return 0; } - Firebird::PerformanceInfo* getPerf() { return NULL; } - Firebird::ITraceParams* getInputs() { return NULL; } - const char* getText() { return m_text.c_str(); } - const char* getPlan() { return ""; } + ISC_INT64 getStmtID() { return 0; } + Firebird::ITraceParams* getInputs() { return nullptr; } + const char* getText() { return m_text.c_str(); } + const char* getPlan() { return ""; } const char* getTextUTF8(); - const char* getExplainedPlan() { return ""; } + const char* getExplainedPlan() { return ""; } + Firebird::PerformanceInfo* getPerf() { return nullptr; } + Firebird::IPerformanceStats* getPerfStats() { return nullptr; } private: Firebird::string& m_text; @@ -444,15 +612,15 @@ class TraceProcedureImpl : TraceProcedureImpl(const Firebird::string& name, const Statement* statement) : StatementHolder(statement), m_name(name), - m_perf(nullptr), - m_inputs(nullptr, nullptr) + m_inputs(nullptr, nullptr), + m_stats(nullptr) {} - TraceProcedureImpl(Request* request, Firebird::PerformanceInfo* perf) : + TraceProcedureImpl(Request* request, TraceRuntimeStats* stats) : StatementHolder(request), m_name(getName()), - m_perf(perf), - m_inputs(request->req_proc_caller, request->req_proc_inputs) + m_inputs(request->req_proc_caller, request->req_proc_inputs), + m_stats(stats) {} // TraceProcedure implementation @@ -466,11 +634,6 @@ class TraceProcedureImpl : return m_inputs; } - Firebird::PerformanceInfo* getPerf() - { - return m_perf; - }; - ISC_INT64 getStmtID() { return getId(); @@ -486,10 +649,20 @@ class TraceProcedureImpl : return ensurePlan(true); } + Firebird::PerformanceInfo* getPerf() + { + return m_stats ? m_stats->getInfo() : nullptr; + } + + Firebird::IPerformanceStats* getPerfStats() + { + return m_stats; + } + private: const Firebird::string m_name; - Firebird::PerformanceInfo* const m_perf; TraceDscFromValues m_inputs; + TraceRuntimeStats* const m_stats; }; @@ -501,18 +674,18 @@ class TraceFunctionImpl : TraceFunctionImpl(const Firebird::string& name, const Statement* statement) : StatementHolder(statement), m_name(name), - m_perf(nullptr), m_inputs(nullptr), - m_value(nullptr) + m_value(nullptr), + m_stats(nullptr) {} - TraceFunctionImpl(Request* request, Firebird::PerformanceInfo* perf, + TraceFunctionImpl(Request* request, TraceRuntimeStats* stats, Firebird::ITraceParams* inputs, const dsc* value) : StatementHolder(request), m_name(getName()), - m_perf(perf), m_inputs(inputs), - m_value(value) + m_value(value), + m_stats(stats) {} // TraceFunction implementation @@ -531,11 +704,6 @@ class TraceFunctionImpl : return m_value; } - Firebird::PerformanceInfo* getPerf() - { - return m_perf; - }; - ISC_INT64 getStmtID() { return getId(); @@ -551,11 +719,21 @@ class TraceFunctionImpl : return ensurePlan(true); } + Firebird::PerformanceInfo* getPerf() + { + return m_stats ? m_stats->getInfo() : nullptr; + }; + + Firebird::IPerformanceStats* getPerfStats() + { + return m_stats; + }; + private: Firebird::string m_name; - Firebird::PerformanceInfo* const m_perf; Firebird::ITraceParams* const m_inputs; TraceDscFromDsc m_value; + TraceRuntimeStats* const m_stats; }; @@ -571,17 +749,17 @@ class TraceTriggerImpl : m_relationName(relationName), m_which(which), m_action(action), - m_perf(nullptr) + m_stats(nullptr) {} - TraceTriggerImpl(int which, const Request* request, Firebird::PerformanceInfo* perf) : + TraceTriggerImpl(int which, const Request* request, TraceRuntimeStats* stats) : StatementHolder(request), m_name(getName()), m_relationName((request->req_rpb.hasData() && request->req_rpb[0].rpb_relation) ? request->req_rpb[0].rpb_relation->rel_name.toQuotedString() : ""), m_which(which), m_action(request->req_trigger_action), - m_perf(perf) + m_stats(stats) {} // TraceTrigger implementation @@ -605,11 +783,6 @@ class TraceTriggerImpl : return m_action; } - Firebird::PerformanceInfo* getPerf() - { - return m_perf; - } - ISC_INT64 getStmtID() { return getId(); @@ -625,12 +798,22 @@ class TraceTriggerImpl : return ensurePlan(true); } + Firebird::PerformanceInfo* getPerf() + { + return m_stats ? m_stats->getInfo() : nullptr; + }; + + Firebird::IPerformanceStats* getPerfStats() + { + return m_stats; + }; + private: const Firebird::string m_name; const Firebird::string m_relationName; const int m_which; const int m_action; - Firebird::PerformanceInfo* const m_perf; + TraceRuntimeStats* const m_stats; }; @@ -662,22 +845,6 @@ class TraceServiceImpl : }; -class TraceRuntimeStats -{ -public: - TraceRuntimeStats(Attachment* att, RuntimeStatistics* baseline, RuntimeStatistics* stats, - SINT64 clock, SINT64 records_fetched); - - Firebird::PerformanceInfo* getPerf() { return &m_info; } - -private: - Firebird::PerformanceInfo m_info; - TraceCountsArray m_counts; - Firebird::ObjectsArray m_tempNames; - static SINT64 m_dummy_counts[RuntimeStatistics::GLOBAL_ITEMS]; // Zero-initialized array with zero counts -}; - - class TraceInitInfoImpl : public Firebird::AutoIface > { @@ -759,14 +926,7 @@ class TraceSweepImpl : public Firebird::AutoIface > { public: - TraceSweepImpl() - { - m_oit = 0; - m_ost = 0; - m_oat = 0; - m_next = 0; - m_perf = 0; - } + TraceSweepImpl() = default; void update(const Ods::header_page* header) { @@ -776,23 +936,32 @@ class TraceSweepImpl : m_next = header->hdr_next_transaction; } - void setPerf(Firebird::PerformanceInfo* perf) + void setStats(TraceRuntimeStats* stats) { - m_perf = perf; + m_stats = stats; } ISC_INT64 getOIT() { return m_oit; }; ISC_INT64 getOST() { return m_ost; }; ISC_INT64 getOAT() { return m_oat; }; ISC_INT64 getNext() { return m_next; }; - Firebird::PerformanceInfo* getPerf() { return m_perf; }; + + Firebird::PerformanceInfo* getPerf() + { + return m_stats ? m_stats->getInfo() : nullptr; + }; + + Firebird::IPerformanceStats* getPerfStats() + { + return m_stats; + }; private: - TraNumber m_oit; - TraNumber m_ost; - TraNumber m_oat; - TraNumber m_next; - Firebird::PerformanceInfo* m_perf; + TraNumber m_oit = 0; + TraNumber m_ost = 0; + TraNumber m_oat = 0; + TraNumber m_next = 0; + TraceRuntimeStats* m_stats = nullptr; }; } // namespace Jrd diff --git a/src/utilities/ntrace/TracePluginImpl.cpp b/src/utilities/ntrace/TracePluginImpl.cpp index a962af94748..5447402550d 100644 --- a/src/utilities/ntrace/TracePluginImpl.cpp +++ b/src/utilities/ntrace/TracePluginImpl.cpp @@ -629,56 +629,78 @@ void TracePluginImpl::logRecordError(const char* action, ITraceConnection* conne logRecord(action); } -void TracePluginImpl::appendGlobalCounts(const PerformanceInfo* info) +void TracePluginImpl::appendGlobalCounts(IPerformanceStats* stats) { string temp; - temp.printf("%7" QUADFORMAT"d ms", info->pin_time); + temp.printf("%7" QUADFORMAT"d ms", stats->getElapsedTime()); record.append(temp); - ntrace_counter_t cnt; + const auto pageCounters = stats->getCounters(IPerformanceStats::COUNTER_GROUP_PAGES); + fb_assert(pageCounters); - if ((cnt = info->pin_counters[PerformanceInfo::READS]) != 0) + if (const auto count = pageCounters->getObjectCount()) { - temp.printf(", %" QUADFORMAT"d read(s)", cnt); - record.append(temp); - } + // Holds counters: IPerformanceCounters::PAGE_{FETCHES|READS|MARKS|WRITES} + static constexpr unsigned GLOBAL_COUNTERS = 4; + SINT64 globalCounters[GLOBAL_COUNTERS] = {0}; - if ((cnt = info->pin_counters[PerformanceInfo::WRITES]) != 0) - { - temp.printf(", %" QUADFORMAT"d write(s)", cnt); - record.append(temp); - } + for (unsigned i = 0; i < count; i++) + { + const auto counters = pageCounters->getObjectCounters(i); + for (unsigned j = 0; j < GLOBAL_COUNTERS; j++) + globalCounters[j] += counters[j]; + } - if ((cnt = info->pin_counters[PerformanceInfo::FETCHES]) != 0) - { - temp.printf(", %" QUADFORMAT"d fetch(es)", cnt); - record.append(temp); - } + if (const auto cnt = globalCounters[IPerformanceCounters::PAGE_READS]) + { + temp.printf(", %" QUADFORMAT"d read(s)", cnt); + record.append(temp); + } - if ((cnt = info->pin_counters[PerformanceInfo::MARKS]) != 0) - { - temp.printf(", %" QUADFORMAT"d mark(s)", cnt); - record.append(temp); + if (const auto cnt = globalCounters[IPerformanceCounters::PAGE_WRITES]) + { + temp.printf(", %" QUADFORMAT"d write(s)", cnt); + record.append(temp); + } + + if (const auto cnt = globalCounters[IPerformanceCounters::PAGE_FETCHES]) + { + temp.printf(", %" QUADFORMAT"d fetch(es)", cnt); + record.append(temp); + } + + if (const auto cnt = globalCounters[IPerformanceCounters::PAGE_MARKS]) + { + temp.printf(", %" QUADFORMAT"d mark(s)", cnt); + record.append(temp); + } } record.append(NEWLINE); } -void TracePluginImpl::appendTableCounts(const PerformanceInfo *info) +void TracePluginImpl::appendTableCounts(IPerformanceStats* stats) { - if (!config.print_perf || info->pin_count == 0) + if (!config.print_perf) return; - const TraceCounts* trc = info->pin_tables; - const TraceCounts* trc_end = trc + info->pin_count; + const auto tableCounters = stats->getCounters(IPerformanceStats::COUNTER_GROUP_TABLES); + fb_assert(tableCounters); + + const auto count = tableCounters->getObjectCount(); + if (!count) + return; FB_SIZE_T max_len = 0; - for (; trc < trc_end; trc++) + for (unsigned i = 0; i < count; i++) { - FB_SIZE_T len = fb_strlen(trc->trc_relation_name); - if (max_len < len) - max_len = len; + if (const auto relName = tableCounters->getObjectName(i)) + { + const FB_SIZE_T len = fb_strlen(relName); + if (max_len < len) + max_len = len; + } } if (max_len < 32) @@ -691,24 +713,45 @@ void TracePluginImpl::appendTableCounts(const PerformanceInfo *info) record.append(NEWLINE); string temp; - for (trc = info->pin_tables; trc < trc_end; trc++) + for (unsigned i = 0; i < count; i++) { - record.append(trc->trc_relation_name); - record.append(max_len - fb_strlen(trc->trc_relation_name), ' '); - for (int j = 0; j <= TraceCounts::EXPUNGES; j++) + const auto relName = tableCounters->getObjectName(i); + if (relName && *relName) + { + record.append(relName); + record.append(max_len - fb_strlen(relName), ' '); + } + else + { + temp.printf("Table id <%u>", tableCounters->getObjectId(i)); + record.append(temp); + record.append(max_len - temp.length(), ' '); + } + + const auto counters = tableCounters->getObjectCounters(i); + + string line; + bool nonZero = false; + for (unsigned j = IPerformanceCounters::RECORD_SEQ_READS; + j <= IPerformanceCounters::RECORD_EXPUNGES; j++) { - if (trc->trc_counters[j] == 0) + if (counters[j] == 0) { - record.append(10, ' '); + line.append(10, ' '); } else { - //fb_utils::exactNumericToStr(trc->trc_counters[j], 0, temp); + //fb_utils::exactNumericToStr(counterVector[j], 0, temp); //record.append(' ', 10 - temp.length()); - temp.printf("%10" QUADFORMAT"d", trc->trc_counters[j]); - record.append(temp); + temp.printf("%10" QUADFORMAT"d", counters[j]); + line.append(temp); + nonZero = true; } } + + if (nonZero) + record.append(line); + record.append(NEWLINE); } } @@ -1494,11 +1537,10 @@ void TracePluginImpl::log_event_transaction_end(ITraceDatabaseConnection* connec } } - PerformanceInfo* info = transaction->getPerf(); - if (info) + if (const auto stats = transaction->getPerfStats()) { - appendGlobalCounts(info); - appendTableCounts(info); + appendGlobalCounts(stats); + appendTableCounts(stats); } const char* event_type; @@ -1596,8 +1638,8 @@ void TracePluginImpl::log_event_proc_execute(ITraceDatabaseConnection* connectio return; // Do not log operation if it is below time threshold - const PerformanceInfo* info = started ? NULL : procedure->getPerf(); - if (config.time_threshold && info && info->pin_time < config.time_threshold) + IPerformanceStats* const stats = started ? nullptr : procedure->getPerfStats(); + if (config.time_threshold && stats && stats->getElapsedTime() < config.time_threshold) return; ITraceParams* params = procedure->getInputs(); @@ -1608,16 +1650,17 @@ void TracePluginImpl::log_event_proc_execute(ITraceDatabaseConnection* connectio record.append(NEWLINE); } - if (info) + if (stats) { - if (info->pin_records_fetched) + if (const auto cnt = stats->getFetchedRecords()) { string temp; - temp.printf("%" QUADFORMAT"d records fetched" NEWLINE, info->pin_records_fetched); + temp.printf("%" QUADFORMAT"d records fetched" NEWLINE, cnt); record.append(temp); } - appendGlobalCounts(info); - appendTableCounts(info); + + appendGlobalCounts(stats); + appendTableCounts(stats); } const char* event_type; @@ -1677,8 +1720,8 @@ void TracePluginImpl::log_event_func_execute(ITraceDatabaseConnection* connectio return; // Do not log operation if it is below time threshold - const PerformanceInfo* info = started ? NULL : function->getPerf(); - if (config.time_threshold && info && info->pin_time < config.time_threshold) + IPerformanceStats* const stats = started ? nullptr : function->getPerfStats(); + if (config.time_threshold && stats && stats->getElapsedTime() < config.time_threshold) return; ITraceParams* params = function->getInputs(); @@ -1699,16 +1742,17 @@ void TracePluginImpl::log_event_func_execute(ITraceDatabaseConnection* connectio } } - if (info) + if (stats) { - if (info->pin_records_fetched) + if (const auto cnt = stats->getFetchedRecords()) { string temp; - temp.printf("%" QUADFORMAT"d records fetched" NEWLINE, info->pin_records_fetched); + temp.printf("%" QUADFORMAT"d records fetched" NEWLINE, cnt); record.append(temp); } - appendGlobalCounts(info); - appendTableCounts(info); + + appendGlobalCounts(stats); + appendTableCounts(stats); } const char* event_type; @@ -1767,14 +1811,14 @@ void TracePluginImpl::log_event_trigger_execute(ITraceDatabaseConnection* connec return; // Do not log operation if it is below time threshold - const PerformanceInfo* info = started ? NULL : trigger->getPerf(); - if (config.time_threshold && info && info->pin_time < config.time_threshold) + IPerformanceStats* const stats = started ? nullptr : trigger->getPerfStats(); + if (config.time_threshold && stats && stats->getElapsedTime() < config.time_threshold) return; - if (info) + if (stats) { - appendGlobalCounts(info); - appendTableCounts(info); + appendGlobalCounts(stats); + appendTableCounts(stats); } const char* event_type; @@ -1920,8 +1964,8 @@ void TracePluginImpl::log_event_dsql_execute(ITraceDatabaseConnection* connectio return; // Do not log operation if it is below time threshold - const PerformanceInfo* info = started ? NULL : statement->getPerf(); - if (config.time_threshold && info && info->pin_time < config.time_threshold) + IPerformanceStats* const stats = started ? nullptr : statement->getPerfStats(); + if (config.time_threshold && stats && stats->getElapsedTime() < config.time_threshold) return; if (restart) @@ -1939,14 +1983,14 @@ void TracePluginImpl::log_event_dsql_execute(ITraceDatabaseConnection* connectio record.append(NEWLINE); } - if (info) + if (stats) { string temp; - temp.printf("%" QUADFORMAT"d records fetched" NEWLINE, info->pin_records_fetched); + temp.printf("%" QUADFORMAT"d records fetched" NEWLINE, stats->getFetchedRecords()); record.append(temp); - appendGlobalCounts(info); - appendTableCounts(info); + appendGlobalCounts(stats); + appendTableCounts(stats); } string event_type; @@ -2058,16 +2102,19 @@ void TracePluginImpl::log_event_blr_execute(ITraceDatabaseConnection* connection ITraceTransaction* transaction, ITraceBLRStatement* statement, ntrace_result_t req_result) { - PerformanceInfo *info = statement->getPerf(); + const auto stats = statement->getPerfStats(); // Do not log operation if it is below time threshold - if (config.time_threshold && info->pin_time < config.time_threshold) + if (config.time_threshold && stats && stats->getElapsedTime() < config.time_threshold) return; if (config.log_blr_requests) { - appendGlobalCounts(info); - appendTableCounts(info); + if (stats) + { + appendGlobalCounts(stats); + appendTableCounts(stats); + } const char* event_type; switch (req_result) @@ -2440,11 +2487,10 @@ void TracePluginImpl::log_event_sweep(ITraceDatabaseConnection* connection, ITra ); } - PerformanceInfo* info = sweep->getPerf(); - if (info) + if (const auto stats = sweep->getPerfStats()) { - appendGlobalCounts(info); - appendTableCounts(info); + appendGlobalCounts(stats); + appendTableCounts(stats); } const char* event_type = NULL; diff --git a/src/utilities/ntrace/TracePluginImpl.h b/src/utilities/ntrace/TracePluginImpl.h index e84330599b1..4025af881ff 100644 --- a/src/utilities/ntrace/TracePluginImpl.h +++ b/src/utilities/ntrace/TracePluginImpl.h @@ -178,8 +178,8 @@ class TracePluginImpl final : GdsCodesArray include_codes; GdsCodesArray exclude_codes; - void appendGlobalCounts(const Firebird::PerformanceInfo* info); - void appendTableCounts(const Firebird::PerformanceInfo* info); + void appendGlobalCounts(Firebird::IPerformanceStats* stats); + void appendTableCounts(Firebird::IPerformanceStats* stats); void appendParams(Firebird::ITraceParams* params); void appendServiceQueryParams(size_t send_item_length, const ntrace_byte_t* send_items, size_t recv_item_length, const ntrace_byte_t* recv_items); From 3b980c614002fa10399947a5dc5d003a4617bb35 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 8 Dec 2025 20:21:27 +0000 Subject: [PATCH 22/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index a9e315fd9a9..6b457983a93 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1365 + FORMAL BUILD NUMBER:1366 */ -#define PRODUCT_VER_STRING "6.0.0.1365" -#define FILE_VER_STRING "WI-T6.0.0.1365" -#define LICENSE_VER_STRING "WI-T6.0.0.1365" -#define FILE_VER_NUMBER 6, 0, 0, 1365 +#define PRODUCT_VER_STRING "6.0.0.1366" +#define FILE_VER_STRING "WI-T6.0.0.1366" +#define LICENSE_VER_STRING "WI-T6.0.0.1366" +#define FILE_VER_NUMBER 6, 0, 0, 1366 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1365" +#define FB_BUILD_NO "1366" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index fe2ffff5bb6..392a79bf231 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1365 +BuildNum=1366 NowAt=`pwd` cd `dirname $0` From ddc44028c90dd6d5b68b0abcd71dbadbfad4d1ff Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Wed, 10 Dec 2025 14:24:43 +0300 Subject: [PATCH 23/71] Fixes a loop in the GENERATE_SERIES function on boundary values. (#8812) * Fixes a loop in the GENERATE_SERIES function on boundary values. * Correction according to dyemanov * A more complete solution for taking into account boundary values * Refactoring calculation with different types. ArithmeticNode::add is now used as agreed upon by @asfernandes. * Fixed non-ASCII character in variable name * Adjusting indents --- src/jrd/recsrc/RecordSource.h | 30 +---- src/jrd/recsrc/TableValueFunctionScan.cpp | 134 +++++++++------------- 2 files changed, 62 insertions(+), 102 deletions(-) diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 2b81225ec63..236c256c790 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1626,32 +1626,14 @@ namespace Jrd struct Impure final : public TableValueFunctionScan::Impure { - union - { - SINT64 vlu_int64; - Firebird::Int128 vlu_int128; - } m_start; - - union - { - SINT64 vlu_int64; - Firebird::Int128 vlu_int128; - } m_finish; - - union - { - SINT64 vlu_int64; - Firebird::Int128 vlu_int128; - } m_step; - - union - { - SINT64 vlu_int64; - Firebird::Int128 vlu_int128; - } m_result; + impure_value m_start; + impure_value m_finish; + impure_value m_step; + impure_value m_result; - UCHAR m_dtype; + USHORT m_flags; SCHAR m_scale; + SCHAR m_stepSign; }; public: diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index 637d5f49cb6..b0b5bbe30b3 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -407,54 +407,43 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const const auto impure = request->getImpure(m_impure); impure->m_recordBuffer = nullptr; - // common scale - impure->m_scale = MIN(MIN(startDesc->dsc_scale, finishDesc->dsc_scale), stepDesc->dsc_scale); - // common type - impure->m_dtype = MAX(MAX(startDesc->dsc_dtype, finishDesc->dsc_dtype), stepDesc->dsc_dtype); + ArithmeticNode::getDescDialect3(tdbb, &impure->m_result.vlu_desc, *startDesc, *stepDesc, blr_add, &impure->m_scale, &impure->m_flags); - if (impure->m_dtype != dtype_int128) - { - const auto start = MOV_get_int64(tdbb, startDesc, impure->m_scale); - const auto finish = MOV_get_int64(tdbb, finishDesc, impure->m_scale); - const auto step = MOV_get_int64(tdbb, stepDesc, impure->m_scale); + SLONG zero = 0; + dsc zeroDesc; + zeroDesc.makeLong(0, &zero); + impure->m_stepSign = MOV_compare(tdbb, stepDesc, &zeroDesc); - if (step == 0) - status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); + if (impure->m_stepSign == 0) + status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); - // validate parameter value - if (((step > 0) && (start > finish)) || - ((step < 0) && (start < finish))) - { - return; - } - - impure->m_start.vlu_int64 = start; - impure->m_finish.vlu_int64 = finish; - impure->m_step.vlu_int64 = step; - impure->m_result.vlu_int64 = start; - } - else + const auto boundaryComparison = MOV_compare(tdbb, startDesc, finishDesc); + // validate parameter value + if (((impure->m_stepSign > 0) && (boundaryComparison > 0)) || + ((impure->m_stepSign < 0) && (boundaryComparison < 0))) { - const auto start = MOV_get_int128(tdbb, startDesc, impure->m_scale); - const auto finish = MOV_get_int128(tdbb, finishDesc, impure->m_scale); - const auto step = MOV_get_int128(tdbb, stepDesc, impure->m_scale); + return; + } - if (step.sign() == 0) - status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); + EVL_make_value(tdbb, startDesc, &impure->m_start); + EVL_make_value(tdbb, finishDesc, &impure->m_finish); + EVL_make_value(tdbb, stepDesc, &impure->m_step); - // validate parameter value - if (((step.sign() > 0) && (start.compare(finish) > 0)) || - ((step.sign() < 0) && (start.compare(finish) < 0))) - { - return; - } - - impure->m_start.vlu_int128 = start; - impure->m_finish.vlu_int128 = finish; - impure->m_step.vlu_int128 = step; - impure->m_result.vlu_int128 = start; + switch (impure->m_result.vlu_desc.dsc_dtype) + { + case dtype_int64: + impure->m_result.vlu_desc.dsc_address = reinterpret_cast(&impure->m_result.vlu_misc.vlu_int64); + break; + case dtype_int128: + impure->m_result.vlu_desc.dsc_address = reinterpret_cast(&impure->m_result.vlu_misc.vlu_int128); + break; + default: + fb_assert(false); } + // result = start + MOV_move(tdbb, startDesc, &impure->m_result.vlu_desc); + impure->irsb_flags |= irsb_open; VIO_record(tdbb, rpb, m_format, &pool); @@ -505,51 +494,40 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const const auto request = tdbb->getRequest(); const auto impure = request->getImpure(m_impure); - if (impure->m_dtype != dtype_int128) + const auto comparison = MOV_compare(tdbb, &impure->m_result.vlu_desc, &impure->m_finish.vlu_desc); + if (((impure->m_stepSign > 0) && (comparison <= 0)) || + ((impure->m_stepSign < 0) && (comparison >= 0))) { - auto result = impure->m_result.vlu_int64; - const auto finish = impure->m_finish.vlu_int64; - const auto step = impure->m_step.vlu_int64; + Record* const record = request->req_rpb[m_stream].rpb_record; - if (((step > 0) && (result <= finish)) || - ((step < 0) && (result >= finish))) - { - Record* const record = request->req_rpb[m_stream].rpb_record; + auto toDesc = m_format->fmt_desc.begin(); - auto toDesc = m_format->fmt_desc.begin(); - - dsc fromDesc; - fromDesc.makeInt64(impure->m_scale, &result); - assignParameter(tdbb, &fromDesc, toDesc, 0, record); + assignParameter(tdbb, &impure->m_result.vlu_desc, toDesc, 0, record); - result += step; - impure->m_result.vlu_int64 = result; - - return true; + // evaluate next result + try + { + impure_value nextValue; + + ArithmeticNode::add(tdbb, + &impure->m_step.vlu_desc, + &impure->m_result.vlu_desc, + &nextValue, + blr_add, + false, + impure->m_scale, + impure->m_flags); + + MOV_move(tdbb, &nextValue.vlu_desc, &impure->m_result.vlu_desc); } - } - else - { - auto result = impure->m_result.vlu_int128; - const auto finish = impure->m_finish.vlu_int128; - const auto step = impure->m_step.vlu_int128; - - if (((step.sign() > 0) && (result.compare(finish) <= 0)) || - ((step.sign() < 0) && (result.compare(finish) >= 0))) + catch (const status_exception&) { - Record* const record = request->req_rpb[m_stream].rpb_record; - - auto toDesc = m_format->fmt_desc.begin(); - - dsc fromDesc; - fromDesc.makeInt128(impure->m_scale, &result); - assignParameter(tdbb, &fromDesc, toDesc, 0, record); - - result = result.add(step); - impure->m_result.vlu_int128 = result; - - return true; + tdbb->tdbb_status_vector->clearException(); + // stop evaluate next result + impure->m_stepSign = 0; } + + return true; } return false; From 2a02c8c9da502376261697bf9ec4ce6c8d9656d7 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 10 Dec 2025 20:22:12 +0000 Subject: [PATCH 24/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 6b457983a93..8021df1b26e 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1366 + FORMAL BUILD NUMBER:1367 */ -#define PRODUCT_VER_STRING "6.0.0.1366" -#define FILE_VER_STRING "WI-T6.0.0.1366" -#define LICENSE_VER_STRING "WI-T6.0.0.1366" -#define FILE_VER_NUMBER 6, 0, 0, 1366 +#define PRODUCT_VER_STRING "6.0.0.1367" +#define FILE_VER_STRING "WI-T6.0.0.1367" +#define LICENSE_VER_STRING "WI-T6.0.0.1367" +#define FILE_VER_NUMBER 6, 0, 0, 1367 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1366" +#define FB_BUILD_NO "1367" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 392a79bf231..00de5297307 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1366 +BuildNum=1367 NowAt=`pwd` cd `dirname $0` From 2bf350245e92b6d3823e019e06f0e7586131ceb0 Mon Sep 17 00:00:00 2001 From: Mark Rotteveel Date: Fri, 12 Dec 2025 12:39:59 +0100 Subject: [PATCH 25/71] Fix trace docs still using parts of FB 2.5 syntax --- doc/README.trace_services | 66 +++++++++++------------ src/utilities/ntrace/fbtrace.conf | 88 +++++++++++++++---------------- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/doc/README.trace_services b/doc/README.trace_services index a21aec216fc..f88a337adea 100644 --- a/doc/README.trace_services +++ b/doc/README.trace_services @@ -1,4 +1,4 @@ - + Trace and audit services. Firebird 2.5 offers new trace and audit facilities. These new abilities allow @@ -12,7 +12,7 @@ engine. List of events to trace, which data items to trace and placement of trace output is specified by trace configuration when trace session is created. There are two kinds of trace sessions : system audit and user trace. - + System audit session is started by the engine itself and obtains configuration from the text file. The name of this file is set via new setting in firebird.conf ("AuditTraceConfigFile") and by default has empty value, i.e. no system audit @@ -44,7 +44,7 @@ session. When application reads part of the output so output size stay less than When application decides to stop its trace session it just does detach from service. Also there is ability to manage trace sessions (suspend\resume\stop). -Administrators are allowed to manage any trace session while ordinary users are +Administrators are allowed to manage any trace session while ordinary users are allowed to manage their own trace sessions only. If user trace session was created by ordinary user it will trace only @@ -65,9 +65,9 @@ plugin is responsible for logging these events in some form. There is "standard" implementation of trace plugin, fbtrace.dll (.so) located in \plugins folder. - - There is new specialized standalone utility to work with trace services : + + There is new specialized standalone utility to work with trace services : fbtracemgr. It has the following command line switches : Action switches : @@ -94,20 +94,20 @@ Also, it prints usage screen if run without parameters. Examples - + I. Sample configuration files for user trace sessions: - + a) Trace prepare, free and execution of all statements within connection 12345 database { - enabled true - connection_id 12345 - log_statement_prepare true - log_statement_free true - log_statement_start true - log_statement_finish true - time_threshold 0 + enabled = true + connection_id = 12345 + log_statement_prepare= true + log_statement_free = true + log_statement_start = true + log_statement_finish = true + time_threshold = 0 } b) Trace all connections of given user to database mydatabase.fdb @@ -116,28 +116,28 @@ b) Trace all connections of given user to database mydatabase.fdb database = %[\\/]mydatabase.fdb { - enabled true - include_filter (%)(INSERT|UPDATE|DELETE)(%) - log_statement_finish true - log_procedure_finish true - log_trigger_finish true - print_plan true - print_perf true - time_threshold 0 + enabled = true + include_filter = (%)(INSERT|UPDATE|DELETE)(%) + log_statement_finish = true + log_procedure_finish = true + log_trigger_finish = true + print_plan = true + print_perf = true + time_threshold = 0 } - + c) Trace connections and transactions in all databases except of security database database { - enabled true - log_connections true - log_transactions true + enabled = true + log_connections = true + log_transactions = true } database = security.db { - enabled false + enabled = false } @@ -148,27 +148,27 @@ a) Start user trace named "My trace" using configuration file fbtrace.conf and r its output on the screen : fbtracemgr -se service_mgr -start -name "My trace" -config fbtrace.conf - + To stop this trace session press Ctrl+C at fbtracemgr console window. Or, in -another console : list sessions and look for interesting session ID (b) and stop it +another console : list sessions and look for interesting session ID (b) and stop it using this found ID (e). b) List trace sessions fbtracemgr -se service_mgr -list - + c) Suspend trace session with ID 1 fbtracemgr -se service_mgr -suspend -id 1 - + d) Resume trace session with ID 1 fbtracemgr -se service_mgr -resume -id 1 - + e) Stop trace session with ID 1 fbtracemgr -se service_mgr -stop -id 1 - + There are three general use cases : diff --git a/src/utilities/ntrace/fbtrace.conf b/src/utilities/ntrace/fbtrace.conf index a8c6a359df7..daf0bd2a1d1 100644 --- a/src/utilities/ntrace/fbtrace.conf +++ b/src/utilities/ntrace/fbtrace.conf @@ -11,15 +11,15 @@ # expression which is matched against fully qualified database path name. # # For log file name Sed syntax for substitutions is supported. -# I.e. \0 - whole matched string, \1 ... \9 - parenthesis subexpressions. +# I.e. \0 - whole matched string, \1 ... \9 - parenthesis subexpressions. # \\ is backslash. # -# String values should be enclosed into double quotes if contains +# String values should be enclosed into double quotes if contains # spaces embedded, for example: # log_filename "C:\\Documents and Settings\\Firebird\\My Documents\\trace.log" # include_filter "Database Stats" # -# To enter curvy brackets { } somewhere in a configuration dup them: {{ }}. +# To enter braces { } in a configuration value, double them: {{ }}. # For example - to enter this regular expression # database = (%[\\/](e[[:DIGIT:]]{2}).fdb) # type @@ -41,72 +41,72 @@ database # Operations log file name. For use by system audit trace only #log_filename = name - # Maximum size of log file (megabytes). Used by system audit trace for + # Maximum size of log file (megabytes). Used by system audit trace for # log's rotation : when current log file reached this limit it is renamed - # using current date and time and new log file is created. Value of zero + # using current date and time and new log file is created. Value of zero # means that the log file size is unlimited and rotation will never happen. #max_log_size = 0 - # SQL query filters. + # SQL query filters. # - # Only SQL statements falling under given regular expression are reported + # Only SQL statements falling under given regular expression are reported # in the log. - #include_filter + #include_filter = - # SQL statements falling under given regular expression are NOT reported + # SQL statements falling under given regular expression are NOT reported # in the log. - #exclude_filter + #exclude_filter = - # Put attach/detach log records + # Put attach/detach log records #log_connections = false - # Trace only given connection id. If zero - trace all connections + # Trace only given connection id. If zero - trace all connections #connection_id = 0 - # Put transaction start/end records + # Put transaction start/end records #log_transactions = false - # Put sql statement prepare records + # Put sql statement prepare records #log_statement_prepare = false - # Put sql statement free records + # Put sql statement free records #log_statement_free = false - # Put sql statement execution start records + # Put sql statement execution start records #log_statement_start = false - - # Put sql statement execution finish\fetch to eof records + + # Put sql statement execution finish\fetch to eof records #log_statement_finish = false # Put record when stored procedure is being compiled #log_procedure_compile = false - # Put record when stored procedure is start execution + # Put record when stored procedure is start execution #log_procedure_start = false - # Put record when stored procedure is finish execution + # Put record when stored procedure is finish execution #log_procedure_finish = false # Put record when stored function is being compiled #log_function_compile = false - # Put record when stored function is start execution + # Put record when stored function is start execution #log_function_start = false - # Put record when stored function is finish execution + # Put record when stored function is finish execution #log_function_finish = false # Put record when trigger is being compiled #log_trigger_compile = false - # Put trigger execute records + # Put trigger execute records #log_trigger_start = false - # Put trigger execute records + # Put trigger execute records #log_trigger_finish = false @@ -125,11 +125,11 @@ database # Include filter. If empty, trace all errors\warnings events. # Else trace event if any code from list is found in status-vector. - #include_gds_codes + #include_gds_codes = # Exclude filter. If empty, trace all errors\warnings events. # Else trace event if no code from list is found in status-vector. - #exclude_gds_codes + #exclude_gds_codes = # Put trace session init and finish messages #log_initfini = true @@ -148,13 +148,13 @@ database #print_perf = false - # Put blr requests compile/execute records + # Put blr requests compile/execute records #log_blr_requests = false # Print blr requests or not #print_blr = false - # Put dyn requests execute records + # Put dyn requests execute records #log_dyn_requests = false # Print dyn requests or not @@ -164,19 +164,19 @@ database # Put xxx_finish record only if its timing exceeds this number of milliseconds #time_threshold = 100 - # Maximum length of SQL string logged + # Maximum length of SQL string logged #max_sql_length = 300 - # Maximum length of blr request logged + # Maximum length of blr request logged #max_blr_length = 500 - # Maximum length of dyn request logged + # Maximum length of dyn request logged #max_dyn_length = 500 - # Maximum length of individual string argument we log + # Maximum length of individual string argument we log #max_arg_length = 80 - # Maximum number of query arguments to put in log + # Maximum number of query arguments to put in log #max_arg_count = 30 } @@ -184,7 +184,7 @@ database # default services section # -# List of names of currently existing Firebird services (to use with service +# List of names of currently existing Firebird services (to use with service # filters below) : # Backup Database # Restore Database @@ -209,7 +209,7 @@ database # Display User with Admin Info # Validate Database # -services +services { # Do we trace services events or not #enabled = false @@ -217,19 +217,19 @@ services # Operations log file name. For use by system audit trace only #log_filename = name - # Maximum size of log file (megabytes). Used by system audit trace for - # log's rotation + # Maximum size of log file (megabytes). Used by system audit trace for + # log's rotation #max_log_size = 0 # Services filters. # - # Only services whose names fall under given regular expression are + # Only services whose names fall under given regular expression are # reported in the log. - #include_filter + #include_filter = - # Services whose names fall under given regular expression are NOT + # Services whose names fall under given regular expression are NOT # reported in the log. - #exclude_filter + #exclude_filter = # Put service attach, detach and start records #log_services = false @@ -249,11 +249,11 @@ services # Include filter. If empty, trace all errors\warnings events. # Else trace event if any code from list is found in status-vector. - #include_gds_codes + #include_gds_codes = # Exclude filter. If empty, trace all errors\warnings events. # Else trace event if no code from list is found in status-vector. - #exclude_gds_codes + #exclude_gds_codes = # Put trace session init and finish messages #log_initfini = true @@ -271,7 +271,7 @@ database = %[\\/]my_database.fdb # Enable logging for test.fdb, azk2.fdb and rulez.fdb in any directory -# into log file name matching database name - test.log, azk2.log and +# into log file name matching database name - test.log, azk2.log and # rulez.log appropriately # database = %[\\/](test|azk2|rulez).fdb From 67946bfa248dd773c14d563254ccd464c670be88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20C=C3=ADsa=C5=99?= Date: Fri, 12 Dec 2025 18:22:25 +0100 Subject: [PATCH 26/71] Add DeepWiki shield --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d31461ec44e..f7ebf9e5535 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Build Status (GitHub)](https://github.com/FirebirdSQL/firebird/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/FirebirdSQL/firebird/actions/workflows/main.yml?query=branch%3Amaster) [![Build Status (AppVeyor)](https://ci.appveyor.com/api/projects/status/github/FirebirdSQL/firebird?branch=master&svg=true)](https://ci.appveyor.com/project/FirebirdSQL/firebird) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/FirebirdSQL/firebird) # Firebird README From 1ea9ef5f57f657e5d3ad4d163d4d45c1d961537e Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 12 Dec 2025 20:20:55 +0000 Subject: [PATCH 27/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 8021df1b26e..c9bfadd9e31 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1367 + FORMAL BUILD NUMBER:1369 */ -#define PRODUCT_VER_STRING "6.0.0.1367" -#define FILE_VER_STRING "WI-T6.0.0.1367" -#define LICENSE_VER_STRING "WI-T6.0.0.1367" -#define FILE_VER_NUMBER 6, 0, 0, 1367 +#define PRODUCT_VER_STRING "6.0.0.1369" +#define FILE_VER_STRING "WI-T6.0.0.1369" +#define LICENSE_VER_STRING "WI-T6.0.0.1369" +#define FILE_VER_NUMBER 6, 0, 0, 1369 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1367" +#define FB_BUILD_NO "1369" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 00de5297307..527198bc7eb 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1367 +BuildNum=1369 NowAt=`pwd` cd `dirname $0` From 5791b5759edcdf1cb989a8f9cc880dde58fe077d Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Fri, 12 Dec 2025 22:40:29 -0300 Subject: [PATCH 28/71] Fix errors in LISTAGG implementation --- src/dsql/parse.y | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 28fc1e12163..01eae97c432 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -709,17 +709,19 @@ using namespace Firebird; %token CALL %token CURRENT_SCHEMA %token DOWNTO +%token ERROR %token FORMAT %token GENERATE_SERIES %token GREATEST %token LEAST +%token LISTAGG %token LTRIM %token NAMED_ARG_ASSIGN %token RTRIM %token SCHEMA %token SEARCH_PATH +%token TRUNCATE %token UNLIST -%token LISTAGG %token WITHIN // precedence declarations for expression evaluation @@ -8666,25 +8668,25 @@ overflow_behavior %type listagg_truncation_filler_opt listagg_truncation_filler_opt - : /*nothing*/ { $$ = MAKE_str_constant(newIntlString("..."), lex.charSetId); } + : /* nothing */ { $$ = MAKE_str_constant(newIntlString("..."), lex.charSetId); } | listagg_truncation_filler { $$ = $1; } ; -%type listagg_truncation_filler +%type listagg_truncation_filler listagg_truncation_filler - : sql_string + : sql_string { $$ = MAKE_str_constant($1, lex.charSetId); } ; %type listagg_count_indication listagg_count_indication - : WITH COUNT { $$ = true; } + : WITH COUNT { $$ = true; } | WITHOUT COUNT { $$ = false; } ; %type within_group_specification_opt within_group_specification_opt - : /* nothing */ { $$ = newNode(0); } - | within_group_specification { $$ = $1; } + : /* nothing */ { $$ = newNode(0); } + | within_group_specification { $$ = $1; } ; %type within_group_specification From 547c6c63eb17560986386bc125ffcbd4b0e6e9c9 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Sat, 13 Dec 2025 10:02:45 -0300 Subject: [PATCH 29/71] Change wrong and unused return --- src/jrd/met.epp | 4 +--- src/jrd/met_proto.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 3756718b9f9..b2f1b9822a3 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -244,7 +244,7 @@ void MET_get_domain(thread_db* tdbb, MemoryPool& csbPool, const QualifiedName& n } -MetaName MET_get_relation_field(thread_db* tdbb, MemoryPool& csbPool, const QualifiedName& relationName, +void MET_get_relation_field(thread_db* tdbb, MemoryPool& csbPool, const QualifiedName& relationName, const MetaName& fieldName, dsc* desc, FieldInfo* fieldInfo) { /************************************** @@ -321,8 +321,6 @@ MetaName MET_get_relation_field(thread_db* tdbb, MemoryPool& csbPool, const Qual fieldName.toQuotedString() << relationName.toQuotedString()); } - - return sourceName; } diff --git a/src/jrd/met_proto.h b/src/jrd/met_proto.h index fba22677afd..d178125b777 100644 --- a/src/jrd/met_proto.h +++ b/src/jrd/met_proto.h @@ -144,7 +144,7 @@ void MET_trigger_msg(Jrd::thread_db*, Firebird::string&, const Jrd::QualifiedNa void MET_update_shadow(Jrd::thread_db*, Jrd::Shadow*, USHORT); void MET_update_transaction(Jrd::thread_db*, Jrd::jrd_tra*, const bool); void MET_get_domain(Jrd::thread_db*, MemoryPool& csbPool, const Jrd::QualifiedName&, dsc*, Jrd::FieldInfo*); -Jrd::MetaName MET_get_relation_field(Jrd::thread_db*, MemoryPool& csbPool, +void MET_get_relation_field(Jrd::thread_db*, MemoryPool& csbPool, const Jrd::QualifiedName&, const Jrd::MetaName&, dsc*, Jrd::FieldInfo*); void MET_update_partners(Jrd::thread_db*); From 64f30827b5ccf88de9efa9ed7cc7d6de9de9e2e1 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Sat, 13 Dec 2025 10:07:04 -0300 Subject: [PATCH 30/71] Check field source schema name change --- src/jrd/vio.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/jrd/vio.cpp b/src/jrd/vio.cpp index 67c74a6dcb0..89266c49244 100644 --- a/src/jrd/vio.cpp +++ b/src/jrd/vio.cpp @@ -3597,13 +3597,16 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j if ((!rc1 || MOV_get_long(tdbb, &desc1, 0) == 0)) { - dsc desc3, desc4; + dsc desc3, desc4, desc5, desc6; bool rc2 = EVL_field(NULL, new_rpb->rpb_record, f_rfr_null_flag, &desc2); bool rc3 = EVL_field(NULL, org_rpb->rpb_record, f_rfr_sname, &desc3); bool rc4 = EVL_field(NULL, new_rpb->rpb_record, f_rfr_sname, &desc4); + bool rc5 = EVL_field(NULL, org_rpb->rpb_record, f_rfr_field_source_schema, &desc5); + bool rc6 = EVL_field(NULL, new_rpb->rpb_record, f_rfr_field_source_schema, &desc6); if ((rc2 && MOV_get_long(tdbb, &desc2, 0) != 0) || - (rc3 && rc4 && MOV_compare(tdbb, &desc3, &desc4))) + (rc3 && rc4 && MOV_compare(tdbb, &desc3, &desc4)) || + (rc5 && rc6 && MOV_compare(tdbb, &desc5, &desc6))) { EVL_field(0, new_rpb->rpb_record, f_rfr_schema, &schemaDesc); EVL_field(0, new_rpb->rpb_record, f_rfr_rname, &desc1); From 273421880b308aeb715231b916bfe879d7e60072 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 13 Dec 2025 20:19:23 +0000 Subject: [PATCH 31/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index c9bfadd9e31..7b6cc18d8f9 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1369 + FORMAL BUILD NUMBER:1372 */ -#define PRODUCT_VER_STRING "6.0.0.1369" -#define FILE_VER_STRING "WI-T6.0.0.1369" -#define LICENSE_VER_STRING "WI-T6.0.0.1369" -#define FILE_VER_NUMBER 6, 0, 0, 1369 +#define PRODUCT_VER_STRING "6.0.0.1372" +#define FILE_VER_STRING "WI-T6.0.0.1372" +#define LICENSE_VER_STRING "WI-T6.0.0.1372" +#define FILE_VER_NUMBER 6, 0, 0, 1372 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1369" +#define FB_BUILD_NO "1372" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 527198bc7eb..1f92e8c8d23 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1369 +BuildNum=1372 NowAt=`pwd` cd `dirname $0` From 68981facbdeea8e18857feccadb7179d34d946f3 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Mon, 15 Dec 2025 08:37:48 -0300 Subject: [PATCH 32/71] Fix double increment of variable `i` --- src/jrd/trace/TraceObjects.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/jrd/trace/TraceObjects.cpp b/src/jrd/trace/TraceObjects.cpp index 86a503b922e..28651d45e39 100644 --- a/src/jrd/trace/TraceObjects.cpp +++ b/src/jrd/trace/TraceObjects.cpp @@ -670,7 +670,6 @@ TraceRuntimeStats::TraceRuntimeStats(Attachment* attachment, m_info.pin_tables[i].trc_relation_id = m_tableCounters.getObjectId(i); m_info.pin_tables[i].trc_relation_name = m_tableCounters.getObjectName(i); m_info.pin_tables[i].trc_counters = m_tableCounters.getObjectCounters(i); - i++; } } else From a73f5de4d74f26ca74bdaf015d3d7a9a4c7e573a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 15 Dec 2025 20:21:49 +0000 Subject: [PATCH 33/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 7b6cc18d8f9..3daced6cb6e 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1372 + FORMAL BUILD NUMBER:1373 */ -#define PRODUCT_VER_STRING "6.0.0.1372" -#define FILE_VER_STRING "WI-T6.0.0.1372" -#define LICENSE_VER_STRING "WI-T6.0.0.1372" -#define FILE_VER_NUMBER 6, 0, 0, 1372 +#define PRODUCT_VER_STRING "6.0.0.1373" +#define FILE_VER_STRING "WI-T6.0.0.1373" +#define LICENSE_VER_STRING "WI-T6.0.0.1373" +#define FILE_VER_NUMBER 6, 0, 0, 1373 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1372" +#define FB_BUILD_NO "1373" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 1f92e8c8d23..99946d9eefc 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1372 +BuildNum=1373 NowAt=`pwd` cd `dirname $0` From 16a7f49e7fbbb6115f452320c71e3d49ab04b1b8 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Tue, 16 Dec 2025 08:48:47 -0300 Subject: [PATCH 34/71] Correction --- doc/sql.extensions/README.listagg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sql.extensions/README.listagg b/doc/sql.extensions/README.listagg index 03b432fbdd6..2faa53ebeaf 100644 --- a/doc/sql.extensions/README.listagg +++ b/doc/sql.extensions/README.listagg @@ -50,7 +50,7 @@ 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; +SELECT LISTAGG (ALL COL4, ':') FROM TEST_T; ======= J:I:L:K From 3b2bc84445ebf4e50b94e4e2b0f2a970168363f8 Mon Sep 17 00:00:00 2001 From: Vlad Khorsun Date: Tue, 16 Dec 2025 15:15:51 +0200 Subject: [PATCH 35/71] Frontported pull request #8826: Fixed potential endless loop inside MET_scan_relation --- src/jrd/met.epp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/jrd/met.epp b/src/jrd/met.epp index b2f1b9822a3..4ef5bd33973 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -3973,12 +3973,16 @@ static void scan_relation(thread_db* tdbb, jrd_rel* relation) AutoCacheRequest request(tdbb, irq_r_fields, IRQ_REQUESTS); CompilerScratch* csb = NULL; + bool found = false; + FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS CROSS SCH IN RDB$SCHEMAS WITH REL.RDB$RELATION_ID EQ relation->rel_id AND SCH.RDB$SCHEMA_NAME EQ REL.RDB$SCHEMA_NAME { + found = true; + // Pick up relation level stuff relation->rel_current_fmt = REL.RDB$FORMAT; vec* vector = relation->rel_fields = @@ -4251,6 +4255,27 @@ static void scan_relation(thread_db* tdbb, jrd_rel* relation) delete csb; + if (!found && !(relation->rel_flags & REL_scanned)) + { + // Relation was not found in RDB$RELATIONS. It could be system virtual relation + // defined in INI. + + if (relation->isSystem() && relation->isVirtual()) + { + relation->rel_flags |= REL_scanned; + } + else + { + fb_assert(false); + + string name(relation->rel_name.toQuotedString()); + if (name.isEmpty()) + name.printf("", relation->rel_id); + + ERR_post(Arg::Gds(isc_relnotdef) << Arg::Str(name)); + } + } + // We have just loaded the triggers onto the local vector triggers. // It's now time to place them at their rightful place inside the relation block. relation->replaceTriggers(tdbb, triggers); From 8098b4c8d458600f61cd89e25c30185e8701b2f5 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 16 Dec 2025 20:22:25 +0000 Subject: [PATCH 36/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 3daced6cb6e..52601e90c22 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1373 + FORMAL BUILD NUMBER:1375 */ -#define PRODUCT_VER_STRING "6.0.0.1373" -#define FILE_VER_STRING "WI-T6.0.0.1373" -#define LICENSE_VER_STRING "WI-T6.0.0.1373" -#define FILE_VER_NUMBER 6, 0, 0, 1373 +#define PRODUCT_VER_STRING "6.0.0.1375" +#define FILE_VER_STRING "WI-T6.0.0.1375" +#define LICENSE_VER_STRING "WI-T6.0.0.1375" +#define FILE_VER_NUMBER 6, 0, 0, 1375 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1373" +#define FB_BUILD_NO "1375" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 99946d9eefc..c1121ad28d1 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1373 +BuildNum=1375 NowAt=`pwd` cd `dirname $0` From 6826f9bf9e1f0450ab6f3a31e8eeed45372837f6 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Tue, 16 Dec 2025 22:31:05 -0300 Subject: [PATCH 37/71] Fix warnings --- src/dsql/pass1.cpp | 6 +----- src/intl/charsets/cs_koi8u.h | 1 - src/jrd/optimizer/Optimizer.cpp | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/dsql/pass1.cpp b/src/dsql/pass1.cpp index eef4683975e..cad9b6cc039 100644 --- a/src/dsql/pass1.cpp +++ b/src/dsql/pass1.cpp @@ -279,8 +279,6 @@ bool InvalidReferenceFinder::visit(ExprNode* node) if (!node) return false; - bool invalid = false; - // ASF: What we do in this function is the verification of all fields/dbkeys (or any parent // expression involving them) are present in the passed node list. // That makes valid: @@ -732,7 +730,7 @@ void PASS1_ambiguity_check(DsqlCompilerScratch* dsqlScratch, bool PASS1_compare_alias(const QualifiedName& contextAlias, const QualifiedName& lookupAlias) { - return lookupAlias == contextAlias || lookupAlias.schema.isEmpty() && lookupAlias.object == contextAlias.object; + return lookupAlias == contextAlias || (lookupAlias.schema.isEmpty() && lookupAlias.object == contextAlias.object); } @@ -1443,8 +1441,6 @@ void PASS1_expand_select_node(DsqlCompilerScratch* dsqlScratch, ExprNode* node, if (context->ctx_relation) { - thread_db* const tdbb = JRD_get_thread_data(); - for (dsql_fld* field = context->ctx_relation->rel_fields; field; field = field->fld_next) { DEV_BLKCHK(field, dsql_type_fld); diff --git a/src/intl/charsets/cs_koi8u.h b/src/intl/charsets/cs_koi8u.h index 6c8866774d8..27f85da5c69 100644 --- a/src/intl/charsets/cs_koi8u.h +++ b/src/intl/charsets/cs_koi8u.h @@ -7,7 +7,6 @@ -------------------------------------------- */ -static const int UNICODE_REPLACEMENT_CHARACTER = 0xFFFD; static const int CANT_MAP_CHARACTER = 0; static const USHORT to_unicode_map[256] = { diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index c7ae78e32f0..92a13b51f74 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -3165,8 +3165,6 @@ bool Optimizer::getEquiJoinKeys(NestConst& node1, string Optimizer::getStreamName(StreamType stream) { const auto tail = &csb->csb_rpt[stream]; - const auto* relation = tail->csb_relation; - const auto* procedure = tail->csb_procedure; const auto* alias = tail->csb_alias; string name = tail->getName().toQuotedString(); From effbde247458e658c7f96c954be7d330b946749e Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Wed, 17 Dec 2025 18:17:58 +0300 Subject: [PATCH 38/71] Added check for percentile constancy within a group --- src/dsql/AggNodes.cpp | 45 ++++++++++++++++++++++++++++- src/dsql/AggNodes.h | 2 ++ src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 808e9c7e1b0..5f2a65d9209 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1223,6 +1223,49 @@ AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) return this; } +bool PercentileAggNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor) +{ + bool invalid = false; + + if (!visitor.insideOwnMap) + { + // We are not in an aggregate from the same scope_level so + // check for valid fields inside this aggregate + invalid |= ExprNode::dsqlInvalidReferenceFinder(visitor); + } + + if (!visitor.insideHigherMap) + { + NodeRefsHolder holder(visitor.dsqlScratch->getPool()); + getChildren(holder, true); + + for (auto i : holder.refs) + { + // If there's another aggregate with the same scope_level or + // an higher one then it's a invalid aggregate, because + // aggregate-functions from the same context can't + // be part of each other. + if (Aggregate2Finder::find(visitor.dsqlScratch->getPool(), visitor.context->ctx_scope_level, + FIELD_MATCH_TYPE_EQUAL, false, *i)) + { + // Nested aggregate functions are not allowed + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << + Arg::Gds(isc_dsql_agg_nested_err)); + } + } + + if (visitor.visit(**holder.refs.begin())) + { + // The percent argument must be constant within group + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << + Arg::Gds(isc_argmustbe_const_within_group) << + ((type == TYPE_PERCENTILE_CONT) ? Arg::Str("PERCENTILE_CONT") : Arg::Str("PERCENTILE_DISC"))); + } + } + + return invalid; +} + string PercentileAggNode::internalPrint(NodePrinter& printer) const { AggNode::internalPrint(printer); @@ -1433,7 +1476,7 @@ dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ { AggNode* node = FB_NEW_POOL(dsqlScratch->getPool()) PercentileAggNode(dsqlScratch->getPool(), type, - doDsqlPass(dsqlScratch, arg), + doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, dsqlOrderClause) ); PASS1_set_parameter_type(dsqlScratch, node->arg, diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index 929c08e04dc..defcd89e3e4 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -170,6 +170,8 @@ class PercentileAggNode final : public AggNode holder.add(valueArg); } + bool dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor) override; + Firebird::string internalPrint(NodePrinter& printer) const override; void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; void genBlr(DsqlCompilerScratch* dsqlScratch) override; diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index be4dd4cde4e..d146ea1562e 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1003,3 +1003,4 @@ FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments f FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument for @1 must be in the range [0, 1]") FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types") FB_IMPL_MSG(JRD, 1003, percetile_only_one_sort_item, -833, "42", "000", "The PERCENTILE_DISC and PERENTILE_CONT functions support only one sort item in WITHIN GROUP") +FB_IMPL_MSG(JRD, 1004, argmustbe_const_within_group, -833, "42", "000", "Argument for @1 function must be constant within each group") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 61bbb789f60..a18d02ed34a 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5851,6 +5851,7 @@ IProfilerStatsImpl = class(IProfilerStats) isc_sysf_argmustbe_range_inc0_1 = 335545321; isc_argmustbe_numeric_function = 335545322; isc_percetile_only_one_sort_item = 335545323; + isc_argmustbe_const_within_group = 335545324; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; From ac85f8743efc91a09e600d1885751eca9b6acede Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 17 Dec 2025 20:21:33 +0000 Subject: [PATCH 39/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 52601e90c22..2088e459b61 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1375 + FORMAL BUILD NUMBER:1376 */ -#define PRODUCT_VER_STRING "6.0.0.1375" -#define FILE_VER_STRING "WI-T6.0.0.1375" -#define LICENSE_VER_STRING "WI-T6.0.0.1375" -#define FILE_VER_NUMBER 6, 0, 0, 1375 +#define PRODUCT_VER_STRING "6.0.0.1376" +#define FILE_VER_STRING "WI-T6.0.0.1376" +#define LICENSE_VER_STRING "WI-T6.0.0.1376" +#define FILE_VER_NUMBER 6, 0, 0, 1376 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1375" +#define FB_BUILD_NO "1376" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index c1121ad28d1..bb963ccda32 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1375 +BuildNum=1376 NowAt=`pwd` cd `dirname $0` From a3355d557a9180a2e350c5cdd2ba35d2ca4c94dc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 09:49:18 -0300 Subject: [PATCH 40/71] Update tzdata to version 2025c. (#8832) --- extern/icu/tzdata/be.zip | Bin 95023 -> 95176 bytes extern/icu/tzdata/le.zip | Bin 94332 -> 94488 bytes extern/icu/tzdata/version.txt | 2 +- src/common/TimeZones.h | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/icu/tzdata/be.zip b/extern/icu/tzdata/be.zip index 8f2df2bf69d41227307f0c70869a8b1aaf5a2b9c..0b67b5c835b87dfacdde6c39280e3770d56155da 100644 GIT binary patch delta 78527 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 i1s{jnoD9FPOb?lc-0l#fKJtzrv;n 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 Kbo+l3=zjp1vrv5i delta 78336 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 Oo(lm2c{zVfp#K1E=N7pD diff --git a/extern/icu/tzdata/le.zip b/extern/icu/tzdata/le.zip index cd7d698a57efa4a7d733284cae683e7a0a36b87f..08c0fe324a86d5dd0bdf73da844dc1af2dc134b2 100644 GIT binary patch delta 77608 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Ξ4j+; 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 h09l$iGnyE={NFu Date: Sat, 20 Dec 2025 20:19:05 +0000 Subject: [PATCH 41/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 2088e459b61..90bcfc1cd88 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1376 + FORMAL BUILD NUMBER:1377 */ -#define PRODUCT_VER_STRING "6.0.0.1376" -#define FILE_VER_STRING "WI-T6.0.0.1376" -#define LICENSE_VER_STRING "WI-T6.0.0.1376" -#define FILE_VER_NUMBER 6, 0, 0, 1376 +#define PRODUCT_VER_STRING "6.0.0.1377" +#define FILE_VER_STRING "WI-T6.0.0.1377" +#define LICENSE_VER_STRING "WI-T6.0.0.1377" +#define FILE_VER_NUMBER 6, 0, 0, 1377 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1376" +#define FB_BUILD_NO "1377" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index bb963ccda32..08df126fe6c 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1376 +BuildNum=1377 NowAt=`pwd` cd `dirname $0` From 7a87c1fede5ca68c464c34ebc9574c736eade9dd Mon Sep 17 00:00:00 2001 From: Vasiliy Date: Tue, 23 Dec 2025 10:51:45 +0300 Subject: [PATCH 42/71] Fix file name in UTF-8 encoding for gstat (#8829) --- src/utilities/gstat/dba.epp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utilities/gstat/dba.epp b/src/utilities/gstat/dba.epp index c37f9891be2..3171cc6e87e 100644 --- a/src/utilities/gstat/dba.epp +++ b/src/utilities/gstat/dba.epp @@ -635,6 +635,9 @@ int gstat(Firebird::UtilSvc* uSvc) } } + if (uSvc->utf8FileNames()) + ISC_utf8ToSystem(fileName); + expandDatabaseName(fileName, tempStr, NULL); fileName = tempStr; From 0317f680e41eba6e07ad72734e013bb77c241ab6 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 23 Dec 2025 20:20:35 +0000 Subject: [PATCH 43/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 90bcfc1cd88..527883f07ba 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1377 + FORMAL BUILD NUMBER:1378 */ -#define PRODUCT_VER_STRING "6.0.0.1377" -#define FILE_VER_STRING "WI-T6.0.0.1377" -#define LICENSE_VER_STRING "WI-T6.0.0.1377" -#define FILE_VER_NUMBER 6, 0, 0, 1377 +#define PRODUCT_VER_STRING "6.0.0.1378" +#define FILE_VER_STRING "WI-T6.0.0.1378" +#define LICENSE_VER_STRING "WI-T6.0.0.1378" +#define FILE_VER_NUMBER 6, 0, 0, 1378 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1377" +#define FB_BUILD_NO "1378" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 08df126fe6c..52bd5217cee 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1377 +BuildNum=1378 NowAt=`pwd` cd `dirname $0` From 1652fcc1db5445a4262ee847225da34739c16a78 Mon Sep 17 00:00:00 2001 From: Dimitry Sibiryakov Date: Wed, 24 Dec 2025 17:48:35 +0100 Subject: [PATCH 44/71] Fix for #8836 (#8838) --- src/dsql/AggNodes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 4d86f3c82d7..87fd272e1e1 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -597,7 +597,7 @@ void AnyValueAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) cons { const auto argValue = EVL_expr(tdbb, request, arg); - if (!argValue) + if (argValue) EVL_make_value(tdbb, argValue, impure); } From cfe495ddea60d49c2b4be991c95f657decf26c34 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 24 Dec 2025 20:20:18 +0000 Subject: [PATCH 45/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 527883f07ba..19624b2fcab 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1378 + FORMAL BUILD NUMBER:1379 */ -#define PRODUCT_VER_STRING "6.0.0.1378" -#define FILE_VER_STRING "WI-T6.0.0.1378" -#define LICENSE_VER_STRING "WI-T6.0.0.1378" -#define FILE_VER_NUMBER 6, 0, 0, 1378 +#define PRODUCT_VER_STRING "6.0.0.1379" +#define FILE_VER_STRING "WI-T6.0.0.1379" +#define LICENSE_VER_STRING "WI-T6.0.0.1379" +#define FILE_VER_NUMBER 6, 0, 0, 1379 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1378" +#define FB_BUILD_NO "1379" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 52bd5217cee..4c018319890 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1378 +BuildNum=1379 NowAt=`pwd` cd `dirname $0` From f1151638749b7f73d1ad8c0f2b34480766364421 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Thu, 25 Dec 2025 17:43:02 +0300 Subject: [PATCH 46/71] Comment according to Mark's request --- src/yvalve/utl.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yvalve/utl.cpp b/src/yvalve/utl.cpp index 2f14933d3c1..1361dd8168e 100644 --- a/src/yvalve/utl.cpp +++ b/src/yvalve/utl.cpp @@ -3211,6 +3211,10 @@ void setLogin(ClumpletWriter& dpb, bool spbFlag) const UCHAR utf8Tag = spbFlag ? isc_spb_utf8_filename : isc_dpb_utf8_filename; // username and password tags match for both SPB and DPB + // We should not use environment variables when user explicitly requested + // trusted authentication (trusted_auth), on network server (address_path) + // and when authentication block is present (auth_block). The latter + // typically happens only on network server but extra protection won't hurt. if (!(dpb.find(trusted_auth) || dpb.find(address_path) || dpb.find(auth_block))) { bool utf8 = dpb.find(utf8Tag); From 8ed234af3f8e8b889e7468fc058627980ec102a1 Mon Sep 17 00:00:00 2001 From: AlexPeshkoff Date: Thu, 25 Dec 2025 19:11:33 +0300 Subject: [PATCH 47/71] Fixed #8806: Missing privilege checks for the COMMENT ON PARAMETER command on functions in packages --- src/dsql/DdlNodes.epp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 4e8c5c9f4cf..9097c30658e 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -1446,10 +1446,6 @@ void CommentOnNode::checkPermission(thread_db* tdbb, jrd_tra* transaction) SCL_check_view(tdbb, name, SCL_alter); break; - case obj_procedure: - SCL_check_procedure(tdbb, name, SCL_alter); - break; - case obj_trigger: { const auto relationName = getTriggerRelationName(tdbb, transaction, name); @@ -1461,7 +1457,19 @@ void CommentOnNode::checkPermission(thread_db* tdbb, jrd_tra* transaction) } case obj_udf: - SCL_check_function(tdbb, name, SCL_alter); + case obj_procedure: + if (name.package.hasData()) + { + const auto package = name.getSchemaAndPackage(); + SCL_check_package(tdbb, package, SCL_alter); + } + else + { + if (objType == obj_udf) + SCL_check_function(tdbb, name, SCL_alter); + else + SCL_check_procedure(tdbb, name, SCL_alter); + } break; case obj_blob_filter: From 5ee2b415860bc1b7394c7170db0c90c07a22e432 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 25 Dec 2025 20:21:02 +0000 Subject: [PATCH 48/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 19624b2fcab..30c8381a6ed 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1379 + FORMAL BUILD NUMBER:1381 */ -#define PRODUCT_VER_STRING "6.0.0.1379" -#define FILE_VER_STRING "WI-T6.0.0.1379" -#define LICENSE_VER_STRING "WI-T6.0.0.1379" -#define FILE_VER_NUMBER 6, 0, 0, 1379 +#define PRODUCT_VER_STRING "6.0.0.1381" +#define FILE_VER_STRING "WI-T6.0.0.1381" +#define LICENSE_VER_STRING "WI-T6.0.0.1381" +#define FILE_VER_NUMBER 6, 0, 0, 1381 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1379" +#define FB_BUILD_NO "1381" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 4c018319890..0eab9c9c52f 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1379 +BuildNum=1381 NowAt=`pwd` cd `dirname $0` From 08387c8c8bf0e66402b05700b4a14ee50b1f415e Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Fri, 26 Dec 2025 11:34:29 +0300 Subject: [PATCH 49/71] Fix cardinality estimation for the invariant filter --- src/jrd/recsrc/FilteredStream.cpp | 20 +++++++++++++++----- src/jrd/recsrc/RecordSource.h | 16 ++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/jrd/recsrc/FilteredStream.cpp b/src/jrd/recsrc/FilteredStream.cpp index ead4bda7332..f41c1f3e11f 100644 --- a/src/jrd/recsrc/FilteredStream.cpp +++ b/src/jrd/recsrc/FilteredStream.cpp @@ -39,12 +39,9 @@ using namespace Jrd; FilteredStream::FilteredStream(CompilerScratch* csb, RecordSource* next, BoolExprNode* boolean, double selectivity) : RecordSource(csb), + m_invariant(false), m_next(next), - m_boolean(boolean), - m_anyBoolean(NULL), - m_ansiAny(false), - m_ansiAll(false), - m_ansiNot(false) + m_boolean(boolean) { fb_assert(m_next && m_boolean); @@ -59,6 +56,19 @@ FilteredStream::FilteredStream(CompilerScratch* csb, RecordSource* next, m_cardinality = cardinality; } +FilteredStream::FilteredStream(CompilerScratch* csb, RecordSource* next, + BoolExprNode* boolean) + : RecordSource(csb), + m_invariant(true), + m_next(next), + m_boolean(boolean) +{ + fb_assert(m_next && m_boolean); + + m_impure = csb->allocImpure(); + m_cardinality = next->getCardinality(); +} + void FilteredStream::internalOpen(thread_db* tdbb) const { Request* const request = tdbb->getRequest(); diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 236c256c790..9d5d3f3e579 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -584,7 +584,7 @@ namespace Jrd { public: FilteredStream(CompilerScratch* csb, RecordSource* next, - BoolExprNode* boolean, double selectivity = 0); + BoolExprNode* boolean, double selectivity); void close(thread_db* tdbb) const override; @@ -611,11 +611,13 @@ namespace Jrd } protected: + FilteredStream(CompilerScratch* csb, RecordSource* next, BoolExprNode* boolean); + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const override; void internalOpen(thread_db* tdbb) const override; bool internalGetRecord(thread_db* tdbb) const override; - bool m_invariant = false; + const bool m_invariant; private: Firebird::TriState evaluateBoolean(thread_db* tdbb) const; @@ -623,9 +625,9 @@ namespace Jrd NestConst m_next; NestConst const m_boolean; NestConst m_anyBoolean; - bool m_ansiAny; - bool m_ansiAll; - bool m_ansiNot; + bool m_ansiAny = false; + bool m_ansiAll = false; + bool m_ansiNot = false; }; class PreFilteredStream : public FilteredStream @@ -634,9 +636,7 @@ namespace Jrd PreFilteredStream(CompilerScratch* csb, RecordSource* next, BoolExprNode* boolean) : FilteredStream(csb, next, boolean) - { - m_invariant = true; - } + {} }; class SortedStream : public RecordSource From 0c1aa58f3eb58d483900c992c775a601c63b7ed7 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Fri, 26 Dec 2025 11:43:23 +0300 Subject: [PATCH 50/71] Fix the cardinality estimation (FIRST ROWS case) in HASH JOINs --- src/jrd/optimizer/InnerJoin.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/jrd/optimizer/InnerJoin.cpp b/src/jrd/optimizer/InnerJoin.cpp index c356fa5e898..c88be88cad7 100644 --- a/src/jrd/optimizer/InnerJoin.cpp +++ b/src/jrd/optimizer/InnerJoin.cpp @@ -198,20 +198,22 @@ void InnerJoin::estimateCost(unsigned position, // likely cardinality under-estimation. const bool avoidHashJoin = (streamCardinality <= MINIMUM_CARDINALITY && !stream->baseIndexes); - auto currentCardinality = candidate->unique ? - MINIMUM_CARDINALITY : streamCardinality * candidate->selectivity; - auto currentCost = candidate->cost; + auto currentCardinality = streamCardinality * candidate->selectivity; // Given the "first-rows" mode specified (or implied) // and unless an external sort is to be applied afterwards, // fake the expected cardinality to look as low as possible - // to estimate the cost just for a single row being produced + // to estimate the cost just for a single row being produced. + // The same rule is used if the retrieval is unique. - if ((!sort || candidate->navigated) && optimizer->favorFirstRows()) + const bool firstRows = (optimizer->favorFirstRows() && + (!sortPtr || !*sortPtr || candidate->navigated)); + + if ((candidate->unique || firstRows) && currentCardinality > MINIMUM_CARDINALITY) currentCardinality = MINIMUM_CARDINALITY; // Calculate the nested loop cost, it's our default option - const auto loopCost = currentCost * cardinality; + const auto loopCost = candidate->cost * cardinality; cost = loopCost; // Consider whether the current stream can be hash-joined to the prior ones. From ce08ec0f9cf63bac4b70e284549df25c7c06b7eb Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 26 Dec 2025 20:20:02 +0000 Subject: [PATCH 51/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 30c8381a6ed..4c858ce3950 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1381 + FORMAL BUILD NUMBER:1383 */ -#define PRODUCT_VER_STRING "6.0.0.1381" -#define FILE_VER_STRING "WI-T6.0.0.1381" -#define LICENSE_VER_STRING "WI-T6.0.0.1381" -#define FILE_VER_NUMBER 6, 0, 0, 1381 +#define PRODUCT_VER_STRING "6.0.0.1383" +#define FILE_VER_STRING "WI-T6.0.0.1383" +#define LICENSE_VER_STRING "WI-T6.0.0.1383" +#define FILE_VER_NUMBER 6, 0, 0, 1383 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1381" +#define FB_BUILD_NO "1383" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 0eab9c9c52f..cece0cfc156 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1381 +BuildNum=1383 NowAt=`pwd` cd `dirname $0` From fc0b004ace15c8b3b329bd960ee94b1b45da6fb3 Mon Sep 17 00:00:00 2001 From: Dimitry Sibiryakov Date: Mon, 29 Dec 2025 12:55:52 +0100 Subject: [PATCH 52/71] Fix debug build after b7f5b4b (Use cloop cmake build) (#8824) --- builds/win32/gen_helper.nmake | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/builds/win32/gen_helper.nmake b/builds/win32/gen_helper.nmake index 20376da874e..4c799f8e4e3 100644 --- a/builds/win32/gen_helper.nmake +++ b/builds/win32/gen_helper.nmake @@ -19,18 +19,21 @@ RPL_AWK_SRC=$(MISC)\def_awk.c RPL_GREP=$(FB_GEN_DIR)\isc_grep.exe RPL_GREP_SRC=$(MISC)\isc_grep.c # These utils are platform and build independent, use fixed temporary dir. +UTL_TMP_DIR=$(FB_TEMP_DIR)\Release\misc updateCloopInterfaces: $(API_H_FILE) $(API_PAS_FILE) +$(UTL_TMP_DIR): + -mkdir $@ 2>nul $(API_H_FILE): $(IDL_FILE) $(CLOOP) $(IDL_FILE) c++ $@ IDL_FB_INTERFACES_H Firebird I -$(RPL_AWK): $(RPL_AWK_SRC) +$(RPL_AWK): $(RPL_AWK_SRC) $(UTL_TMP_DIR) # NMAKE strips trailing backslash during macro substitution that's why it is here and not in definition. $(CPP) /Fe$@ /Fo$(UTL_TMP_DIR)\ $(RPL_AWK_SRC) -$(RPL_GREP): $(RPL_GREP_SRC) +$(RPL_GREP): $(RPL_GREP_SRC) $(UTL_TMP_DIR) $(CPP) /Fe$@ /Fo$(UTL_TMP_DIR)\ $(RPL_GREP_SRC) $(FB_GEN_DIR)\iberror.pas: $(ERR_CONSTS_PAS) $(FB_ROOT_PATH)\src\include\firebird\impl\msg\*.h From ab9432ce9adf9d488994a420f685c28c1a96be9f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 29 Dec 2025 20:21:53 +0000 Subject: [PATCH 53/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 4c858ce3950..f47ca9c25eb 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1383 + FORMAL BUILD NUMBER:1384 */ -#define PRODUCT_VER_STRING "6.0.0.1383" -#define FILE_VER_STRING "WI-T6.0.0.1383" -#define LICENSE_VER_STRING "WI-T6.0.0.1383" -#define FILE_VER_NUMBER 6, 0, 0, 1383 +#define PRODUCT_VER_STRING "6.0.0.1384" +#define FILE_VER_STRING "WI-T6.0.0.1384" +#define LICENSE_VER_STRING "WI-T6.0.0.1384" +#define FILE_VER_NUMBER 6, 0, 0, 1384 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1383" +#define FB_BUILD_NO "1384" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index cece0cfc156..7e523ff635e 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1383 +BuildNum=1384 NowAt=`pwd` cd `dirname $0` From a7c8c64188e6326053092e9803f3f5202c343352 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Wed, 31 Dec 2025 23:27:11 -0300 Subject: [PATCH 54/71] Fix undefined message number error in DELETE WHERE CURRENT OF RETURNING Regression of #8082 reported in 2025-05-16 --- src/dsql/StmtNodes.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index b3c50644f9c..4b13b923bec 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -2389,6 +2389,9 @@ string EraseNode::internalPrint(NodePrinter& printer) const // RETURNING specified. void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch) { + if (dsqlScratch->recordKeyMessage) + GEN_port(dsqlScratch, dsqlScratch->recordKeyMessage); + std::optional tableNumber; const bool skipLocked = dsqlRse && dsqlRse->hasSkipLocked(); From 3767287c4f5bd8b3a31d4572c5f41e5d61717bd8 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 1 Jan 2026 20:21:38 +0000 Subject: [PATCH 55/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index f47ca9c25eb..8bd3883fdfe 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1384 + FORMAL BUILD NUMBER:1385 */ -#define PRODUCT_VER_STRING "6.0.0.1384" -#define FILE_VER_STRING "WI-T6.0.0.1384" -#define LICENSE_VER_STRING "WI-T6.0.0.1384" -#define FILE_VER_NUMBER 6, 0, 0, 1384 +#define PRODUCT_VER_STRING "6.0.0.1385" +#define FILE_VER_STRING "WI-T6.0.0.1385" +#define LICENSE_VER_STRING "WI-T6.0.0.1385" +#define FILE_VER_NUMBER 6, 0, 0, 1385 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1384" +#define FB_BUILD_NO "1385" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 7e523ff635e..eed03cd75e6 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1384 +BuildNum=1385 NowAt=`pwd` cd `dirname $0` From 77c8c9d5c24adba15f7e0b1973275fed8047c21a Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Fri, 2 Jan 2026 19:35:26 -0300 Subject: [PATCH 56/71] Fix #8842 - GTT accept weird syntax and has unnecessary syntax conflicts --- src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index d18086526cc..08c7529f156 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -134 shift/reduce conflicts, 13 reduce/reduce conflicts. +134 shift/reduce conflicts, 7 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 01eae97c432..5f6d7752bfa 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -2396,12 +2396,6 @@ sql_security_clause | SQL SECURITY INVOKER { $$ = false; } ; -%type sql_security_clause_opt -sql_security_clause_opt - : /* nothing */ { $$ = TriState::empty(); } - | sql_security_clause { $$ = $1; } - ; - %type publication_state publication_state : ENABLE PUBLICATION { $$ = true; } @@ -2415,7 +2409,7 @@ gtt_table_clause $$ = newNode($1); $$->relationType = std::nullopt; } - '(' table_elements($2) ')' gtt_ops($2) + '(' table_elements($2) ')' gtt_subclauses_opt($2) { $$ = $2; if (!$$->relationType.has_value()) @@ -2423,16 +2417,21 @@ gtt_table_clause } ; -%type gtt_ops() -gtt_ops($createRelationNode) - : gtt_op($createRelationNode) - | gtt_ops ',' gtt_op($createRelationNode) +%type gtt_subclauses_opt() +gtt_subclauses_opt($createRelationNode) + : // nothing by default. Will be set "on commit delete rows" in dsqlPass + | gtt_subclauses($createRelationNode) ; -%type gtt_op() -gtt_op($createRelationNode) - : // nothing by default. Will be set "on commit delete rows" in dsqlPass - | sql_security_clause_opt +%type gtt_subclauses() +gtt_subclauses($createRelationNode) + : gtt_subclause($createRelationNode) + | gtt_subclauses ',' gtt_subclause($createRelationNode) + ; + +%type gtt_subclause() +gtt_subclause($createRelationNode) + : sql_security_clause { setClause($createRelationNode->ssDefiner, "SQL SECURITY", $1); } | ON COMMIT DELETE ROWS { setClause($createRelationNode->relationType, "ON COMMIT DELETE ROWS", rel_global_temp_delete); } From b40984e3ea144b42ff5a069f4faa2c7ea2f359a3 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Sat, 3 Jan 2026 15:12:11 +0300 Subject: [PATCH 57/71] Misc correction --- src/jrd/dyn_util.epp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jrd/dyn_util.epp b/src/jrd/dyn_util.epp index 7c962d9223c..e67219ec7d5 100644 --- a/src/jrd/dyn_util.epp +++ b/src/jrd/dyn_util.epp @@ -287,8 +287,8 @@ bool DYN_UTIL_check_unique_name_nothrow(thread_db* tdbb, jrd_tra* transaction, { fb_assert(object_name.schema.isEmpty()); - static const CachedRequestId schemaHandleId; - requestHandle.reset(tdbb, schemaHandleId); + static const CachedRequestId filterHandleId; + requestHandle.reset(tdbb, filterHandleId); FOR(REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) FIL IN RDB$FILTERS From abec538543291be976924a9d6512673135a94e31 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 3 Jan 2026 20:20:28 +0000 Subject: [PATCH 58/71] increment build number --- src/jrd/build_no.h | 12 ++++++------ src/misc/writeBuildNum.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index 8bd3883fdfe..0e28477dbb4 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:1385 + FORMAL BUILD NUMBER:1387 */ -#define PRODUCT_VER_STRING "6.0.0.1385" -#define FILE_VER_STRING "WI-T6.0.0.1385" -#define LICENSE_VER_STRING "WI-T6.0.0.1385" -#define FILE_VER_NUMBER 6, 0, 0, 1385 +#define PRODUCT_VER_STRING "6.0.0.1387" +#define FILE_VER_STRING "WI-T6.0.0.1387" +#define LICENSE_VER_STRING "WI-T6.0.0.1387" +#define FILE_VER_NUMBER 6, 0, 0, 1387 #define FB_MAJOR_VER "6" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "1385" +#define FB_BUILD_NO "1387" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 6.0 Initial" diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index eed03cd75e6..066d01c76ae 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=6 MinorVer=0 RevNo=0 -BuildNum=1385 +BuildNum=1387 NowAt=`pwd` cd `dirname $0` From e88f729078ff369d5bf3ee3b73c3307684ccd9da Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Sat, 3 Jan 2026 14:07:35 -0300 Subject: [PATCH 59/71] Fix unused variable warnings --- src/alice/tdr.cpp | 1 - src/burp/burp.cpp | 1 - src/burp/restore.epp | 2 +- src/common/ThreadStart.cpp | 2 +- src/common/classes/DbImplementation.cpp | 76 ++++++++++---------- src/common/classes/InternalMessageBuffer.cpp | 1 - src/common/classes/alloc.cpp | 1 - src/common/config/config.cpp | 8 +-- src/common/pretty.cpp | 15 ---- src/common/xdr.cpp | 2 - src/dsql/DsqlBatch.cpp | 1 - src/dsql/DsqlStatements.cpp | 2 - src/dsql/ExprNodes.cpp | 6 -- src/dsql/StmtNodes.cpp | 3 - src/dsql/make.cpp | 2 - src/gpre/gpre.cpp | 2 +- src/gpre/obj_cxx.cpp | 1 - src/isql/isql.epp | 9 +-- src/isql/show.epp | 1 - src/jrd/BlobUtil.cpp | 4 -- src/jrd/Monitoring.cpp | 2 - src/jrd/ProfilerManager.cpp | 1 - src/jrd/RecordSourceNodes.cpp | 4 -- src/jrd/Relation.cpp | 1 - src/jrd/btr.cpp | 2 - src/jrd/cch.cpp | 2 - src/jrd/cmp.cpp | 1 - src/jrd/extds/ExtDS.cpp | 1 - src/jrd/idx.cpp | 5 -- src/jrd/jrd.cpp | 2 +- src/jrd/nbak.cpp | 2 - src/jrd/ods.cpp | 8 --- src/jrd/os/posix/unix.cpp | 2 - src/jrd/recsrc/RecordSource.cpp | 1 - src/jrd/replication/Applier.cpp | 2 - src/jrd/sdw.cpp | 1 - src/jrd/shut.cpp | 2 - src/jrd/sqz.cpp | 1 - src/jrd/tra.cpp | 5 -- src/jrd/trace/TraceManager.cpp | 5 -- src/jrd/vio.cpp | 1 - src/remote/client/interface.cpp | 2 - src/remote/remote.cpp | 1 - src/remote/server/server.cpp | 2 - src/utilities/gstat/dba.epp | 2 +- src/utilities/nbackup/nbackup.cpp | 6 -- src/yvalve/utl.cpp | 2 - src/yvalve/why.cpp | 3 - 48 files changed, 49 insertions(+), 160 deletions(-) diff --git a/src/alice/tdr.cpp b/src/alice/tdr.cpp index 7b3ef05adc5..c8dc9b4d31c 100644 --- a/src/alice/tdr.cpp +++ b/src/alice/tdr.cpp @@ -296,7 +296,6 @@ void TDR_list_limbo(FB_API_HANDLE handle, const TEXT* name, const SINT64 switche if (item == isc_info_end) break; - const USHORT length = (USHORT) p.getClumpLength(); switch (item) { case isc_info_limbo: diff --git a/src/burp/burp.cpp b/src/burp/burp.cpp index a8086d659fe..a1b7f4d4598 100644 --- a/src/burp/burp.cpp +++ b/src/burp/burp.cpp @@ -1843,7 +1843,6 @@ void BURP_print_warning(const Firebird::IStatus* status, bool printErrorAsWarnin return; BurpMaster master; - BurpGlobals* tdgbl = master.get(); // print the warning message SCHAR s[1024]; diff --git a/src/burp/restore.epp b/src/burp/restore.epp index b78afb757e6..87a2ef423ed 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -1332,7 +1332,7 @@ SLONG get_compressed(BurpGlobals* tdgbl, UCHAR* buffer, SLONG length) // msg 202: adjusting a decompression length error: invalid length %d was adjusted to %d count = -length; } - const UCHAR c = *p = get(tdgbl); + *p = get(tdgbl); ++p; length -= -count; } diff --git a/src/common/ThreadStart.cpp b/src/common/ThreadStart.cpp index 2b1faded1b9..c066d4c526e 100644 --- a/src/common/ThreadStart.cpp +++ b/src/common/ThreadStart.cpp @@ -119,7 +119,6 @@ Thread Thread::start(ThreadEntryPoint* routine, void* arg, int priority_arg, Han **************************************/ pthread_t thread; pthread_t* p_thread = p_handle ? p_handle : &thread; - pthread_attr_t pattr; int state; #if defined (LINUX) || defined (FREEBSD) @@ -132,6 +131,7 @@ Thread Thread::start(ThreadEntryPoint* routine, void* arg, int priority_arg, Han Firebird::system_call_failed::raise("pthread_detach", state); } #else + pthread_attr_t pattr; state = pthread_attr_init(&pattr); if (state) Firebird::system_call_failed::raise("pthread_attr_init", state); diff --git a/src/common/classes/DbImplementation.cpp b/src/common/classes/DbImplementation.cpp index dea39dd8112..9fb78c56da9 100644 --- a/src/common/classes/DbImplementation.cpp +++ b/src/common/classes/DbImplementation.cpp @@ -31,44 +31,44 @@ namespace { -static constexpr UCHAR CpuIntel = 0; -static constexpr UCHAR CpuAmd = 1; -static constexpr UCHAR CpuUltraSparc = 2; -static constexpr UCHAR CpuPowerPc = 3; -static constexpr UCHAR CpuPowerPc64 = 4; -static constexpr UCHAR CpuMipsel = 5; -static constexpr UCHAR CpuMips = 6; -static constexpr UCHAR CpuArm = 7; -static constexpr UCHAR CpuIa64 = 8; -static constexpr UCHAR CpuS390 = 9; -static constexpr UCHAR CpuS390x = 10; -static constexpr UCHAR CpuSh = 11; -static constexpr UCHAR CpuSheb = 12; -static constexpr UCHAR CpuHppa = 13; -static constexpr UCHAR CpuAlpha = 14; -static constexpr UCHAR CpuArm64 = 15; -static constexpr UCHAR CpuPowerPc64el = 16; -static constexpr UCHAR CpuM68k = 17; -static constexpr UCHAR CpuRiscV64 = 18; -static constexpr UCHAR CpuMips64el = 19; -static constexpr UCHAR CpuLoongArch = 20; - -static constexpr UCHAR OsWindows = 0; -static constexpr UCHAR OsLinux = 1; -static constexpr UCHAR OsDarwin = 2; -static constexpr UCHAR OsSolaris = 3; -static constexpr UCHAR OsHpux = 4; -static constexpr UCHAR OsAix = 5; -static constexpr UCHAR OsMms = 6; -static constexpr UCHAR OsFreeBsd = 7; -static constexpr UCHAR OsNetBsd = 8; - -static constexpr UCHAR CcMsvc = 0; -static constexpr UCHAR CcGcc = 1; -static constexpr UCHAR CcXlc = 2; -static constexpr UCHAR CcAcc = 3; -static constexpr UCHAR CcSunStudio = 4; -static constexpr UCHAR CcIcc = 5; +[[maybe_unused]] static constexpr UCHAR CpuIntel = 0; +[[maybe_unused]] static constexpr UCHAR CpuAmd = 1; +[[maybe_unused]] static constexpr UCHAR CpuUltraSparc = 2; +[[maybe_unused]] static constexpr UCHAR CpuPowerPc = 3; +[[maybe_unused]] static constexpr UCHAR CpuPowerPc64 = 4; +[[maybe_unused]] static constexpr UCHAR CpuMipsel = 5; +[[maybe_unused]] static constexpr UCHAR CpuMips = 6; +[[maybe_unused]] static constexpr UCHAR CpuArm = 7; +[[maybe_unused]] static constexpr UCHAR CpuIa64 = 8; +[[maybe_unused]] static constexpr UCHAR CpuS390 = 9; +[[maybe_unused]] static constexpr UCHAR CpuS390x = 10; +[[maybe_unused]] static constexpr UCHAR CpuSh = 11; +[[maybe_unused]] static constexpr UCHAR CpuSheb = 12; +[[maybe_unused]] static constexpr UCHAR CpuHppa = 13; +[[maybe_unused]] static constexpr UCHAR CpuAlpha = 14; +[[maybe_unused]] static constexpr UCHAR CpuArm64 = 15; +[[maybe_unused]] static constexpr UCHAR CpuPowerPc64el = 16; +[[maybe_unused]] static constexpr UCHAR CpuM68k = 17; +[[maybe_unused]] static constexpr UCHAR CpuRiscV64 = 18; +[[maybe_unused]] static constexpr UCHAR CpuMips64el = 19; +[[maybe_unused]] static constexpr UCHAR CpuLoongArch = 20; + +[[maybe_unused]] static constexpr UCHAR OsWindows = 0; +[[maybe_unused]] static constexpr UCHAR OsLinux = 1; +[[maybe_unused]] static constexpr UCHAR OsDarwin = 2; +[[maybe_unused]] static constexpr UCHAR OsSolaris = 3; +[[maybe_unused]] static constexpr UCHAR OsHpux = 4; +[[maybe_unused]] static constexpr UCHAR OsAix = 5; +[[maybe_unused]] static constexpr UCHAR OsMms = 6; +[[maybe_unused]] static constexpr UCHAR OsFreeBsd = 7; +[[maybe_unused]] static constexpr UCHAR OsNetBsd = 8; + +[[maybe_unused]] static constexpr UCHAR CcMsvc = 0; +[[maybe_unused]] static constexpr UCHAR CcGcc = 1; +[[maybe_unused]] static constexpr UCHAR CcXlc = 2; +[[maybe_unused]] static constexpr UCHAR CcAcc = 3; +[[maybe_unused]] static constexpr UCHAR CcSunStudio = 4; +[[maybe_unused]] static constexpr UCHAR CcIcc = 5; static constexpr UCHAR EndianLittle = 0; static constexpr UCHAR EndianBig = 1; diff --git a/src/common/classes/InternalMessageBuffer.cpp b/src/common/classes/InternalMessageBuffer.cpp index 5980b5583b9..2a2f82d961b 100644 --- a/src/common/classes/InternalMessageBuffer.cpp +++ b/src/common/classes/InternalMessageBuffer.cpp @@ -76,7 +76,6 @@ MetadataFromBlr::MetadataFromBlr(unsigned aBlrLength, const unsigned char* aBlr, fb_assert(!(count & 1)); count /= 2; - unsigned offset = 0; items.grow(count); for (unsigned index = 0; index < count; index++) diff --git a/src/common/classes/alloc.cpp b/src/common/classes/alloc.cpp index 8a1cfcfc1f1..4bb831d0d64 100644 --- a/src/common/classes/alloc.cpp +++ b/src/common/classes/alloc.cpp @@ -171,7 +171,6 @@ void corrupt(const char* text) noexcept } Firebird::Mutex* cache_mutex = NULL; -int dev_zero_fd = 0; #if defined(WIN_NT) size_t get_page_size() diff --git a/src/common/config/config.cpp b/src/common/config/config.cpp index a78de76ce10..0589fbd435d 100644 --- a/src/common/config/config.cpp +++ b/src/common/config/config.cpp @@ -624,13 +624,13 @@ bool Config::getDefaultValue(unsigned int key, string& str) #define DECLARE_GLOBAL_KEY(KEY) \ static_assert(entries[KEY].is_global, "Requires global key"); \ - const ConfigKey key = KEY; \ - const Config* config = getDefaultConfig(); + [[maybe_unused]] const ConfigKey key = KEY; \ + [[maybe_unused]] const Config* config = getDefaultConfig(); #define DECLARE_PER_DB_KEY(KEY) \ static_assert(!entries[KEY].is_global, "Requires per-database key"); \ - const ConfigKey key = KEY; \ - const Config* config = this; + [[maybe_unused]] const ConfigKey key = KEY; \ + [[maybe_unused]] const Config* config = this; int Config::getServerMode() { diff --git a/src/common/pretty.cpp b/src/common/pretty.cpp index 2b669fb8db0..44b883b7755 100644 --- a/src/common/pretty.cpp +++ b/src/common/pretty.cpp @@ -115,21 +115,6 @@ constexpr const char *sdl_table[] = NULL }; -constexpr const char *map_strings[] = -{ - "FIELD2", - "FIELD1", - "MESSAGE", - "TERMINATOR", - "TERMINATING_FIELD", - "OPAQUE", - "TRANSPARENT", - "TAG", - "SUB_FORM", - "ITEM_INDEX", - "SUB_FIELD" -}; - //____________________________________________________________ // // Pretty print create database parameter buffer thru callback routine. diff --git a/src/common/xdr.cpp b/src/common/xdr.cpp index adc4c47689b..73eb6a7eba0 100644 --- a/src/common/xdr.cpp +++ b/src/common/xdr.cpp @@ -91,8 +91,6 @@ inline bool_t PUTLONG(xdr_t* xdrs, const SLONG* lp) return xdrs->x_putbytes(reinterpret_cast(&l), 4); } -static SCHAR zeros[4] = { 0, 0, 0, 0 }; - bool_t xdr_hyper( xdr_t* xdrs, void* pi64) { diff --git a/src/dsql/DsqlBatch.cpp b/src/dsql/DsqlBatch.cpp index 916d0dfe791..92929547daa 100644 --- a/src/dsql/DsqlBatch.cpp +++ b/src/dsql/DsqlBatch.cpp @@ -694,7 +694,6 @@ Firebird::IBatchCompletionState* DsqlBatch::execute(thread_db* tdbb) continue; } - const bool start = startRequest; if (startRequest) { EXE_unwind(tdbb, req); diff --git a/src/dsql/DsqlStatements.cpp b/src/dsql/DsqlStatements.cpp index 21facd79f09..d656534fc5e 100644 --- a/src/dsql/DsqlStatements.cpp +++ b/src/dsql/DsqlStatements.cpp @@ -149,8 +149,6 @@ void DsqlDmlStatement::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, n GEN_statement(scratch, node); - unsigned messageNumber = 0; - // have the access method compile the statement #ifdef DSQL_DEBUG diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 2601077f574..7d74b33b55f 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -2814,7 +2814,6 @@ dsc* ArithmeticNode::addSqlTime(thread_db* tdbb, const dsc* desc, impure_value* fb_assert(blrOp == blr_add || blrOp == blr_subtract); dsc* result = &value->vlu_desc; - Attachment* const attachment = tdbb->getAttachment(); fb_assert(value->vlu_desc.isTime() || desc->isTime()); @@ -8229,7 +8228,6 @@ void LiteralNode::fixMinSInt64(MemoryPool& pool) { // MIN_SINT64 should be stored as BIGINT, not 128-bit integer - const UCHAR* s = litDesc.dsc_address; const char* minSInt64 = "9223372036854775808"; bool hasDot = false; int scale = 0; @@ -8267,7 +8265,6 @@ void LiteralNode::fixMinSInt128(MemoryPool& pool) { // MIN_SINT128 should be stored as INT128, not decfloat - const UCHAR* s = litDesc.dsc_address; const char* const minSInt128 = "170141183460469231731687303715884105728"; const char* minPtr = minSInt128; bool hasDot = false; @@ -10158,7 +10155,6 @@ string RecordKeyNode::internalPrint(NodePrinter& printer) const // Resolve a dbkey to an available context. ValueExprNode* RecordKeyNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { - thread_db* tdbb = JRD_get_thread_data(); DsqlContextStack contexts; ValueExprNode* node = nullptr; @@ -13634,8 +13630,6 @@ dsc* UdfCallNode::execute(thread_db* tdbb, Request* request) const { const_cast(function.getObject())->checkReload(tdbb); - Jrd::Attachment* attachment = tdbb->getAttachment(); - const ULONG inMsgLength = function->getInputFormat() ? function->getInputFormat()->fmt_length : 0; const ULONG outMsgLength = function->getOutputFormat()->fmt_length; UCHAR* const inMsg = FB_ALIGN(impure + sizeof(impure_value), FB_ALIGNMENT); diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 4b13b923bec..7f084ad3d05 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -6527,7 +6527,6 @@ void LocalDeclarationsNode::genBlr(DsqlCompilerScratch* dsqlScratch) // EXECUTE BLOCK needs "ports", which creates DSQL messages using the client charset. // Sub routine doesn't need ports and should generate BLR as declared in its metadata. const bool isSubRoutine = dsqlScratch->flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE; - const auto& variables = isSubRoutine ? dsqlScratch->outputVariables : dsqlScratch->variables; Array declaredVariables; @@ -11246,7 +11245,6 @@ static RseNode* dsqlPassCursorReference(DsqlCompilerScratch* dsqlScratch, const { DEV_BLKCHK(dsqlScratch, dsql_type_req); - thread_db* tdbb = JRD_get_thread_data(); // Use scratch pool because none of created object is stored anywhere MemoryPool& pool = dsqlScratch->getPool(); @@ -11868,7 +11866,6 @@ static StmtNode* pass1ExpandView(thread_db* tdbb, CompilerScratch* csb, StreamTy jrd_rel* relation = csb->csb_rpt[orgStream].csb_relation; vec* fields = relation->rel_fields; - dsc desc; USHORT id = 0, newId = 0; vec::iterator ptr = fields->begin(); diff --git a/src/dsql/make.cpp b/src/dsql/make.cpp index 0928ccd182f..dfde9062099 100644 --- a/src/dsql/make.cpp +++ b/src/dsql/make.cpp @@ -522,8 +522,6 @@ dsql_par* MAKE_parameter(dsql_msg* message, bool sqlda_flag, bool null_flag, } } - thread_db* tdbb = JRD_get_thread_data(); - if (message->msg_parameter == MAX_USHORT) { string msg; diff --git a/src/gpre/gpre.cpp b/src/gpre/gpre.cpp index e07542d7225..24e04b6829e 100644 --- a/src/gpre/gpre.cpp +++ b/src/gpre/gpre.cpp @@ -2484,8 +2484,8 @@ static void pass2( SLONG start_position) SLONG current = 1 + start_position; SLONG column = 0; - const SSHORT comment_start_len = static_cast(strlen(comment_start)); #if defined(GPRE_COBOL) + const SSHORT comment_start_len = static_cast(strlen(comment_start)); SSHORT to_skip = 0; #endif diff --git a/src/gpre/obj_cxx.cpp b/src/gpre/obj_cxx.cpp index b863f2cf3e6..dcc2735d45b 100644 --- a/src/gpre/obj_cxx.cpp +++ b/src/gpre/obj_cxx.cpp @@ -135,7 +135,6 @@ static const TEXT* global_status_name = 0; constexpr int INDENT = 3; static constexpr const char* NULL_STRING = "NULL"; -static constexpr const char* NULL_STATUS = "NULL"; static constexpr const char* NULL_SQLDA = "NULL"; static constexpr const char* GDS_INCLUDE = ""; diff --git a/src/isql/isql.epp b/src/isql/isql.epp index a2c5c8225ee..daab2b9d08f 100644 --- a/src/isql/isql.epp +++ b/src/isql/isql.epp @@ -158,7 +158,6 @@ constexpr int INT64_LEN = 21; // NUMERIC(18,2) = -92233720368547758.08 //constexpr int QUAD_LEN = 19; constexpr int FLOAT_LEN = 14; // -1.2345678E+38 constexpr int DOUBLE_LEN = 23; // -1.234567890123456E+300 -constexpr int DATE_LEN = 11; // 11 for date only constexpr int DATETIME_LEN = 25; // 25 for date-time constexpr int DATETIME_TZ_LEN = DATETIME_LEN - 1 + 1 + TimeZoneUtil::MAX_LEN; // DATETIME_LEN should be 24 constexpr int TIME_ONLY_LEN = 13; // 13 for time only @@ -167,8 +166,6 @@ constexpr int DATE_ONLY_LEN = 11; constexpr int BOOLEAN_LEN = 7; // constexpr int UNKNOWN_LEN = 20; // Unknown type: %d -constexpr int MAX_TERMS = 10; // max # of terms in an interactive cmd - constexpr const char* ISQL_COUNTERS_SET = "CurrentMemory, MaxMemory, RealTime, UserTime, Buffers, Reads, Writes, Fetches"; constexpr int ISQL_COUNTERS = 8; @@ -177,7 +174,7 @@ constexpr const char* UNKNOWN = "*unknown*"; namespace IcuUtil { // Duplicate from ICU to not need to link ISQL with it. It's used by U8_NEXT_UNSAFE. - static constexpr uint8_t utf8_countTrailBytes[256] = { + [[maybe_unused]] static constexpr uint8_t utf8_countTrailBytes[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -2432,7 +2429,6 @@ static processing_state add_row(const QualifiedMetaString& tabname) unsigned type = msg->getType(fbStatus, i); if (ISQL_errmsg(fbStatus)) return (SKIP); - const bool nullable = msg->isNullable(fbStatus, i); if (ISQL_errmsg(fbStatus)) return (SKIP); unsigned varLength; @@ -3145,7 +3141,7 @@ static processing_state bulk_insert_hack(const char* command) const char* lastPos = insert; Extender extender; // Used only for multi-line tuples. - for (unsigned i = 0, textFieldIter = 0; i < n_cols && !done; ++i) + for (unsigned i = 0; i < n_cols && !done; ++i) { unsigned offset = message->getNullOffset(fbStatus, i); if (ISQL_errmsg(fbStatus)) @@ -3160,7 +3156,6 @@ static processing_state bulk_insert_hack(const char* command) unsigned type = message->getType(fbStatus, i); if (ISQL_errmsg(fbStatus)) return (SKIP); - const bool nullable = message->isNullable(fbStatus, i); if (ISQL_errmsg(fbStatus)) return (SKIP); diff --git a/src/isql/show.epp b/src/isql/show.epp index 6f190c5bac6..23d09259305 100644 --- a/src/isql/show.epp +++ b/src/isql/show.epp @@ -4303,7 +4303,6 @@ static processing_state show_func(const QualifiedMetaString& name) } bool first_param = true; - const SSHORT default_charset = ISQL_get_default_char_set_id(); FOR ARG IN RDB$FUNCTION_ARGUMENTS CROSS FLD IN RDB$FIELDS diff --git a/src/jrd/BlobUtil.cpp b/src/jrd/BlobUtil.cpp index 1dec99daf97..18ca4a4270c 100644 --- a/src/jrd/BlobUtil.cpp +++ b/src/jrd/BlobUtil.cpp @@ -67,7 +67,6 @@ IExternalResultSet* BlobUtilPackage::cancelBlobProcedure(ThrowStatusExceptionWra IExternalContext* context, const BlobMessage::Type* in, void*) { const auto tdbb = JRD_get_thread_data(); - const auto transaction = tdbb->getTransaction(); const auto blobId = *(bid*) &in->blob; @@ -102,7 +101,6 @@ void BlobUtilPackage::isWritableFunction(ThrowStatusExceptionWrapper* status, IExternalContext* context, const BlobMessage::Type* in, BooleanMessage::Type* out) { const auto tdbb = JRD_get_thread_data(); - const auto transaction = tdbb->getTransaction(); const auto blobId = *(bid*) &in->blob; @@ -161,7 +159,6 @@ void BlobUtilPackage::seekFunction(ThrowStatusExceptionWrapper* status, IExternalContext* context, const SeekInput::Type* in, SeekOutput::Type* out) { const auto tdbb = JRD_get_thread_data(); - const auto transaction = tdbb->getTransaction(); const auto blob = getBlobFromHandle(tdbb, in->handle); if (!(in->mode >= 0 && in->mode <= 2)) @@ -185,7 +182,6 @@ void BlobUtilPackage::readDataFunction(ThrowStatusExceptionWrapper* status, status_exception::raise(Arg::Gds(isc_random) << "Length must be NULL or greater than 0"); const auto tdbb = JRD_get_thread_data(); - const auto transaction = tdbb->getTransaction(); const auto blob = getBlobFromHandle(tdbb, in->handle); if (in->lengthNull) diff --git a/src/jrd/Monitoring.cpp b/src/jrd/Monitoring.cpp index 07f64418679..7e56fa88388 100644 --- a/src/jrd/Monitoring.cpp +++ b/src/jrd/Monitoring.cpp @@ -1042,8 +1042,6 @@ void Monitoring::putAttachment(SnapshotData::DumpRecord& record, const Jrd::Atta if (!attachment->att_user) return; - const auto* dbb = attachment->att_database; - record.reset(rel_mon_attachments); PathName attName(attachment->att_filename); diff --git a/src/jrd/ProfilerManager.cpp b/src/jrd/ProfilerManager.cpp index 1c45579bd9b..52265e5f4bf 100644 --- a/src/jrd/ProfilerManager.cpp +++ b/src/jrd/ProfilerManager.cpp @@ -258,7 +258,6 @@ IExternalResultSet* ProfilerPackage::cancelSessionProcedure(ThrowStatusException return nullptr; } - const auto* transaction = tdbb->getTransaction(); const auto profilerManager = attachment->getProfilerManager(tdbb); profilerManager->cancelSession(); diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 46042cafdaa..5e892959c32 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -2840,8 +2840,6 @@ void WindowSourceNode::collectStreams(SortedStreamList& streamList) const RecordSource* WindowSourceNode::compile(thread_db* tdbb, Optimizer* opt, bool /*innerSubStream*/) { - const auto csb = opt->getCompilerScratch(); - return FB_NEW_POOL(*tdbb->getDefaultPool()) WindowedStream(tdbb, opt, windows, opt->compile(rse, NULL)); } @@ -4311,8 +4309,6 @@ void TableValueFunctionSourceNode::setDefaultNameField(DsqlCompilerScratch* /*ds { const auto nameFunc = tableValueFunctionContext->funName; - auto i = 0U; - if ((nameFunc == UnlistFunctionSourceNode::FUNC_NAME) || (nameFunc == GenSeriesFunctionSourceNode::FUNC_NAME)) { diff --git a/src/jrd/Relation.cpp b/src/jrd/Relation.cpp index 4a40f874761..9713d45344b 100644 --- a/src/jrd/Relation.cpp +++ b/src/jrd/Relation.cpp @@ -214,7 +214,6 @@ void jrd_rel::retainPages(thread_db* tdbb, TraNumber oldNumber, TraNumber newNum if (!rel_pages_inst) return; - const SINT64 inst_id = oldNumber; FB_SIZE_T pos; if (!rel_pages_inst->find(oldNumber, pos)) return; diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index 1fc07ca7660..5554dd4910c 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -2811,7 +2811,6 @@ static void compress(thread_db* tdbb, constexpr UCHAR desc_end_value_prefix = 0x01; // ~0xFE constexpr UCHAR desc_end_value_check = 0x00; // ~0xFF; - const Database* dbb = tdbb->getDatabase(); bool first_key = true; VaryStr buffer; size_t multiKeyLength; @@ -5670,7 +5669,6 @@ static void generate_jump_nodes(thread_db* tdbb, btree_page* page, const UCHAR* const startpoint = page->btr_nodes + page->btr_jump_size; const UCHAR* const endpoint = (UCHAR*) page + page->btr_length; const UCHAR* halfpoint = (UCHAR*) page + (BTR_SIZE + page->btr_jump_size + page->btr_length) / 2; - const UCHAR* const excludePointer = (UCHAR*) page + excludeOffset; IndexJumpNode jumpNode; IndexNode node; diff --git a/src/jrd/cch.cpp b/src/jrd/cch.cpp index ecb7ed947bb..6a84fe15da3 100644 --- a/src/jrd/cch.cpp +++ b/src/jrd/cch.cpp @@ -2488,7 +2488,6 @@ bool CCH_write_all_shadows(thread_db* tdbb, Shadow* shadow, BufferDesc* bdb, Ods if (bdb->bdb_page == HEADER_PAGE_NUMBER) { // fixup header for shadow file - jrd_file* shadow_file = sdw->sdw_file; header_page* header = (header_page*) page; PageSpace* pageSpaceID = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE); @@ -2721,7 +2720,6 @@ static void flushAll(thread_db* tdbb, USHORT flush_flag) const bool all_flag = (flush_flag & FLUSH_ALL) != 0; const bool sweep_flag = (flush_flag & FLUSH_SWEEP) != 0; const bool release_flag = (flush_flag & FLUSH_RLSE) != 0; - const bool write_thru = release_flag; { Sync bcbSync(&bcb->bcb_syncObject, FB_FUNCTION); diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index 031515c5170..a94dd1570c4 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -292,7 +292,6 @@ IndexLock* CMP_get_index_lock(thread_db* tdbb, jrd_rel* relation, USHORT id) * **************************************/ SET_TDBB(tdbb); - Database* dbb = tdbb->getDatabase(); DEV_BLKCHK(relation, type_rel); diff --git a/src/jrd/extds/ExtDS.cpp b/src/jrd/extds/ExtDS.cpp index 15ee3112d59..bc788ec62e6 100644 --- a/src/jrd/extds/ExtDS.cpp +++ b/src/jrd/extds/ExtDS.cpp @@ -394,7 +394,6 @@ Connection* Provider::getBoundConnection(Jrd::thread_db* tdbb, const Firebird::PathName& dbName, Firebird::ClumpletReader& dpb, TraScope tra_scope, bool isCurrentAtt) { - Database* dbb = tdbb->getDatabase(); Attachment* att = tdbb->getAttachment(); CryptHash ch; if (!isCurrentAtt) diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp index 86f889d2bc2..ae5a42e693e 100644 --- a/src/jrd/idx.cpp +++ b/src/jrd/idx.cpp @@ -573,7 +573,6 @@ bool IndexCreateTask::handler(WorkItem& _item) const bool isPrimary = (idx->idx_flags & idx_primary); const bool isForeign = (idx->idx_flags & idx_foreign); const UCHAR pad = isDescending ? -1 : 0; - bool key_is_null = false; primary.rpb_number.compose(dbb->dbb_max_records, dbb->dbb_dp_per_pp, 0, 0, item->m_ppSequence); primary.rpb_number.decrement(); @@ -847,7 +846,6 @@ void IDX_create_index(thread_db* tdbb, **************************************/ SET_TDBB(tdbb); Database* dbb = tdbb->getDatabase(); - Jrd::Attachment* attachment = tdbb->getAttachment(); if (relation->rel_file) { @@ -865,7 +863,6 @@ void IDX_create_index(thread_db* tdbb, fb_assert(transaction); const bool isDescending = (idx->idx_flags & idx_descending); - const bool isPrimary = (idx->idx_flags & idx_primary); const bool isForeign = (idx->idx_flags & idx_foreign); // hvlad: in ODS11 empty string and NULL values can have the same binary @@ -1687,8 +1684,6 @@ static idx_e check_duplicates(thread_db* tdbb, * a unique index or a foreign key. * **************************************/ - DSC desc1, desc2; - SET_TDBB(tdbb); idx_e result = idx_e_ok; diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index dcbddf62e9d..b05b5ccbabb 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -282,7 +282,7 @@ const Attachment* JAttachment::getHandle() const noexcept void JAttachment::addRef() { - const int v = ++refCounter; + [[maybe_unused]] const int v = ++refCounter; #ifdef DEBUG_ATT_COUNTERS ReferenceCounterDebugger* my = ReferenceCounterDebugger::get(DEB_AR_JATT); const char* point = my ? my->rcd_point : " "; diff --git a/src/jrd/nbak.cpp b/src/jrd/nbak.cpp index be471beb9dd..098e5eb8ed4 100644 --- a/src/jrd/nbak.cpp +++ b/src/jrd/nbak.cpp @@ -162,7 +162,6 @@ BackupManager::StateWriteGuard::StateWriteGuard(thread_db* tdbb, Jrd::WIN* windo : m_tdbb(tdbb), m_window(NULL), m_success(false) { Database* const dbb = tdbb->getDatabase(); - Jrd::Attachment* const att = tdbb->getAttachment(); dbb->dbb_backup_manager->beginFlush(); CCH_flush(tdbb, FLUSH_ALL, 0); // Flush local cache to release all dirty pages @@ -186,7 +185,6 @@ BackupManager::StateWriteGuard::StateWriteGuard(thread_db* tdbb, Jrd::WIN* windo BackupManager::StateWriteGuard::~StateWriteGuard() { Database* const dbb = m_tdbb->getDatabase(); - Jrd::Attachment* const att = m_tdbb->getAttachment(); // It is important to set state into nbak_state_unknown *before* release of state lock, // otherwise someone could acquire state lock, fetch and modify some page before state will diff --git a/src/jrd/ods.cpp b/src/jrd/ods.cpp index baae1a0f13c..5a4f2d605c4 100644 --- a/src/jrd/ods.cpp +++ b/src/jrd/ods.cpp @@ -26,14 +26,6 @@ using namespace Firebird; -namespace -{ - constexpr FB_SIZE_T NEXT_INDEX = 0; - constexpr FB_SIZE_T OIT_INDEX = 1; - constexpr FB_SIZE_T OAT_INDEX = 2; - constexpr FB_SIZE_T OST_INDEX = 3; -} - namespace Ods { bool isSupported(const header_page* hdr) noexcept diff --git a/src/jrd/os/posix/unix.cpp b/src/jrd/os/posix/unix.cpp index b2573bf5020..f3d37aeb1ad 100644 --- a/src/jrd/os/posix/unix.cpp +++ b/src/jrd/os/posix/unix.cpp @@ -419,8 +419,6 @@ void PIO_force_write(jrd_file* file, const bool forceWrite) if (forceWrite != oldForce) { - const int control = forceWrite ? SYNC : 0; - #ifdef FCNTL_SYNC_BROKEN maybeCloseFile(file->fil_desc); diff --git a/src/jrd/recsrc/RecordSource.cpp b/src/jrd/recsrc/RecordSource.cpp index 56fa4a72bf1..bda4279a9ea 100644 --- a/src/jrd/recsrc/RecordSource.cpp +++ b/src/jrd/recsrc/RecordSource.cpp @@ -196,7 +196,6 @@ void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversio const index_desc& idx = retrieval->irb_desc; const USHORT segCount = idx.idx_count; - const USHORT minSegs = MIN(retrieval->irb_lower_count, retrieval->irb_upper_count); const USHORT maxSegs = MAX(retrieval->irb_lower_count, retrieval->irb_upper_count); const bool equality = (retrieval->irb_generic & irb_equality); diff --git a/src/jrd/replication/Applier.cpp b/src/jrd/replication/Applier.cpp index c9e9f39419a..02fc6d2e431 100644 --- a/src/jrd/replication/Applier.cpp +++ b/src/jrd/replication/Applier.cpp @@ -446,8 +446,6 @@ void Applier::process(thread_db* tdbb, ULONG length, const UCHAR* data) void Applier::startTransaction(thread_db* tdbb, TraNumber traNum) { - const auto attachment = tdbb->getAttachment(); - if (m_txnMap.exist(traNum)) raiseError("Transaction %" SQUADFORMAT" already exists", traNum); diff --git a/src/jrd/sdw.cpp b/src/jrd/sdw.cpp index aa6a1ef8b37..3a0c48aa0a9 100644 --- a/src/jrd/sdw.cpp +++ b/src/jrd/sdw.cpp @@ -1043,7 +1043,6 @@ static bool check_for_file(thread_db* tdbb, const SCHAR* name, USHORT length) **************************************/ SET_TDBB(tdbb); - const Database* dbb = tdbb->getDatabase(); const Firebird::PathName path(name, length); try { diff --git a/src/jrd/shut.cpp b/src/jrd/shut.cpp index ce7e5351203..18a487d1812 100644 --- a/src/jrd/shut.cpp +++ b/src/jrd/shut.cpp @@ -40,8 +40,6 @@ using namespace Jrd; using namespace Firebird; -constexpr SSHORT SHUT_WAIT_TIME = 5; - // Shutdown lock data union shutdown_data { diff --git a/src/jrd/sqz.cpp b/src/jrd/sqz.cpp index aa8027e2157..5ebcc7db49d 100644 --- a/src/jrd/sqz.cpp +++ b/src/jrd/sqz.cpp @@ -108,7 +108,6 @@ Compressor::Compressor(MemoryPool& pool, bool allowLongRuns, bool allowUnpacked, m_allowUnpacked(allowUnpacked) { const auto end = data + length; - const auto input = data; while (auto count = end - data) { diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index b2844c4f8a2..d9935f022e2 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -79,8 +79,6 @@ #include "firebird/impl/msg_helper.h" -constexpr int DYN_MSG_FAC = FB_IMPL_MSG_FACILITY_DYN; - using namespace Jrd; using namespace Ods; using namespace Firebird; @@ -4364,7 +4362,6 @@ void TraceSweepEvent::report(ntrace_process_state_t state) if (!m_need_trace) return; - const Database* dbb = m_tdbb->getDatabase(); TraceManager* trace_mgr = att->att_trace_manager; TraceConnectionImpl conn(att); @@ -4373,8 +4370,6 @@ void TraceSweepEvent::report(ntrace_process_state_t state) if (state != ITracePlugin::SWEEP_STATE_PROGRESS) m_base_stats.reset(); - const jrd_tra* tran = m_tdbb->getTransaction(); - TraceRuntimeStats stats(att, &m_base_stats, &att->att_stats, finiTime, 0); m_sweep_info.setStats(&stats); diff --git a/src/jrd/trace/TraceManager.cpp b/src/jrd/trace/TraceManager.cpp index c1ee9e8bd10..0a2f0857745 100644 --- a/src/jrd/trace/TraceManager.cpp +++ b/src/jrd/trace/TraceManager.cpp @@ -43,11 +43,6 @@ using namespace Firebird; -namespace -{ - static const char* const NTRACE_PREFIX = "fbtrace"; -} - namespace Jrd { GlobalPtr TraceManager::storageInstance; diff --git a/src/jrd/vio.cpp b/src/jrd/vio.cpp index 89266c49244..74849637b0b 100644 --- a/src/jrd/vio.cpp +++ b/src/jrd/vio.cpp @@ -429,7 +429,6 @@ bool SweepTask::handler(WorkItem& _item) RelInfo* relInfo = item->m_relInfo; Database* dbb = tdbb->getDatabase(); - Attachment* att = tdbb->getAttachment(); /*relation = (*att->att_relations)[relInfo->rel_id]; if (relation)*/ diff --git a/src/remote/client/interface.cpp b/src/remote/client/interface.cpp index 6c2d7b6067b..fc4b6d739ac 100644 --- a/src/remote/client/interface.cpp +++ b/src/remote/client/interface.cpp @@ -5027,8 +5027,6 @@ bool ResultSet::fetch(CheckStatusWrapper* status, void* buffer, P_FETCH operatio if (relative && adjustment) { - const bool isAhead = (statement->rsr_fetch_operation == fetch_next); - PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_fetch_scroll; P_SQLDATA* sqldata = &packet->p_sqldata; diff --git a/src/remote/remote.cpp b/src/remote/remote.cpp index 299b8cb01aa..7de4d2ded74 100644 --- a/src/remote/remote.cpp +++ b/src/remote/remote.cpp @@ -125,7 +125,6 @@ const ParametersSet connectParam = constexpr SLONG DUMMY_INTERVAL = 60; // seconds -constexpr int ATTACH_FAILURE_SPACE = 16 * 1024; // bytes void REMOTE_cleanup_transaction( Rtr* transaction) diff --git a/src/remote/server/server.cpp b/src/remote/server/server.cpp index 6cea42401fa..3a3ca2281ed 100644 --- a/src/remote/server/server.cpp +++ b/src/remote/server/server.cpp @@ -1078,8 +1078,6 @@ class CryptKeyTypeManager : public PermanentStorage class CryptKeyType; public: - static const unsigned BITS64 = sizeof(FB_UINT64) * 8u; - class SpecificPlugins { public: diff --git a/src/utilities/gstat/dba.epp b/src/utilities/gstat/dba.epp index 3171cc6e87e..53baa567a85 100644 --- a/src/utilities/gstat/dba.epp +++ b/src/utilities/gstat/dba.epp @@ -641,7 +641,7 @@ int gstat(Firebird::UtilSvc* uSvc) expandDatabaseName(fileName, tempStr, NULL); fileName = tempStr; - const dba_fil* file = db_open(fileName.c_str(), fileName.length()); + db_open(fileName.c_str(), fileName.length()); alignas(DIRECT_IO_BLOCK_SIZE) SCHAR temp[MAX(RAW_HEADER_SIZE, DIRECT_IO_BLOCK_SIZE)]; tddba->page_size = MAX(RAW_HEADER_SIZE, DIRECT_IO_BLOCK_SIZE); diff --git a/src/utilities/nbackup/nbackup.cpp b/src/utilities/nbackup/nbackup.cpp index 9d597a44d79..211a2c05ac9 100644 --- a/src/utilities/nbackup/nbackup.cpp +++ b/src/utilities/nbackup/nbackup.cpp @@ -82,12 +82,6 @@ #define O_LARGEFILE 0 #endif -// How much we align memory when reading database header. -// Sector alignment of memory is necessary to use unbuffered IO on Windows. -// Actually, sectors may be bigger than 1K, but let's be consistent with -// JRD regarding the matter for the moment. -constexpr FB_SIZE_T SECTOR_ALIGNMENT = PAGE_ALIGNMENT; - using namespace Firebird; namespace diff --git a/src/yvalve/utl.cpp b/src/yvalve/utl.cpp index 1361dd8168e..25610b6a13e 100644 --- a/src/yvalve/utl.cpp +++ b/src/yvalve/utl.cpp @@ -602,8 +602,6 @@ YAttachment* UtilInterface::executeCreateDatabase2( return NULL; } - bool v3Error = false; - if (!stmtEaten) { att->execute(status, crdbTrans, statement.length(), statement.c_str(), dialect, NULL, NULL, NULL, NULL); diff --git a/src/yvalve/why.cpp b/src/yvalve/why.cpp index ea72ab0431a..035f867327a 100644 --- a/src/yvalve/why.cpp +++ b/src/yvalve/why.cpp @@ -99,9 +99,6 @@ static ISC_STATUS openOrCreateBlob(ISC_STATUS* userStatus, isc_db_handle* dbHand //------------------------------------- -static constexpr USHORT PREPARE_BUFFER_SIZE = 32768; // size of buffer used in isc_dsql_prepare call -static constexpr USHORT DESCRIBE_BUFFER_SIZE = 1024; // size of buffer used in isc_dsql_describe_xxx call - namespace Why { class StatusVector; extern UtilInterface utilInterface; From 976015790a71fed560424626d5916ab5d1d63de3 Mon Sep 17 00:00:00 2001 From: Dmitry Yemanov Date: Sun, 4 Jan 2026 11:01:56 +0300 Subject: [PATCH 60/71] Fix #8822: Some procedures containing LIST aggregate function are not restored in Firebird 6.0 (#8839) * Fix #8822: Some procedures containing LIST aggregate function are not restored in Firebird 6.0 * Add comment as requested by Adriano --- src/dsql/AggNodes.cpp | 10 +++++++--- src/dsql/parse.y | 2 +- src/include/firebird/impl/blr.h | 6 +++++- src/jrd/blp.h | 7 ++++--- src/yvalve/gds.cpp | 19 ++++++++++++++++++- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 87fd272e1e1..77d4bce5abb 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -935,8 +935,12 @@ DmlNode* ListAggNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* 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); + if (csb->csb_blr_reader.peekByte() == blr_within_group_order) + { + csb->csb_blr_reader.getByte(); // skip blr_within_group_order + if (const auto count = csb->csb_blr_reader.getByte()) + node->sort = PAR_sort_internal(tdbb, csb, true, count); + } return node; } @@ -966,7 +970,7 @@ void ListAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) { AggNode::genBlr(dsqlScratch); if (dsqlOrderClause) - GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); + GEN_sort(dsqlScratch, blr_within_group_order, dsqlOrderClause); } bool ListAggNode::setParameterType(DsqlCompilerScratch* dsqlScratch, diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 5f6d7752bfa..98625c8b02c 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -8684,7 +8684,7 @@ listagg_count_indication %type within_group_specification_opt within_group_specification_opt - : /* nothing */ { $$ = newNode(0); } + : /* nothing */ { $$ = nullptr; } | within_group_specification { $$ = $1; } ; diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index bb7825e3d4a..4fc7669a807 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -507,10 +507,12 @@ // Table value function #define blr_table_value_fun (unsigned char) 229 +// subcodes of blr_table_value_fun #define blr_table_value_fun_unlist (unsigned char) 1 #define blr_table_value_fun_gen_series (unsigned char) 2 #define blr_for_range (unsigned char) 230 +// subcodes of blr_for_range #define blr_for_range_variable (unsigned char) 1 #define blr_for_range_initial_value (unsigned char) 2 #define blr_for_range_final_value (unsigned char) 3 @@ -523,9 +525,11 @@ #define blr_gen_id3 (unsigned char) 231 #define blr_default2 (unsigned char) 232 #define blr_current_schema (unsigned char) 233 -#define blr_flags (unsigned char) 234 +#define blr_flags (unsigned char) 234 // subcodes of blr_flags #define blr_flags_search_system_schema (unsigned char) 1 +#define blr_within_group_order (unsigned char) 235 + #endif // FIREBIRD_IMPL_BLR_H diff --git a/src/jrd/blp.h b/src/jrd/blp.h index ef29ce55567..797bfa94b38 100644 --- a/src/jrd/blp.h +++ b/src/jrd/blp.h @@ -196,8 +196,8 @@ static inline constexpr struct {"cursor_stmt", cursor_stmt}, {"current_timestamp2", byte_line}, {"current_time2", byte_line}, - {"agg_list", two}, // 170 - {"agg_list_distinct", two}, + {"agg_list", list_function}, // 170 + {"agg_list_distinct", list_function}, {"modify2", modify2}, {"erase2", erase2}, // New BLR in FB1 @@ -229,7 +229,7 @@ static inline constexpr struct {"partition_by", partition_by}, {"continue_loop", byte_line}, {"procedure4", procedure4}, - {"agg_function", function}, + {"agg_function", agg_function}, {"substring_similar", three}, // 200 {"bool_as_value", one}, {"coalesce", byte_args}, @@ -267,5 +267,6 @@ static inline constexpr struct {"default2", default2}, {"current_schema", zero}, {NULL, NULL}, // flags - part of header + {NULL, NULL}, // blr_within_group_order - part of blr_agg_list[_distinct] and blr_agg_function {0, 0} }; diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index ce575729601..e114def3ad7 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -327,6 +327,7 @@ constexpr int op_invoke_function = 33; constexpr int op_invsel_procedure = 34; constexpr int op_table_value_fun = 35; constexpr int op_for_range = 36; +constexpr int op_within_group_order = 37; static constexpr UCHAR // generic print formats @@ -433,7 +434,9 @@ static constexpr UCHAR default2[] = { op_line, op_indent, op_byte, op_literal, op_line, op_indent, op_byte, op_literal, op_line, op_indent, op_byte, op_literal, - op_pad, op_line, 0}; + op_pad, op_line, 0 }, + list_function[] = { op_line, op_verb, op_verb, op_within_group_order, 0 }, + agg_function[] = { op_byte, op_literal, op_byte, op_line, op_args, op_within_group_order, 0 }; #include "../jrd/blp.h" @@ -4432,6 +4435,20 @@ static void blr_print_verb(gds_ctl* control, SSHORT level) break; } + case op_within_group_order: + { + if (control->ctl_blr_reader.peekByte() == blr_within_group_order) + { + blr_indent(control, level); + blr_print_blr(control, control->ctl_blr_reader.getByte()); + n = blr_print_byte(control); + blr_print_line(control, (SSHORT) offset); + while (--n >= 0) + blr_print_verb(control, level + 1); + } + break; + } + default: fb_assert(false); break; From f8c49d4a042c34816b358d884313f0f61efeeb21 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Thu, 20 Nov 2025 19:54:15 +0300 Subject: [PATCH 61/71] Percentile functions --- .../README.percentile_disc_cont.md | 195 +++++++++++ src/common/ParserTokens.h | 2 + src/dsql/AggNodes.cpp | 331 ++++++++++++++++++ src/dsql/AggNodes.h | 46 +++ src/dsql/Nodes.h | 2 + src/dsql/parse-conflicts.txt | 1 - src/dsql/parse.y | 17 + src/include/firebird/impl/msg/jrd.h | 2 + src/include/gen/Firebird.pas | 2 + src/jrd/optimizer/Optimizer.cpp | 2 +- 10 files changed, 598 insertions(+), 2 deletions(-) create mode 100644 doc/sql.extensions/README.percentile_disc_cont.md delete mode 100644 src/dsql/parse-conflicts.txt diff --git a/doc/sql.extensions/README.percentile_disc_cont.md b/doc/sql.extensions/README.percentile_disc_cont.md new file mode 100644 index 00000000000..d07044e2ec2 --- /dev/null +++ b/doc/sql.extensions/README.percentile_disc_cont.md @@ -0,0 +1,195 @@ +# PERCENTILE_DISC and PERCENTILE_CONT functions + +The `PERCENTILE_CONT` and `PERCENTILE_DISC` functions are known as inverse distribution functions. +These functions operate on an ordered set. Both functions can be used as aggregate or window functions. + +## PERCENTILE_DISC + +`PERCENTILE_DISC` is an inverse distribution function that assumes a discrete distribution model. +It takes a percentile value and a sort specification and returns an element from the set. +Nulls are ignored in the calculation. + +Syntax for the `PERCENTILE_DISC` function as an aggregate function. + +``` +PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) + + ::= numeric_literal | | +``` + +Syntax for the `PERCENTILE_DISC` function as an window function. + +``` +PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) + OVER (PARTITION BY ) + + ::= numeric_literal | | +``` + +The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. +This expression must be constant within each aggregate group. +The `ORDER BY` clause takes a single expression that can be of any type that can be sorted. + +The function `PERCENTILE_DISC` returns a value of the same type as the argument in `ORDER BY`. + +For a given percentile value `P`, `PERCENTILE_DISC` sorts the values of the expression in the `ORDER BY` clause and +returns the value with the smallest `CUME_DIST` value (with respect to the same sort specification) +that is greater than or equal to `P`. + +### Analytic Example + +```sql +SELECT + DEPT_NO, + SALARY, + CUME_DIST() OVER(PARTITION BY DEPT_NO ORDER BY SALARY) AS "CUME_DIST", + PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SALARY) + OVER(PARTITION BY DEPT_NO) AS MEDIAN_DISC +FROM EMPLOYEE +WHERE DEPT_NO < 600 +ORDER BY 1, 2; +``` + +``` +DEPT_NO SALARY CUME_DIST MEDIAN_DISC +======= ===================== ======================= ===================== +000 53793.00 0.5000000000000000 53793.00 +000 212850.00 1.000000000000000 53793.00 +100 44000.00 0.5000000000000000 44000.00 +100 111262.50 1.000000000000000 44000.00 +110 61637.81 0.5000000000000000 61637.81 +110 68805.00 1.000000000000000 61637.81 +115 6000000.00 0.5000000000000000 6000000.00 +115 7480000.00 1.000000000000000 6000000.00 +120 22935.00 0.3333333333333333 33620.63 +120 33620.63 0.6666666666666666 33620.63 +120 39224.06 1.000000000000000 33620.63 +121 110000.00 1.000000000000000 110000.00 +123 38500.00 1.000000000000000 38500.00 +125 33000.00 1.000000000000000 33000.00 +130 86292.94 0.5000000000000000 86292.94 +130 102750.00 1.000000000000000 86292.94 +140 100914.00 1.000000000000000 100914.00 +180 42742.50 0.5000000000000000 42742.50 +180 64635.00 1.000000000000000 42742.50 +``` + +## PERCENTILE_CONT + +`PERCENTILE_CONT` is an inverse distribution function that assumes a continuous distribution model. +It takes a percentile value and a sort specification and returns an element from the set. +Nulls are ignored in the calculation. + +Syntax for the `PERCENTILE_CONT` function as an aggregate function. + +``` +PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) + + ::= numeric_literal | | +``` + +Syntax for the `PERCENTILE_CONT` function as an window function. + +``` +PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) + OVER (PARTITION BY ) + + ::= numeric_literal | | +``` + +The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. +This expression must be constant within each aggregate group. +The `ORDER BY` clause takes a single expression, which must be of numeric type to perform interpolation. + +The `PERCENTILE_CONT` function returns a value of type `DOUBLE PRECISION` or `DECFLOAT(34)` depending on the type +of the argument in the `ORDER BY` clause. A value of type `DECFLOAT(34)` is returned if `ORDER BY` contains +an expression of one of the types `INT128`, `NUMERIC(38, x)` or `DECFLOAT(16 | 34)`, otherwise - `DOUBLE PRECISION`. + +The result of `PERCENTILE_CONT` is computed by linear interpolation between values after ordering them. +Using the percentile value (`P`) and the number of rows (`N`) in the aggregation group, you can compute +the row number you are interested in after ordering the rows with respect to the sort specification. +This row number (`RN`) is computed according to the formula `RN = (1 + (P * (N - 1))`. +The final result of the aggregate function is computed by linear interpolation between the values from rows +at row numbers `CRN = CEILING(RN)` and `FRN = FLOOR(RN)`. + +``` +function f(N) ::= value of expression from row at N + +if (CRN = FRN = RN) then + return f(RN) +else + return (CRN - RN) * f(FRN) + (RN - FRN) * f(CRN) +``` + +### Analytic Example + +```sql +SELECT + DEPT_NO, + SALARY, + PERCENT_RANK() OVER(PARTITION BY DEPT_NO ORDER BY SALARY) AS "PERCENT_RANK", + PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY SALARY) + OVER(PARTITION BY DEPT_NO) AS MEDIAN_CONT +FROM EMPLOYEE +WHERE DEPT_NO < 600 +ORDER BY 1, 2; +``` + +``` +DEPT_NO SALARY PERCENT_RANK MEDIAN_CONT +======= ===================== ======================= ======================= +000 53793.00 0.000000000000000 133321.5000000000 +000 212850.00 1.000000000000000 133321.5000000000 +100 44000.00 0.000000000000000 77631.25000000000 +100 111262.50 1.000000000000000 77631.25000000000 +110 61637.81 0.000000000000000 65221.40500000000 +110 68805.00 1.000000000000000 65221.40500000000 +115 6000000.00 0.000000000000000 6740000.000000000 +115 7480000.00 1.000000000000000 6740000.000000000 +120 22935.00 0.000000000000000 33620.63000000000 +120 33620.63 0.5000000000000000 33620.63000000000 +120 39224.06 0.2500000000000000 33620.63000000000 +121 110000.00 0.000000000000000 110000.0000000000 +123 38500.00 0.000000000000000 38500.00000000000 +125 33000.00 0.000000000000000 33000.00000000000 +130 86292.94 0.000000000000000 94521.47000000000 +130 102750.00 1.000000000000000 94521.47000000000 +140 100914.00 0.000000000000000 100914.0000000000 +180 42742.50 0.000000000000000 53688.75000000000 +180 64635.00 1.000000000000000 53688.75000000000 +``` + +## An example of using both aggregate functions + +```sql +SELECT + DEPT_NO, + PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY SALARY) AS MEDIAN_CONT, + PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SALARY) AS MEDIAN_DISC +FROM EMPLOYEE +GROUP BY DEPT_NO; +``` + +``` +DEPT_NO MEDIAN_CONT MEDIAN_DISC +======= ======================= ===================== +000 133321.5000000000 53793.00 +100 77631.25000000000 44000.00 +110 65221.40500000000 61637.81 +115 6740000.000000000 6000000.00 +120 33620.63000000000 33620.63 +121 110000.0000000000 110000.00 +123 38500.00000000000 38500.00 +125 33000.00000000000 33000.00 +130 94521.47000000000 86292.94 +140 100914.0000000000 100914.00 +180 53688.75000000000 42742.50 +600 66450.00000000000 27000.00 +621 71619.75000000000 62550.00 +622 53167.50000000000 53167.50 +623 60000.00000000000 60000.00 +670 71268.75000000000 31275.00 +671 81810.19000000000 81810.19 +672 45647.50000000000 35000.00 +900 92791.31500000000 69482.63 +``` diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 588252b429c..4440ccf55ff 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -373,6 +373,8 @@ PARSER_TOKEN(TOK_PARAMETER, "PARAMETER", false) PARSER_TOKEN(TOK_PARTITION, "PARTITION", true) PARSER_TOKEN(TOK_PASSWORD, "PASSWORD", true) PARSER_TOKEN(TOK_PERCENT_RANK, "PERCENT_RANK", true) +PARSER_TOKEN(TOK_PERCENTILE_CONT, "PERCENTILE_CONT", true) +PARSER_TOKEN(TOK_PERCENTILE_DISC, "PERCENTILE_DISC", true) PARSER_TOKEN(TOK_PI, "PI", true) PARSER_TOKEN(TOK_PKCS_1_5, "PKCS_1_5", true) PARSER_TOKEN(TOK_PLACING, "PLACING", true) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 77d4bce5abb..121d97a19d3 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -359,6 +359,11 @@ AggNode* AggNode::pass2(thread_db* tdbb, CompilerScratch* csb) return this; } +void AggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + arg->getDesc(tdbb, csb, desc); +} + void AggNode::aggInit(thread_db* tdbb, Request* request) const { impure_value_ex* impure = request->getImpure(impureOffset); @@ -1098,6 +1103,332 @@ AggNode* ListAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ return node; } +//-------------------- + + +static AggNode::RegisterFactory1 percentileContAggInfo( + "PERCENTILE_CONT", PercentileAggNode::TYPE_PERCENTILE_CONT); +static AggNode::RegisterFactory1 percentileDiscAggInfo( + "PERCENTILE_DISC", PercentileAggNode::TYPE_PERCENTILE_DISC); + +PercentileAggNode::PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg, + ValueListNode* aOrderClause) + : AggNode(pool, + (aType == PercentileAggNode::TYPE_PERCENTILE_CONT ? percentileContAggInfo : percentileDiscAggInfo), + false, false, aArg), + type(aType), + valueArg(nullptr), + dsqlOrderClause(aOrderClause) +{ + if (dsqlOrderClause) + valueArg = nodeAs(dsqlOrderClause->items[0])->value; +} + +void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned /*count*/) +{ + arg = PAR_parse_value(tdbb, csb); + if (csb->csb_blr_reader.peekByte() == blr_sort) + { + sort = PAR_sort(tdbb, csb, blr_sort, true); + valueArg = sort->expressions[0]; + } +} + +bool PercentileAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const +{ + if (!AggNode::dsqlMatch(dsqlScratch, other, ignoreMapCast)) + return false; + + const PercentileAggNode* o = nodeAs(other); + fb_assert(o); + return PASS1_node_match(dsqlScratch, dsqlOrderClause, o->dsqlOrderClause, ignoreMapCast); +} + +void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) +{ + fb_assert(dsqlOrderClause); + if (dsqlOrderClause->items.getCount() != 1) + { + ERR_post(Arg::Gds(isc_wronumarg)); // WITHIN GROUP should only 1 ORDER item + } + + + if (type == TYPE_PERCENTILE_DISC) + { + // same type as order by argument + DsqlDescMaker::fromNode(dsqlScratch, desc, valueArg, true); + } + else + { + DsqlDescMaker::fromNode(dsqlScratch, desc, valueArg, true); + if (desc->isDecOrInt128()) + { + desc->makeDecimal128(); + desc->setNullable(true); + } + else + { + desc->makeDouble(); + desc->setNullable(true); + } + } +} + +void PercentileAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + AggNode::genBlr(dsqlScratch); + GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); +} + +void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + valueArg->getDesc(tdbb, csb, desc); +} + +void PercentileAggNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + arg->getDesc(tdbb, csb, desc); + desc->makeDouble(); +} + +ValueExprNode* PercentileAggNode::copy(thread_db* tdbb, NodeCopier& copier) const +{ + PercentileAggNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) PercentileAggNode(*tdbb->getDefaultPool(), type); + + node->nodScale = nodScale; + node->arg = copier.copy(tdbb, arg); + node->valueArg = copier.copy(tdbb, valueArg); + node->sort = sort->copy(tdbb, copier); + + return node; +} + +AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) +{ + AggNode::pass2(tdbb, csb); + + // We need a second descriptor in the impure area for PERCENTILE. + impure2Offset = csb->allocImpure(); + + return this; +} + +string PercentileAggNode::internalPrint(NodePrinter& printer) const +{ + AggNode::internalPrint(printer); + + NODE_PRINT(printer, type); + + return "PercentileAggNode"; +} + + +void PercentileAggNode::aggInit(thread_db* tdbb, Request* request) const +{ + AggNode::aggInit(tdbb, request); + + impure_value_ex* impure = request->getImpure(impureOffset); + impure->vlu_desc.dsc_dtype = 0; + impure->vlux_count = 0; + + impure_value_ex* impure2 = request->getImpure(impure2Offset); + impure2->vlu_desc.dsc_dtype = 0; + impure2->vlux_count = 0; +} + +bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const +{ + dsc* percenteDesc = nullptr; + percenteDesc = EVL_expr(tdbb, request, arg); + if (!percenteDesc) + return false; + + dsc* desc = nullptr; + desc = EVL_expr(tdbb, request, valueArg); + if (!desc) + return false; + + if (desc->isNull()) + return false; + + impure_value_ex* impure = request->getImpure(impureOffset); + if (impure->vlux_count++ == 0) // first call to aggPass() + { + if ((type == TYPE_PERCENTILE_CONT) && !desc->isNumeric()) + ERRD_post(Arg::Gds(isc_argmustbe_numeric_function) << Arg::Str("PERCENTILE_CONT")); + + if (desc->isBlob()) + ERRD_post(Arg::Gds(isc_blobnotsup) << Arg::Str("ORDER BY")); + + const auto percentile_value = MOV_get_double(tdbb, percenteDesc); + if ((percentile_value < 0) || (percentile_value > 1)) + { + if (type == TYPE_PERCENTILE_DISC) + ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_DISC")); + else + ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_CONT")); + } + + impure->vlu_misc.vlu_double = percentile_value; + } + + 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; + } + + return true; +} + +void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) const +{ + impure_value_ex* impure = request->getImpure(impureOffset); + impure_value_ex* impure2 = request->getImpure(impure2Offset); + + if (type == TYPE_PERCENTILE_DISC) + { + // TODO: calculate only ones + const double rn = impure->vlu_misc.vlu_double * impure->vlux_count; + const auto crn = static_cast(ceil(rn)); + + impure2->vlux_count++; + + if (impure2->vlux_count == crn) + { + EVL_make_value(tdbb, desc, impure2); + } + } + else { + // TODO: calculate only ones + const double rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); + const auto crn = static_cast(ceil(rn)); + const auto frn = static_cast(floor(rn)); + + if (impure2->vlux_count++ == 0) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + Firebird::Decimal128 d128; + d128.set(0, decSt, 0); + impure2->make_decimal128(d128); + } + else + impure2->make_double(0); + } + + if (crn == frn) + { + if (impure2->vlux_count == frn) + { + if (desc->isDecOrInt128()) + { + const auto value = MOV_get_dec128(tdbb, desc); + impure2->make_decimal128(value); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure2->make_double(value); + } + } + } + else + { + if (impure2->vlux_count == frn) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + const auto value = MOV_get_dec128(tdbb, desc); + Firebird::Decimal128 d128; + d128.set(crn - rn, decSt); + const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure2->make_decimal128(part); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure2->vlu_misc.vlu_double += value * (crn - rn); + } + } + if (impure2->vlux_count == crn) + { + if (desc->isDecOrInt128()) + { + DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; + const auto value = MOV_get_dec128(tdbb, desc); + Firebird::Decimal128 d128; + d128.set(rn - frn, decSt); + const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure2->make_decimal128(part); + } + else + { + const auto value = MOV_get_double(tdbb, desc); + impure2->vlu_misc.vlu_double += value * (rn - frn); + } + } + } + } +} + +dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const +{ + impure_value_ex* impure2 = request->getImpure(impure2Offset); + + if (!impure2->vlux_count || !impure2->vlu_desc.dsc_dtype) + return nullptr; + + + return &impure2->vlu_desc; +} + +AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ +{ + AggNode* node = FB_NEW_POOL(dsqlScratch->getPool()) PercentileAggNode(dsqlScratch->getPool(), type, + doDsqlPass(dsqlScratch, arg), + doDsqlPass(dsqlScratch, dsqlOrderClause) ); + + return node; +} + //-------------------- diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index b9a186b698e..e036f54ef26 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -134,6 +134,52 @@ class ListAggNode final : public AggNode NestConst dsqlOrderClause; }; +class PercentileAggNode final : public AggNode +{ +public: + enum PercentileType : UCHAR + { + TYPE_PERCENTILE_CONT, + TYPE_PERCENTILE_DISC + }; + + explicit PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg = nullptr, + ValueListNode* aOrderClause = nullptr); + + void parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned count) override; + + unsigned getCapabilities() const override + { + return CAP_WANTS_AGG_CALLS; + } + + 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; + + void makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; + + void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; + ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier) const override; + AggNode* pass2(thread_db* tdbb, CompilerScratch* csb) override; + + void aggInit(thread_db* tdbb, Request* request) const override; + bool aggPass(thread_db* tdbb, Request* request) const override; + void aggPass(thread_db* tdbb, Request* request, dsc* desc) const override; + dsc* aggExecute(thread_db* tdbb, Request* request) const override; + +protected: + AggNode* dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ override; + +private: + const PercentileType type; + NestConst valueArg; + NestConst dsqlOrderClause; + ULONG impure2Offset = 0; +}; + class CountAggNode final : public AggNode { public: diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index f769b4b33ed..8c0baa6ebda 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -1092,6 +1092,8 @@ class AggNode : public TypedNode return NULL; } + virtual void makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc); + virtual void aggInit(thread_db* tdbb, Request* request) const = 0; // pure, but defined virtual void aggFinish(thread_db* tdbb, Request* request) const; virtual bool aggPass(thread_db* tdbb, Request* request) const; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt deleted file mode 100644 index 08c7529f156..00000000000 --- a/src/dsql/parse-conflicts.txt +++ /dev/null @@ -1 +0,0 @@ -134 shift/reduce conflicts, 7 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 98625c8b02c..b229fcd14dd 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -717,6 +717,8 @@ using namespace Firebird; %token LISTAGG %token LTRIM %token NAMED_ARG_ASSIGN +%token PERCENTILE_CONT +%token PERCENTILE_DISC %token RTRIM %token SCHEMA %token SEARCH_PATH @@ -4727,6 +4729,8 @@ keyword_or_column | WITHIN | LISTAGG | TRUNCATE + | PERCENTILE_CONT + | PERCENTILE_DISC ; col_opt @@ -8619,6 +8623,10 @@ aggregate_function_prefix { $$ = newNode(BinAggNode::TYPE_BIN_XOR, $4); } | BIN_XOR_AGG '(' DISTINCT value ')' { $$ = newNode(BinAggNode::TYPE_BIN_XOR_DISTINCT, $4); } + | PERCENTILE_CONT '(' percentile_arg ')' within_group_specification + { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_CONT, $3, $5); } + | PERCENTILE_DISC '(' percentile_arg ')' within_group_specification + { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_DISC, $3, $5); } ; %type listagg_set_function @@ -8693,6 +8701,13 @@ within_group_specification : WITHIN GROUP '(' order_clause ')' { $$ = $4; } ; +%type percentile_arg +percentile_arg + : u_numeric_constant + | variable + | parameter + ; + %type window_function window_function : DENSE_RANK '(' ')' @@ -10065,6 +10080,8 @@ non_reserved_word | FORMAT | GENERATE_SERIES | OWNER + | PERCENTILE_CONT + | PERCENTILE_DISC | SEARCH_PATH | SCHEMA | UNLIST diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 7ba26ca0ac2..2ee23a99c49 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1000,3 +1000,5 @@ FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid 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, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument STEP must be different than zero for function @1") FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types") +FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument for @1 must be in the range [0, 1]") +FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index eb0cfeae5a0..7a175acdd29 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5955,6 +5955,8 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_no_user_att_while_restore = 335545318; isc_genseq_stepmustbe_nonzero = 335545319; isc_argmustbe_exact_function = 335545320; + isc_sysf_argmustbe_range_inc0_1 = 335545321; + isc_argmustbe_numeric_function = 335545322; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index 92a13b51f74..7007aa09d5d 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -1525,7 +1525,7 @@ void Optimizer::generateAggregateSort(AggNode* aggNode) fb_assert(prevKey); ULONG length = prevKey ? ROUNDUP(prevKey->getSkdOffset() + prevKey->getSkdLength(), sizeof(SLONG)) : 0; - aggNode->arg->getDesc(tdbb, csb, desc); + aggNode->makeSortDesc(tdbb, csb, desc); if (desc->dsc_dtype >= dtype_aligned) length = FB_ALIGN(length, type_alignments[desc->dsc_dtype]); From 5ea818f64fb6cfd726cd9c2ad8cab376615fbdc9 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Fri, 21 Nov 2025 19:40:44 +0300 Subject: [PATCH 62/71] Optimization. Record numbers are calculated only once. --- src/dsql/AggNodes.cpp | 47 ++++++++++++++++++++++++++----------------- src/dsql/AggNodes.h | 8 ++++++++ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 121d97a19d3..0ea6ca372e7 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1209,6 +1209,8 @@ AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) // We need a second descriptor in the impure area for PERCENTILE. impure2Offset = csb->allocImpure(); + // impure area for calculate border + percentileImpureOffset = csb->allocImpure(); return this; } @@ -1234,6 +1236,11 @@ void PercentileAggNode::aggInit(thread_db* tdbb, Request* request) const impure_value_ex* impure2 = request->getImpure(impure2Offset); impure2->vlu_desc.dsc_dtype = 0; impure2->vlux_count = 0; + + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + percentileImpure->rn = 0; + percentileImpure->crn = 0; + percentileImpure->frn = 0; } bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const @@ -1320,28 +1327,30 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co { impure_value_ex* impure = request->getImpure(impureOffset); impure_value_ex* impure2 = request->getImpure(impure2Offset); + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); if (type == TYPE_PERCENTILE_DISC) { - // TODO: calculate only ones - const double rn = impure->vlu_misc.vlu_double * impure->vlux_count; - const auto crn = static_cast(ceil(rn)); - - impure2->vlux_count++; + if (impure2->vlux_count++ == 0) + { + // calculate only ones + percentileImpure->rn = impure->vlu_misc.vlu_double * impure->vlux_count; + percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); + } - if (impure2->vlux_count == crn) + if (impure2->vlux_count == percentileImpure->crn) { EVL_make_value(tdbb, desc, impure2); } } else { - // TODO: calculate only ones - const double rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); - const auto crn = static_cast(ceil(rn)); - const auto frn = static_cast(floor(rn)); - if (impure2->vlux_count++ == 0) { + // calculate only ones + percentileImpure->rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); + percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); + percentileImpure->frn = static_cast(floor(percentileImpure->rn)); + if (desc->isDecOrInt128()) { DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; @@ -1353,9 +1362,9 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co impure2->make_double(0); } - if (crn == frn) + if (percentileImpure->crn == percentileImpure->frn) { - if (impure2->vlux_count == frn) + if (impure2->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { @@ -1371,38 +1380,38 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co } else { - if (impure2->vlux_count == frn) + if (impure2->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; - d128.set(crn - rn, decSt); + d128.set(percentileImpure->crn - percentileImpure->rn, decSt); const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); impure2->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (crn - rn); + impure2->vlu_misc.vlu_double += value * (percentileImpure->crn - percentileImpure->rn); } } - if (impure2->vlux_count == crn) + if (impure2->vlux_count == percentileImpure->crn) { if (desc->isDecOrInt128()) { DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; - d128.set(rn - frn, decSt); + d128.set(percentileImpure->rn - percentileImpure->frn, decSt); const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); impure2->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (rn - frn); + impure2->vlu_misc.vlu_double += value * (percentileImpure->rn - percentileImpure->frn); } } } diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index e036f54ef26..ed6da539e63 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -143,6 +143,13 @@ class PercentileAggNode final : public AggNode TYPE_PERCENTILE_DISC }; + struct PercentileImpure + { + double rn; + SINT64 crn; + SINT64 frn; + }; + explicit PercentileAggNode(MemoryPool& pool, PercentileType aType, ValueExprNode* aArg = nullptr, ValueListNode* aOrderClause = nullptr); @@ -178,6 +185,7 @@ class PercentileAggNode final : public AggNode NestConst valueArg; NestConst dsqlOrderClause; ULONG impure2Offset = 0; + ULONG percentileImpureOffset = 0; }; class CountAggNode final : public AggNode From 4b9e7d7bb920858d13bd1e63f63c45bbef78b90b Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Mon, 24 Nov 2025 20:28:38 +0300 Subject: [PATCH 63/71] Fixed PERCENTILE_DISC with non-numeric argument --- src/dsql/AggNodes.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 0ea6ca372e7..3c8ce0f622e 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1187,8 +1187,25 @@ void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* void PercentileAggNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) { - arg->getDesc(tdbb, csb, desc); - desc->makeDouble(); + if (type == TYPE_PERCENTILE_DISC) + { + // same type as order by argument + valueArg->getDesc(tdbb, csb, desc); + } + else + { + valueArg->getDesc(tdbb, csb, desc); + if (desc->isDecOrInt128()) + { + desc->makeDecimal128(); + desc->setNullable(true); + } + else + { + desc->makeDouble(); + desc->setNullable(true); + } + } } ValueExprNode* PercentileAggNode::copy(thread_db* tdbb, NodeCopier& copier) const @@ -1335,7 +1352,7 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co { // calculate only ones percentileImpure->rn = impure->vlu_misc.vlu_double * impure->vlux_count; - percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); + percentileImpure->crn = MAX(static_cast(ceil(percentileImpure->rn)), 1); } if (impure2->vlux_count == percentileImpure->crn) @@ -1425,7 +1442,6 @@ dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const if (!impure2->vlux_count || !impure2->vlu_desc.dsc_dtype) return nullptr; - return &impure2->vlu_desc; } From 80e3189364ef5a96f5cc076f147ee345e8110eb5 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Tue, 25 Nov 2025 20:26:31 +0300 Subject: [PATCH 64/71] Getting the parameter type when preparing a query for percentile functions. --- src/dsql/AggNodes.cpp | 4 ++++ src/dsql/parse.y | 11 ++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 3c8ce0f622e..d117c6bf7eb 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1451,6 +1451,10 @@ AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, dsqlOrderClause) ); + PASS1_set_parameter_type(dsqlScratch, node->arg, + [&](dsc* desc) { desc->makeDouble(); }, + false); + return node; } diff --git a/src/dsql/parse.y b/src/dsql/parse.y index b229fcd14dd..3f432b65ebd 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -8623,9 +8623,9 @@ aggregate_function_prefix { $$ = newNode(BinAggNode::TYPE_BIN_XOR, $4); } | BIN_XOR_AGG '(' DISTINCT value ')' { $$ = newNode(BinAggNode::TYPE_BIN_XOR_DISTINCT, $4); } - | PERCENTILE_CONT '(' percentile_arg ')' within_group_specification + | PERCENTILE_CONT '(' value ')' within_group_specification { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_CONT, $3, $5); } - | PERCENTILE_DISC '(' percentile_arg ')' within_group_specification + | PERCENTILE_DISC '(' value ')' within_group_specification { $$ = newNode(PercentileAggNode::TYPE_PERCENTILE_DISC, $3, $5); } ; @@ -8701,13 +8701,6 @@ within_group_specification : WITHIN GROUP '(' order_clause ')' { $$ = $4; } ; -%type percentile_arg -percentile_arg - : u_numeric_constant - | variable - | parameter - ; - %type window_function window_function : DENSE_RANK '(' ')' From 25cbbbfdd01889e41eca27db8dcea8977146dfaf Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Tue, 25 Nov 2025 20:33:41 +0300 Subject: [PATCH 65/71] update doc --- doc/sql.extensions/README.percentile_disc_cont.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/sql.extensions/README.percentile_disc_cont.md b/doc/sql.extensions/README.percentile_disc_cont.md index d07044e2ec2..47997023665 100644 --- a/doc/sql.extensions/README.percentile_disc_cont.md +++ b/doc/sql.extensions/README.percentile_disc_cont.md @@ -13,8 +13,6 @@ Syntax for the `PERCENTILE_DISC` function as an aggregate function. ``` PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) - - ::= numeric_literal | | ``` Syntax for the `PERCENTILE_DISC` function as an window function. @@ -22,8 +20,6 @@ Syntax for the `PERCENTILE_DISC` function as an window function. ``` PERCENTILE_DISC() WITHIN GROUP (ORDER BY [ASC | DESC]) OVER (PARTITION BY ) - - ::= numeric_literal | | ``` The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. @@ -84,8 +80,6 @@ Syntax for the `PERCENTILE_CONT` function as an aggregate function. ``` PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) - - ::= numeric_literal | | ``` Syntax for the `PERCENTILE_CONT` function as an window function. @@ -93,8 +87,6 @@ Syntax for the `PERCENTILE_CONT` function as an window function. ``` PERCENTILE_CONT() WITHIN GROUP (ORDER BY [ASC | DESC]) OVER (PARTITION BY ) - - ::= numeric_literal | | ``` The first argument `` must evaluate to a numeric value between 0 and 1, because it is a percentile value. From 3e54bde6a08c1ad5532a9b31277765382c5c0f29 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Wed, 26 Nov 2025 20:42:08 +0300 Subject: [PATCH 66/71] Changes according to asfernandes --- src/dsql/AggNodes.cpp | 83 +++++++++++++---------------- src/dsql/AggNodes.h | 9 +++- src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index d117c6bf7eb..8009909b5fc 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1127,11 +1127,9 @@ PercentileAggNode::PercentileAggNode(MemoryPool& pool, PercentileType aType, Val void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned /*count*/) { arg = PAR_parse_value(tdbb, csb); + valueArg = PAR_parse_value(tdbb, csb); if (csb->csb_blr_reader.peekByte() == blr_sort) - { sort = PAR_sort(tdbb, csb, blr_sort, true); - valueArg = sort->expressions[0]; - } } bool PercentileAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const @@ -1149,10 +1147,9 @@ void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) fb_assert(dsqlOrderClause); if (dsqlOrderClause->items.getCount() != 1) { - ERR_post(Arg::Gds(isc_wronumarg)); // WITHIN GROUP should only 1 ORDER item + ERR_post(Arg::Gds(isc_percetile_only_one_sort_item)); } - if (type == TYPE_PERCENTILE_DISC) { // same type as order by argument @@ -1224,8 +1221,6 @@ AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) { AggNode::pass2(tdbb, csb); - // We need a second descriptor in the impure area for PERCENTILE. - impure2Offset = csb->allocImpure(); // impure area for calculate border percentileImpureOffset = csb->allocImpure(); @@ -1250,11 +1245,8 @@ void PercentileAggNode::aggInit(thread_db* tdbb, Request* request) const impure->vlu_desc.dsc_dtype = 0; impure->vlux_count = 0; - impure_value_ex* impure2 = request->getImpure(impure2Offset); - impure2->vlu_desc.dsc_dtype = 0; - impure2->vlux_count = 0; - PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + percentileImpure->vlux_count = 0; percentileImpure->rn = 0; percentileImpure->crn = 0; percentileImpure->frn = 0; @@ -1272,11 +1264,8 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const if (!desc) return false; - if (desc->isNull()) - return false; - - impure_value_ex* impure = request->getImpure(impureOffset); - if (impure->vlux_count++ == 0) // first call to aggPass() + PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); + if (percentileImpure->vlux_count++ == 0) // first call to aggPass() { if ((type == TYPE_PERCENTILE_CONT) && !desc->isNumeric()) ERRD_post(Arg::Gds(isc_argmustbe_numeric_function) << Arg::Str("PERCENTILE_CONT")); @@ -1284,8 +1273,8 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const if (desc->isBlob()) ERRD_post(Arg::Gds(isc_blobnotsup) << Arg::Str("ORDER BY")); - const auto percentile_value = MOV_get_double(tdbb, percenteDesc); - if ((percentile_value < 0) || (percentile_value > 1)) + const auto percentileValue = MOV_get_double(tdbb, percenteDesc); + if ((percentileValue < 0) || (percentileValue > 1)) { if (type == TYPE_PERCENTILE_DISC) ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_DISC")); @@ -1293,7 +1282,7 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const ERRD_post(Arg::Gds(isc_sysf_argmustbe_range_inc0_1) << Arg::Str("PERCENTILE_CONT")); } - impure->vlu_misc.vlu_double = percentile_value; + percentileImpure->percentile = percentileValue; } if (sort) @@ -1312,7 +1301,7 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const for (auto& nodeOrder : sort->expressions) { dsc toDesc = *(descOrder++); - toDesc.dsc_address = data + (IPTR)toDesc.dsc_address; + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; if (const auto fromDsc = EVL_expr(tdbb, request, nodeOrder)) { if (IS_INTL_DATA(fromDsc)) @@ -1331,7 +1320,7 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const } dsc toDesc = asb->desc; - toDesc.dsc_address = data + (IPTR)toDesc.dsc_address; + toDesc.dsc_address = data + (IPTR) toDesc.dsc_address; MOV_move(tdbb, desc, &toDesc); return true; @@ -1343,28 +1332,27 @@ bool PercentileAggNode::aggPass(thread_db* tdbb, Request* request) const void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) const { impure_value_ex* impure = request->getImpure(impureOffset); - impure_value_ex* impure2 = request->getImpure(impure2Offset); PercentileImpure* percentileImpure = request->getImpure(percentileImpureOffset); if (type == TYPE_PERCENTILE_DISC) { - if (impure2->vlux_count++ == 0) + if (impure->vlux_count++ == 0) { // calculate only ones - percentileImpure->rn = impure->vlu_misc.vlu_double * impure->vlux_count; + percentileImpure->rn = percentileImpure->percentile * percentileImpure->vlux_count; percentileImpure->crn = MAX(static_cast(ceil(percentileImpure->rn)), 1); } - if (impure2->vlux_count == percentileImpure->crn) - { - EVL_make_value(tdbb, desc, impure2); - } + if (impure->vlux_count == percentileImpure->crn) + EVL_make_value(tdbb, desc, impure); + } - else { - if (impure2->vlux_count++ == 0) + else + { + if (impure->vlux_count++ == 0) { // calculate only ones - percentileImpure->rn = 1 + impure->vlu_misc.vlu_double * (impure->vlux_count - 1); + percentileImpure->rn = 1 + percentileImpure->percentile * (percentileImpure->vlux_count - 1); percentileImpure->crn = static_cast(ceil(percentileImpure->rn)); percentileImpure->frn = static_cast(floor(percentileImpure->rn)); @@ -1373,31 +1361,31 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co DecimalStatus decSt = tdbb->getAttachment()->att_dec_status; Firebird::Decimal128 d128; d128.set(0, decSt, 0); - impure2->make_decimal128(d128); + impure->make_decimal128(d128); } else - impure2->make_double(0); + impure->make_double(0); } if (percentileImpure->crn == percentileImpure->frn) { - if (impure2->vlux_count == percentileImpure->frn) + if (impure->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { const auto value = MOV_get_dec128(tdbb, desc); - impure2->make_decimal128(value); + impure->make_decimal128(value); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->make_double(value); + impure->make_double(value); } } } else { - if (impure2->vlux_count == percentileImpure->frn) + if (impure->vlux_count == percentileImpure->frn) { if (desc->isDecOrInt128()) { @@ -1405,16 +1393,17 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; d128.set(percentileImpure->crn - percentileImpure->rn, decSt); - const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); - impure2->make_decimal128(part); + const auto part = impure->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (percentileImpure->crn - percentileImpure->rn); + impure->vlu_misc.vlu_double += value * (percentileImpure->crn - percentileImpure->rn); } } - if (impure2->vlux_count == percentileImpure->crn) + + if (impure->vlux_count == percentileImpure->crn) { if (desc->isDecOrInt128()) { @@ -1422,13 +1411,13 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co const auto value = MOV_get_dec128(tdbb, desc); Firebird::Decimal128 d128; d128.set(percentileImpure->rn - percentileImpure->frn, decSt); - const auto part = impure2->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); - impure2->make_decimal128(part); + const auto part = impure->vlu_misc.vlu_dec128.add(decSt, value.mul(decSt, d128)); + impure->make_decimal128(part); } else { const auto value = MOV_get_double(tdbb, desc); - impure2->vlu_misc.vlu_double += value * (percentileImpure->rn - percentileImpure->frn); + impure->vlu_misc.vlu_double += value * (percentileImpure->rn - percentileImpure->frn); } } } @@ -1437,12 +1426,12 @@ void PercentileAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) co dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const { - impure_value_ex* impure2 = request->getImpure(impure2Offset); + impure_value_ex* impure = request->getImpure(impureOffset); - if (!impure2->vlux_count || !impure2->vlu_desc.dsc_dtype) + if (!impure->vlux_count || !impure->vlu_desc.dsc_dtype) return nullptr; - return &impure2->vlu_desc; + return &impure->vlu_desc; } AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index ed6da539e63..929c08e04dc 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -145,6 +145,8 @@ class PercentileAggNode final : public AggNode struct PercentileImpure { + SINT64 vlux_count; + double percentile; double rn; SINT64 crn; SINT64 frn; @@ -162,6 +164,12 @@ class PercentileAggNode final : public AggNode bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const override; + void getChildren(NodeRefsHolder& holder, bool dsql) const override + { + AggNode::getChildren(holder, dsql); + holder.add(valueArg); + } + Firebird::string internalPrint(NodePrinter& printer) const override; void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; void genBlr(DsqlCompilerScratch* dsqlScratch) override; @@ -184,7 +192,6 @@ class PercentileAggNode final : public AggNode const PercentileType type; NestConst valueArg; NestConst dsqlOrderClause; - ULONG impure2Offset = 0; ULONG percentileImpureOffset = 0; }; diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 2ee23a99c49..be4dd4cde4e 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1002,3 +1002,4 @@ FB_IMPL_MSG(JRD, 999, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument ST FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types") FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument for @1 must be in the range [0, 1]") FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types") +FB_IMPL_MSG(JRD, 1003, percetile_only_one_sort_item, -833, "42", "000", "The PERCENTILE_DISC and PERENTILE_CONT functions support only one sort item in WITHIN GROUP") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 7a175acdd29..b3ba6217529 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5957,6 +5957,7 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_argmustbe_exact_function = 335545320; isc_sysf_argmustbe_range_inc0_1 = 335545321; isc_argmustbe_numeric_function = 335545322; + isc_percetile_only_one_sort_item = 335545323; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; From 834feaf97360aac854860c38ce0ccfb8045c562b Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Wed, 17 Dec 2025 18:17:58 +0300 Subject: [PATCH 67/71] Added check for percentile constancy within a group --- src/dsql/AggNodes.cpp | 45 ++++++++++++++++++++++++++++- src/dsql/AggNodes.h | 2 ++ src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 8009909b5fc..401c6a74b13 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1227,6 +1227,49 @@ AggNode* PercentileAggNode::pass2(thread_db* tdbb, CompilerScratch* csb) return this; } +bool PercentileAggNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor) +{ + bool invalid = false; + + if (!visitor.insideOwnMap) + { + // We are not in an aggregate from the same scope_level so + // check for valid fields inside this aggregate + invalid |= ExprNode::dsqlInvalidReferenceFinder(visitor); + } + + if (!visitor.insideHigherMap) + { + NodeRefsHolder holder(visitor.dsqlScratch->getPool()); + getChildren(holder, true); + + for (auto i : holder.refs) + { + // If there's another aggregate with the same scope_level or + // an higher one then it's a invalid aggregate, because + // aggregate-functions from the same context can't + // be part of each other. + if (Aggregate2Finder::find(visitor.dsqlScratch->getPool(), visitor.context->ctx_scope_level, + FIELD_MATCH_TYPE_EQUAL, false, *i)) + { + // Nested aggregate functions are not allowed + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << + Arg::Gds(isc_dsql_agg_nested_err)); + } + } + + if (visitor.visit(**holder.refs.begin())) + { + // The percent argument must be constant within group + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << + Arg::Gds(isc_argmustbe_const_within_group) << + ((type == TYPE_PERCENTILE_CONT) ? Arg::Str("PERCENTILE_CONT") : Arg::Str("PERCENTILE_DISC"))); + } + } + + return invalid; +} + string PercentileAggNode::internalPrint(NodePrinter& printer) const { AggNode::internalPrint(printer); @@ -1437,7 +1480,7 @@ dsc* PercentileAggNode::aggExecute(thread_db* tdbb, Request* request) const AggNode* PercentileAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ { AggNode* node = FB_NEW_POOL(dsqlScratch->getPool()) PercentileAggNode(dsqlScratch->getPool(), type, - doDsqlPass(dsqlScratch, arg), + doDsqlPass(dsqlScratch, arg), doDsqlPass(dsqlScratch, dsqlOrderClause) ); PASS1_set_parameter_type(dsqlScratch, node->arg, diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index 929c08e04dc..defcd89e3e4 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -170,6 +170,8 @@ class PercentileAggNode final : public AggNode holder.add(valueArg); } + bool dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor) override; + Firebird::string internalPrint(NodePrinter& printer) const override; void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; void genBlr(DsqlCompilerScratch* dsqlScratch) override; diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index be4dd4cde4e..d146ea1562e 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -1003,3 +1003,4 @@ FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments f FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument for @1 must be in the range [0, 1]") FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types") FB_IMPL_MSG(JRD, 1003, percetile_only_one_sort_item, -833, "42", "000", "The PERCENTILE_DISC and PERENTILE_CONT functions support only one sort item in WITHIN GROUP") +FB_IMPL_MSG(JRD, 1004, argmustbe_const_within_group, -833, "42", "000", "Argument for @1 function must be constant within each group") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index b3ba6217529..36acac61a1e 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5958,6 +5958,7 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_sysf_argmustbe_range_inc0_1 = 335545321; isc_argmustbe_numeric_function = 335545322; isc_percetile_only_one_sort_item = 335545323; + isc_argmustbe_const_within_group = 335545324; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; From fe789bd18b46b2228626a7ee162e412ef6951b46 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Sun, 4 Jan 2026 13:08:22 +0300 Subject: [PATCH 68/71] Use blr_within_group_order instead blr_sort --- src/dsql/AggNodes.cpp | 10 +++++++--- src/dsql/parse-conflicts.txt | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 src/dsql/parse-conflicts.txt diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 401c6a74b13..4efac401bb4 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1128,8 +1128,12 @@ void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigne { arg = PAR_parse_value(tdbb, csb); valueArg = PAR_parse_value(tdbb, csb); - if (csb->csb_blr_reader.peekByte() == blr_sort) - sort = PAR_sort(tdbb, csb, blr_sort, true); + if (csb->csb_blr_reader.peekByte() == blr_within_group_order) + { + csb->csb_blr_reader.getByte(); // skip blr_within_group_order + if (const auto count = csb->csb_blr_reader.getByte()) + sort = PAR_sort_internal(tdbb, csb, true, count); + } } bool PercentileAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const @@ -1174,7 +1178,7 @@ void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) void PercentileAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) { AggNode::genBlr(dsqlScratch); - GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); + GEN_sort(dsqlScratch, blr_within_group_order, dsqlOrderClause); } void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt new file mode 100644 index 00000000000..93a526691e0 --- /dev/null +++ b/src/dsql/parse-conflicts.txt @@ -0,0 +1 @@ +134 shift/reduce conflicts, 19 reduce/reduce conflicts. From 98c3a5005ffd2fcdd3ac78f59cbe62514f19ecdc Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Sun, 4 Jan 2026 13:23:32 +0300 Subject: [PATCH 69/71] use blr_within_group_order instead nlr_sort --- src/dsql/AggNodes.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 605b5ae497f..b9517ac0be7 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1128,8 +1128,12 @@ void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigne { arg = PAR_parse_value(tdbb, csb); valueArg = PAR_parse_value(tdbb, csb); - if (csb->csb_blr_reader.peekByte() == blr_sort) - sort = PAR_sort(tdbb, csb, blr_sort, true); + if (csb->csb_blr_reader.peekByte() == blr_within_group_order) + { + csb->csb_blr_reader.getByte(); // skip blr_within_group_order + if (const auto count = csb->csb_blr_reader.getByte()) + node->sort = PAR_sort_internal(tdbb, csb, true, count); + } } bool PercentileAggNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const @@ -1174,7 +1178,7 @@ void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) void PercentileAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) { AggNode::genBlr(dsqlScratch); - GEN_sort(dsqlScratch, blr_sort, dsqlOrderClause); + GEN_sort(dsqlScratch, blr_within_group_order, dsqlOrderClause); } void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) From aaf3174d97148d53722ec1aff426100b5e214c48 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Sun, 4 Jan 2026 13:40:50 +0300 Subject: [PATCH 70/71] fixed build --- src/dsql/AggNodes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index b9517ac0be7..5c0a961441c 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1132,7 +1132,7 @@ void PercentileAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigne { csb->csb_blr_reader.getByte(); // skip blr_within_group_order if (const auto count = csb->csb_blr_reader.getByte()) - node->sort = PAR_sort_internal(tdbb, csb, true, count); + sort = PAR_sort_internal(tdbb, csb, true, count); } } From 0d51cdf70065672000c8af0641da93ddcbb97bc8 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Sun, 4 Jan 2026 20:05:27 +0300 Subject: [PATCH 71/71] Changes according to dyemanov --- src/dsql/AggNodes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 5c0a961441c..a10c7368ada 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1178,7 +1178,8 @@ void PercentileAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) void PercentileAggNode::genBlr(DsqlCompilerScratch* dsqlScratch) { AggNode::genBlr(dsqlScratch); - GEN_sort(dsqlScratch, blr_within_group_order, dsqlOrderClause); + if (dsqlOrderClause) + GEN_sort(dsqlScratch, blr_within_group_order, dsqlOrderClause); } void PercentileAggNode::makeSortDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)