diff --git a/doc/sql.extensions/README.aggregate_functions.md b/doc/sql.extensions/README.aggregate_functions.md index 5ed0211e346..e6ea5e663ef 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`, `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. + +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..0cf1f80488e 100644 --- a/src/dsql/AggNodes.cpp +++ b/src/dsql/AggNodes.cpp @@ -1467,6 +1467,173 @@ 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) || (desc->dsc_scale != 0)) + { + 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: + fb_assert(false); + 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); + + SINT64 initValue = 0; + if (type == TYPE_BIN_AND) + initValue = -1; + + impure_value_ex* impure = request->getImpure(impureOffset); + if (nodFlags & FLAG_INT128) + { + Firebird::Int128 i128; + impure->make_decimal_fixed(i128, 0); + impure->vlu_misc.vlu_int128 = initValue; + } + 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 value = MOV_get_int128(tdbb, desc, 0); + switch (type) + { + case TYPE_BIN_AND: + impure->vlu_misc.vlu_int128 &= value; + break; + + case TYPE_BIN_OR: + impure->vlu_misc.vlu_int128 |= value; + break; + + case TYPE_BIN_XOR: + case TYPE_BIN_XOR_DISTINCT: + impure->vlu_misc.vlu_int128 ^= value; + break; + } + } + else + { + const auto value = MOV_get_int64(tdbb, desc, 0); + switch (type) + { + case TYPE_BIN_AND: + impure->vlu_misc.vlu_int64 &= value; + break; + + case TYPE_BIN_OR: + impure->vlu_misc.vlu_int64 |= value; + break; + + case TYPE_BIN_XOR: + case TYPE_BIN_XOR_DISTINCT: + impure->vlu_misc.vlu_int64 ^= value; + 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..1fd7257bc0b 100644 --- a/src/dsql/AggNodes.h +++ b/src/dsql/AggNodes.h @@ -219,6 +219,42 @@ class MaxMinAggNode final : public AggNode const MaxMinType type; }; +class BinAggNode final : public AggNode +{ +public: + enum BinType : UCHAR + { + TYPE_BIN_AND, + TYPE_BIN_OR, + TYPE_BIN_XOR, + TYPE_BIN_XOR_DISTINCT + }; + + explicit BinAggNode(MemoryPool& pool, BinType aType, ValueExprNode* aArg = nullptr); + + void parseArgs(thread_db* tdbb, CompilerScratch* csb, unsigned count) override; + + unsigned getCapabilities() const override + { + return CAP_RESPECTS_WINDOW_FRAME | CAP_WANTS_AGG_CALLS; + } + + 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; + + 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: + AggNode* dsqlCopy(DsqlCompilerScratch* dsqlScratch) /*const*/ override; + +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