From 21dca5bd07aada9029d7adfbd873152115efa08a Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Thu, 9 Oct 2025 21:28:47 +0300 Subject: [PATCH 1/3] Add support bitwise aggregate functions --- .../README.aggregate_functions.md | 36 ++++ src/common/ParserTokens.h | 3 + src/dsql/AggNodes.cpp | 172 ++++++++++++++++++ src/dsql/AggNodes.h | 35 ++++ src/dsql/parse.y | 14 ++ 5 files changed, 260 insertions(+) diff --git a/doc/sql.extensions/README.aggregate_functions.md b/doc/sql.extensions/README.aggregate_functions.md index 5ed0211e346..b04eb0046c5 100644 --- a/doc/sql.extensions/README.aggregate_functions.md +++ b/doc/sql.extensions/README.aggregate_functions.md @@ -22,3 +22,39 @@ select department, from employee_department group by department ``` + +## Bitwise aggregates (Firebird 6.0) + +The `BIN_AND_AGG`, `BIT_XOR`, and `BIT_XOR` aggregate functions perform bit operations. + +`NULLs` are ignored. It's returned only in the case of none evaluated records having a non-null value. + +The input argument must be one of the integer types (`SMALLINT`, `INTEGER`, `BIGINT`, or `INT128`). +The output result is of the same type as the input argument. + +Syntax: + +``` + ::= BIN_AND_AGG() + + ::= BIN_OR_AGG() + + ::= BIN_XOR_AGG([ALL | DISTINCT] ) +``` + +The `BIN_AND_AGG` and `BIN_OR_AGG` functions do not support the `DISTINCT` keyword, since eliminating duplicates does +not affect the result. However, for `BIN_XOR_AGG`, you can specify `DISTINCT` to exclude duplicates from processing. + +Example: + +``` +SELECT + name, + BIN_AND_AGG(n) AS F_AND, + BIN_OR_AGG(n) AS F_OR, + BIN_XOR_AGG(n) AS F_XOR, + BIN_XOR_AGG(ALL n) AS F_XOR_A, + BIN_XOR_AGG(DISTINCT n) AS F_XOR_D +FROM acl_masks +GROUP BY name +``` diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 3abce408f92..8a44da58a38 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -88,11 +88,14 @@ PARSER_TOKEN(TOK_BEGIN, "BEGIN", false) PARSER_TOKEN(TOK_BETWEEN, "BETWEEN", false) PARSER_TOKEN(TOK_BIGINT, "BIGINT", false) PARSER_TOKEN(TOK_BIN_AND, "BIN_AND", true) +PARSER_TOKEN(TOK_BIN_AND_AGG, "BIN_AND_AGG", true) PARSER_TOKEN(TOK_BIN_NOT, "BIN_NOT", true) PARSER_TOKEN(TOK_BIN_OR, "BIN_OR", true) +PARSER_TOKEN(TOK_BIN_OR_AGG, "BIN_OR_AGG", true) PARSER_TOKEN(TOK_BIN_SHL, "BIN_SHL", true) PARSER_TOKEN(TOK_BIN_SHR, "BIN_SHR", true) PARSER_TOKEN(TOK_BIN_XOR, "BIN_XOR", true) +PARSER_TOKEN(TOK_BIN_XOR_AGG, "BIN_XOR_AGG", true) PARSER_TOKEN(TOK_BINARY, "BINARY", false) PARSER_TOKEN(TOK_BIND, "BIND", true) PARSER_TOKEN(TOK_BIT_LENGTH, "BIT_LENGTH", false) diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index b2674157c9d..60c6032015f 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1467,6 +1467,178 @@ AggNode* MaxMinAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ type, doDsqlPass(dsqlScratch, arg)); } +//-------------------- + +static AggNode::RegisterFactory1 binAndAggInfo( + "BIN_AND_AGG", BinAggNode::TYPE_BIN_AND); +static AggNode::RegisterFactory1 binOrAggInfo( + "BIN_OR_AGG", BinAggNode::TYPE_BIN_OR); +static AggNode::RegisterFactory1 binXorAggInfo( + "BIN_XOR_AGG", BinAggNode::TYPE_BIN_XOR); +static AggNode::RegisterFactory1 binXorDistinctAggInfo( + "BIN_XOR_DISTINCT_AGG", BinAggNode::TYPE_BIN_XOR_DISTINCT); + +BinAggNode::BinAggNode(MemoryPool& pool, BinType aType, ValueExprNode* aArg) + : AggNode(pool, + (aType == BinAggNode::TYPE_BIN_AND ? binAndAggInfo : + aType == BinAggNode::TYPE_BIN_OR ? binOrAggInfo : + aType == BinAggNode::TYPE_BIN_XOR ? binXorAggInfo : binXorDistinctAggInfo), + (aType == BinAggNode::TYPE_BIN_XOR_DISTINCT), false, aArg), + type(aType) +{ +} + +void BinAggNode::parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned /*count*/) +{ + arg = PAR_parse_value(tdbb, csb); +} + +void BinAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) +{ + DsqlDescMaker::fromNode(dsqlScratch, desc, arg, true); + + if (desc->isNull()) + return; + + if (!DTYPE_IS_EXACT(desc->dsc_dtype)) + { + switch(type) { + case TYPE_BIN_AND: + ERRD_post(Arg::Gds(isc_expression_eval_err) << + Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_AND_AGG")); + break; + case TYPE_BIN_OR: + ERRD_post(Arg::Gds(isc_expression_eval_err) << + Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_OR_AGG")); + break; + case TYPE_BIN_XOR: + case TYPE_BIN_XOR_DISTINCT: + ERRD_post(Arg::Gds(isc_expression_eval_err) << + Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_XOR_AGG")); + break; + default: + ERRD_post(Arg::Gds(isc_expression_eval_err) << + Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_XXX_AGG")); + break; + } + } +} + +void BinAggNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) +{ + arg->getDesc(tdbb, csb, desc); + + if (desc->is128()) { + nodFlags |= FLAG_INT128; + desc->makeInt128(0); + } + else { + desc->makeInt64(0); + } +} + +ValueExprNode* BinAggNode::copy(thread_db* tdbb, NodeCopier& copier) const +{ + BinAggNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) BinAggNode(*tdbb->getDefaultPool(), type); + node->arg = copier.copy(tdbb, arg); + return node; +} + +string BinAggNode::internalPrint(NodePrinter& printer) const +{ + AggNode::internalPrint(printer); + + NODE_PRINT(printer, type); + + return "BinAggNode"; +} + +void BinAggNode::aggInit(thread_db* tdbb, Request* request) const +{ + AggNode::aggInit(tdbb, request); + + impure_value_ex* impure = request->getImpure(impureOffset); + if (nodFlags & FLAG_INT128) { + Firebird::Int128 i128; + impure->make_decimal_fixed(i128, 0); + switch(type) { + case TYPE_BIN_AND: + impure->vlu_misc.vlu_int128 = -1; + break; + case TYPE_BIN_OR: + case TYPE_BIN_XOR: + case TYPE_BIN_XOR_DISTINCT: + default: + impure->vlu_misc.vlu_int128 = 0; + break; + } + } + else { + switch(type) { + case TYPE_BIN_AND: + impure->make_int64(-1); + break; + case TYPE_BIN_OR: + case TYPE_BIN_XOR: + case TYPE_BIN_XOR_DISTINCT: + default: + impure->make_int64(0); + break; + } + } +} + +void BinAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) const +{ + impure_value_ex* impure = request->getImpure(impureOffset); + ++impure->vlux_count; + if (nodFlags & FLAG_INT128) { + const auto x = MOV_get_int128(tdbb, desc, 0); + switch(type) { + case TYPE_BIN_AND: + impure->vlu_misc.vlu_int128 &= x; + break; + case TYPE_BIN_OR: + impure->vlu_misc.vlu_int128 |= x; + break; + case TYPE_BIN_XOR: + case TYPE_BIN_XOR_DISTINCT: + impure->vlu_misc.vlu_int128 ^= x; + break; + } + } + else { + const SINT64 x = MOV_get_int64(tdbb, desc, 0); + switch(type) { + case TYPE_BIN_AND: + impure->vlu_misc.vlu_int64 &= x; + break; + case TYPE_BIN_OR: + impure->vlu_misc.vlu_int64 |= x; + break; + case TYPE_BIN_XOR: + case TYPE_BIN_XOR_DISTINCT: + impure->vlu_misc.vlu_int64 ^= x; + break; + } + } +} + +dsc* BinAggNode::aggExecute(thread_db* tdbb, Request* request) const +{ + impure_value_ex* impure = request->getImpure(impureOffset); + + if (!impure->vlux_count) + return nullptr; + + return &impure->vlu_desc; +} + +AggNode* BinAggNode::dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ +{ + return FB_NEW_POOL(dsqlScratch->getPool()) BinAggNode(dsqlScratch->getPool(), + type, doDsqlPass(dsqlScratch, arg)); +} //-------------------- diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index 861b1e6c377..12232caca66 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -219,6 +219,41 @@ class MaxMinAggNode final : public AggNode const MaxMinType type; }; +class BinAggNode final : public AggNode +{ +public: + enum BinType + { + TYPE_BIN_AND, + TYPE_BIN_OR, + TYPE_BIN_XOR, + TYPE_BIN_XOR_DISTINCT + }; + + explicit BinAggNode(MemoryPool& pool, BinType aType, ValueExprNode* aArg = nullptr); + + virtual void parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned count); + + virtual unsigned getCapabilities() const + { + return CAP_RESPECTS_WINDOW_FRAME | CAP_WANTS_AGG_CALLS; + } + + virtual Firebird::string internalPrint(NodePrinter& printer) const; + virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc); + virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc); + virtual ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier) const; + + virtual void aggInit(thread_db* tdbb, Request* request) const; + virtual void aggPass(thread_db* tdbb, Request* request, dsc* desc) const; + virtual dsc* aggExecute(thread_db* tdbb, Request* request) const; + +protected: + virtual AggNode* dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/; +public: + const BinType type; +}; + class StdDevAggNode final : public AggNode { public: diff --git a/src/dsql/parse.y b/src/dsql/parse.y index beb79abf85c..c8f2204af17 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -702,6 +702,9 @@ using namespace Firebird; // tokens added for Firebird 6.0 %token ANY_VALUE +%token BIN_AND_AGG +%token BIN_OR_AGG +%token BIN_XOR_AGG %token BTRIM %token CALL %token CURRENT_SCHEMA @@ -8584,6 +8587,14 @@ aggregate_function_prefix { $$ = newNode(RegrAggNode::TYPE_REGR_SYY, $3, $5); } | ANY_VALUE '(' distinct_noise value ')' { $$ = newNode($4); } + | BIN_AND_AGG '(' value ')' + { $$ = newNode(BinAggNode::TYPE_BIN_AND, $3); } + | BIN_OR_AGG '(' value ')' + { $$ = newNode(BinAggNode::TYPE_BIN_OR, $3); } + | BIN_XOR_AGG '(' all_noise value ')' + { $$ = newNode(BinAggNode::TYPE_BIN_XOR, $4); } + | BIN_XOR_AGG '(' DISTINCT value ')' + { $$ = newNode(BinAggNode::TYPE_BIN_XOR_DISTINCT, $4); } ; %type window_function @@ -9951,6 +9962,9 @@ non_reserved_word | UNICODE_VAL // added in FB 6.0 | ANY_VALUE + | BIN_AND_AGG + | BIN_OR_AGG + | BIN_XOR_AGG | DOWNTO | FORMAT | OWNER From aac337503803ffba0bd1bdea3f58c386a2c47c87 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Thu, 9 Oct 2025 21:34:18 +0300 Subject: [PATCH 2/3] Fixed doc --- doc/sql.extensions/README.aggregate_functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sql.extensions/README.aggregate_functions.md b/doc/sql.extensions/README.aggregate_functions.md index b04eb0046c5..3db2ed8158e 100644 --- a/doc/sql.extensions/README.aggregate_functions.md +++ b/doc/sql.extensions/README.aggregate_functions.md @@ -25,7 +25,7 @@ select department, ## Bitwise aggregates (Firebird 6.0) -The `BIN_AND_AGG`, `BIT_XOR`, and `BIT_XOR` aggregate functions perform bit operations. +The `BIN_AND_AGG`, `BIN_XO_AGG`, and `BIN_XOR_AGG` aggregate functions perform bit operations. `NULLs` are ignored. It's returned only in the case of none evaluated records having a non-null value. From f849ac50bb65cf6f98a39206d27c722b238002a9 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Fri, 10 Oct 2025 19:51:42 +0300 Subject: [PATCH 3/3] Corrections after review. --- .../README.aggregate_functions.md | 2 +- src/dsql/AggNodes.cpp | 81 +++++++++---------- src/dsql/AggNodes.h | 23 +++--- 3 files changed, 51 insertions(+), 55 deletions(-) diff --git a/doc/sql.extensions/README.aggregate_functions.md b/doc/sql.extensions/README.aggregate_functions.md index 3db2ed8158e..e6ea5e663ef 100644 --- a/doc/sql.extensions/README.aggregate_functions.md +++ b/doc/sql.extensions/README.aggregate_functions.md @@ -25,7 +25,7 @@ select department, ## Bitwise aggregates (Firebird 6.0) -The `BIN_AND_AGG`, `BIN_XO_AGG`, and `BIN_XOR_AGG` aggregate functions perform bit operations. +The `BIN_AND_AGG`, `BIN_OR_AGG`, and `BIN_XOR_AGG` aggregate functions perform bit operations. `NULLs` are ignored. It's returned only in the case of none evaluated records having a non-null value. diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp index 60c6032015f..43593643ca2 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1502,23 +1502,26 @@ void BinAggNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc) if (!DTYPE_IS_EXACT(desc->dsc_dtype)) { - switch(type) { + switch (type) + { case TYPE_BIN_AND: ERRD_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_AND_AGG")); break; + case TYPE_BIN_OR: ERRD_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_OR_AGG")); break; + case TYPE_BIN_XOR: case TYPE_BIN_XOR_DISTINCT: ERRD_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_XOR_AGG")); break; + default: - ERRD_post(Arg::Gds(isc_expression_eval_err) << - Arg::Gds(isc_dsql_agg2_wrongarg) << Arg::Str("BIN_XXX_AGG")); + fb_assert(false); break; } } @@ -1528,13 +1531,13 @@ void BinAggNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) { arg->getDesc(tdbb, csb, desc); - if (desc->is128()) { + if (desc->is128()) + { nodFlags |= FLAG_INT128; desc->makeInt128(0); } - else { + else desc->makeInt64(0); - } } ValueExprNode* BinAggNode::copy(thread_db* tdbb, NodeCopier& copier) const @@ -1557,68 +1560,60 @@ void BinAggNode::aggInit(thread_db* tdbb, Request* request) const { AggNode::aggInit(tdbb, request); + SINT64 initValue = 0; + if (type == TYPE_BIN_AND) + initValue = -1; + impure_value_ex* impure = request->getImpure(impureOffset); - if (nodFlags & FLAG_INT128) { + if (nodFlags & FLAG_INT128) + { Firebird::Int128 i128; impure->make_decimal_fixed(i128, 0); - switch(type) { - case TYPE_BIN_AND: - impure->vlu_misc.vlu_int128 = -1; - break; - case TYPE_BIN_OR: - case TYPE_BIN_XOR: - case TYPE_BIN_XOR_DISTINCT: - default: - impure->vlu_misc.vlu_int128 = 0; - break; - } + impure->vlu_misc.vlu_int128 = initValue; } - else { - switch(type) { - case TYPE_BIN_AND: - impure->make_int64(-1); - break; - case TYPE_BIN_OR: - case TYPE_BIN_XOR: - case TYPE_BIN_XOR_DISTINCT: - default: - impure->make_int64(0); - break; - } - } + else + impure->make_int64(initValue); } void BinAggNode::aggPass(thread_db* tdbb, Request* request, dsc* desc) const { impure_value_ex* impure = request->getImpure(impureOffset); ++impure->vlux_count; - if (nodFlags & FLAG_INT128) { - const auto x = MOV_get_int128(tdbb, desc, 0); - switch(type) { + if (nodFlags & FLAG_INT128) + { + const auto value = MOV_get_int128(tdbb, desc, 0); + switch (type) + { case TYPE_BIN_AND: - impure->vlu_misc.vlu_int128 &= x; + impure->vlu_misc.vlu_int128 &= value; break; + case TYPE_BIN_OR: - impure->vlu_misc.vlu_int128 |= x; + impure->vlu_misc.vlu_int128 |= value; break; + case TYPE_BIN_XOR: case TYPE_BIN_XOR_DISTINCT: - impure->vlu_misc.vlu_int128 ^= x; + impure->vlu_misc.vlu_int128 ^= value; break; } } - else { - const SINT64 x = MOV_get_int64(tdbb, desc, 0); - switch(type) { + else + { + const auto value = MOV_get_int64(tdbb, desc, 0); + switch (type) + { case TYPE_BIN_AND: - impure->vlu_misc.vlu_int64 &= x; + impure->vlu_misc.vlu_int64 &= value; break; + case TYPE_BIN_OR: - impure->vlu_misc.vlu_int64 |= x; + impure->vlu_misc.vlu_int64 |= value; break; + case TYPE_BIN_XOR: case TYPE_BIN_XOR_DISTINCT: - impure->vlu_misc.vlu_int64 ^= x; + impure->vlu_misc.vlu_int64 ^= value; break; } } diff --git a/src/dsql/AggNodes.h b/src/dsql/AggNodes.h index 12232caca66..1fd7257bc0b 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -222,7 +222,7 @@ class MaxMinAggNode final : public AggNode class BinAggNode final : public AggNode { public: - enum BinType + enum BinType : UCHAR { TYPE_BIN_AND, TYPE_BIN_OR, @@ -232,24 +232,25 @@ class BinAggNode final : public AggNode explicit BinAggNode(MemoryPool& pool, BinType aType, ValueExprNode* aArg = nullptr); - virtual void parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned count); + void parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned count) override; - virtual unsigned getCapabilities() const + unsigned getCapabilities() const override { return CAP_RESPECTS_WINDOW_FRAME | CAP_WANTS_AGG_CALLS; } - virtual Firebird::string internalPrint(NodePrinter& printer) const; - virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc); - virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc); - virtual ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier) const; + Firebird::string internalPrint(NodePrinter& printer) const override; + void make(DsqlCompilerScratch* dsqlScratch, dsc* desc) override; + void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc) override; + ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier) const override; - virtual void aggInit(thread_db* tdbb, Request* request) const; - virtual void aggPass(thread_db* tdbb, Request* request, dsc* desc) const; - virtual dsc* aggExecute(thread_db* tdbb, Request* request) const; + void aggInit(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: - virtual AggNode* dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/; + AggNode* dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ override; + public: const BinType type; };