8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 04:43:03 +01:00

Refactor blr_via, blr_from, blr_maximum, blr_minimum, blr_count, blr_average and blr_total

This commit is contained in:
asfernandes 2010-11-07 02:18:58 +00:00
parent dff60ff120
commit 1d702dae81
16 changed files with 607 additions and 552 deletions

View File

@ -1136,8 +1136,13 @@ void ArithmeticNode::getDescDialect1(thread_db* /*tdbb*/, dsc* desc, dsc& desc1,
desc->dsc_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
break;
return;
}
if (dtype == dtype_quad)
IBERROR(224); // msg 224 quad word arithmetic not supported
ERR_post(Arg::Gds(isc_datype_notsup)); // data type not supported for arithmetic
}
void ArithmeticNode::getDescDialect3(thread_db* /*tdbb*/, dsc* desc, dsc& desc1, dsc& desc2)
@ -1329,6 +1334,7 @@ void ArithmeticNode::getDescDialect3(thread_db* /*tdbb*/, dsc* desc, dsc& desc1,
#ifdef NATIVE_QUAD
return;
#endif
default:
fb_assert(false);
// FALLINTO
@ -1383,6 +1389,11 @@ void ArithmeticNode::getDescDialect3(thread_db* /*tdbb*/, dsc* desc, dsc& desc1,
break;
}
if (dtype == dtype_quad)
IBERROR(224); // msg 224 quad word arithmetic not supported
ERR_post(Arg::Gds(isc_datype_notsup)); // data type not supported for arithmetic
}
ValueExprNode* ArithmeticNode::copy(thread_db* tdbb, NodeCopier& copier)
@ -1535,11 +1546,13 @@ dsc* ArithmeticNode::add(const dsc* desc, impure_value* value, const jrd_nod* no
DEV_BLKCHK(node, type_nod);
const ArithmeticNode* arithmeticNode = ExprNode::as<ArithmeticNode>(node);
const SubQueryNode* subQueryNode = ExprNode::as<SubQueryNode>(node);
fb_assert(
(arithmeticNode && arithmeticNode->dialect1 &&
(arithmeticNode->blrOp == blr_add || arithmeticNode->blrOp == blr_subtract)) ||
ExprNode::is<AggNode>(node) || node->nod_type == nod_total || node->nod_type == nod_average);
ExprNode::is<AggNode>(node) ||
(subQueryNode && (subQueryNode->blrOp == blr_total || subQueryNode->blrOp == blr_average)));
dsc* const result = &value->vlu_desc;
@ -5890,6 +5903,496 @@ dsc* StrLenNode::execute(thread_db* tdbb, jrd_req* request) const
//--------------------
// Only blr_via is generated by DSQL.
static RegisterNode<SubQueryNode> regSubQueryNodeVia(blr_via);
static RegisterNode<SubQueryNode> regSubQueryNodeFrom(blr_from);
static RegisterNode<SubQueryNode> regSubQueryNodeAverage(blr_average);
static RegisterNode<SubQueryNode> regSubQueryNodeCount(blr_count);
static RegisterNode<SubQueryNode> regSubQueryNodeMaximum(blr_maximum);
static RegisterNode<SubQueryNode> regSubQueryNodeMinimum(blr_minimum);
static RegisterNode<SubQueryNode> regSubQueryNodeTotal(blr_total);
SubQueryNode::SubQueryNode(MemoryPool& pool, UCHAR aBlrOp, dsql_nod* aDsqlRse,
dsql_nod* aValue1, dsql_nod* aValue2)
: TypedNode<ValueExprNode, ExprNode::TYPE_SUBQUERY>(pool),
blrOp(aBlrOp),
dsqlRse(aDsqlRse),
dsqlValue1(aValue1),
dsqlValue2(aValue2),
value1(NULL),
value2(NULL),
rsb(NULL)
{
addChildNode(dsqlRse, rse);
addChildNode(dsqlValue1, value1);
addChildNode(dsqlValue2, value2);
}
DmlNode* SubQueryNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, UCHAR blrOp)
{
// We treat blr_from as blr_via after parse.
SubQueryNode* node = FB_NEW(pool) SubQueryNode(pool, (blrOp == blr_from ? blr_via : blrOp));
node->rse = PAR_parse_node(tdbb, csb, TYPE_RSE);
if (blrOp != blr_count)
node->value1 = PAR_parse_node(tdbb, csb, VALUE);
if (blrOp == blr_via)
node->value2 = PAR_parse_node(tdbb, csb, VALUE);
return node;
}
void SubQueryNode::print(string& text, Array<dsql_nod*>& nodes) const
{
text = "SubQueryNode";
ExprNode::print(text, nodes);
}
ValueExprNode* SubQueryNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
// This node is created after dsqlPass and should never be called.
fb_assert(false);
return this;
}
void SubQueryNode::setParameterName(dsql_par* parameter) const
{
MAKE_parameter_names(parameter, dsqlValue1);
}
void SubQueryNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->appendUChar(blrOp);
GEN_expr(dsqlScratch, dsqlRse);
GEN_expr(dsqlScratch, dsqlValue1);
GEN_expr(dsqlScratch, dsqlValue2);
}
void SubQueryNode::make(DsqlCompilerScratch* dsqlScratch, dsql_nod* /*thisNode*/, dsc* desc)
{
MAKE_desc(dsqlScratch, desc, dsqlValue1);
// Set the descriptor flag as nullable. The select expression may or may not return this row
// based on the WHERE clause. Setting this flag warns the client to expect null values.
// (bug 10379)
desc->dsc_flags |= DSC_nullable;
}
bool SubQueryNode::dsqlAggregateFinder(AggregateFinder& visitor)
{
return !visitor.ignoreSubSelects && visitor.visit(&dsqlRse);
}
bool SubQueryNode::dsqlAggregate2Finder(Aggregate2Finder& visitor)
{
return visitor.visit(&dsqlRse); // Pass only the rse.
}
bool SubQueryNode::dsqlSubSelectFinder(SubSelectFinder& visitor)
{
return true;
}
bool SubQueryNode::dsqlFieldFinder(FieldFinder& visitor)
{
return visitor.visit(&dsqlRse); // Pass only the rse.
}
bool SubQueryNode::dsqlFieldRemapper(FieldRemapper& visitor)
{
visitor.visit(&dsqlRse);
dsqlValue1 = dsqlRse->nod_arg[Dsql::e_rse_items]->nod_arg[0];
return false;
}
bool SubQueryNode::jrdStreamFinder(StreamFinder& visitor)
{
if (rse && visitor.visit(rse))
return true;
if (value1 && visitor.visit(value1))
return true;
return false;
}
bool SubQueryNode::jrdStreamsCollector(StreamsCollector& visitor)
{
visitor.visit(rse);
visitor.visit(value1);
return false;
}
bool SubQueryNode::computable(CompilerScratch* csb, SSHORT stream, bool idxUse, bool allowOnlyCurrentStream)
{
if (value2 && !OPT_computable(csb, value2, stream, idxUse, allowOnlyCurrentStream))
return false;
fb_assert(rse->nod_type == nod_class_recsrcnode_jrd);
RseNode* rseNode = reinterpret_cast<RseNode*>(rse->nod_arg[0]);
return rseNode->computable(csb, stream, idxUse, allowOnlyCurrentStream, value1);
}
void SubQueryNode::findDependentFromStreams(const OptimizerRetrieval* optRet, SortedStreamList* streamList)
{
if (value2)
optRet->findDependentFromStreams(value2, streamList);
fb_assert(rse->nod_type == nod_class_recsrcnode_jrd);
RseNode* rseNode = reinterpret_cast<RseNode*>(rse->nod_arg[0]);
rseNode->findDependentFromStreams(optRet, streamList);
// Check value expression, if any.
if (value1)
optRet->findDependentFromStreams(value1, streamList);
}
void SubQueryNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
{
if (blrOp == blr_count)
desc->makeLong(0);
else
CMP_get_desc(tdbb, csb, value1, desc);
if (blrOp == blr_average)
{
if (!(DTYPE_IS_NUMERIC(desc->dsc_dtype) || DTYPE_IS_TEXT(desc->dsc_dtype)))
{
if (desc->dsc_dtype != dtype_unknown)
return;
}
desc->dsc_dtype = DEFAULT_DOUBLE;
desc->dsc_length = sizeof(double);
desc->dsc_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
}
else if (blrOp == blr_total)
{
USHORT dtype;
switch ((dtype = desc->dsc_dtype))
{
case dtype_short:
desc->dsc_dtype = dtype_long;
desc->dsc_length = sizeof(SLONG);
node->nod_scale = desc->dsc_scale;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
return;
case dtype_unknown:
desc->dsc_dtype = dtype_unknown;
desc->dsc_length = 0;
node->nod_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
return;
case dtype_long:
case dtype_int64:
case dtype_real:
case dtype_double:
case dtype_text:
case dtype_cstring:
case dtype_varying:
desc->dsc_dtype = DEFAULT_DOUBLE;
desc->dsc_length = sizeof(double);
desc->dsc_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
node->nod_flags |= nod_double;
return;
case dtype_quad:
desc->dsc_dtype = dtype_quad;
desc->dsc_length = sizeof(SQUAD);
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
node->nod_scale = desc->dsc_scale;
node->nod_flags |= nod_quad;
#ifdef NATIVE_QUAD
return;
#endif
default:
fb_assert(false);
// fall into
case dtype_sql_time:
case dtype_sql_date:
case dtype_timestamp:
case dtype_blob:
case dtype_array:
case dtype_dbkey:
// break to error reporting code
break;
}
if (dtype == dtype_quad)
IBERROR(224); // msg 224 quad word arithmetic not supported
ERR_post(Arg::Gds(isc_datype_notsup)); // data type not supported for arithmetic
}
}
ValueExprNode* SubQueryNode::copy(thread_db* tdbb, NodeCopier& copier)
{
SubQueryNode* node = FB_NEW(*tdbb->getDefaultPool()) SubQueryNode(*tdbb->getDefaultPool(), blrOp);
node->rse = copier.copy(tdbb, rse);
node->value1 = copier.copy(tdbb, value1);
node->value2 = copier.copy(tdbb, value2);
return node;
}
bool SubQueryNode::expressionEqual(thread_db* tdbb, CompilerScratch* csb, /*const*/ ExprNode* other,
USHORT stream)
{
return false;
}
ExprNode* SubQueryNode::pass1(thread_db* tdbb, CompilerScratch* csb)
{
fb_assert(rse->nod_type == nod_class_recsrcnode_jrd);
RseNode* rseNode = reinterpret_cast<RseNode*>(rse->nod_arg[0]);
fb_assert(rseNode->type == RseNode::TYPE);
rseNode->ignoreDbKey(tdbb, csb, csb->csb_view);
rseNode->pass1(tdbb, csb, csb->csb_view);
csb->csb_current_nodes.push(rseNode);
value1 = CMP_pass1(tdbb, csb, value1);
value2 = CMP_pass1(tdbb, csb, value2);
csb->csb_current_nodes.pop();
return this;
}
ExprNode* SubQueryNode::pass2(thread_db* tdbb, CompilerScratch* csb)
{
fb_assert(rse->nod_type == nod_class_recsrcnode_jrd);
RseNode* rseNode = reinterpret_cast<RseNode*>(rse->nod_arg[0]);
if (!rseNode)
ERR_post(Arg::Gds(isc_wish_list));
fb_assert(rseNode->type == RseNode::TYPE);
if (!(rseNode->flags & RseNode::FLAG_VARIANT))
{
node->nod_flags |= nod_invariant;
csb->csb_invariants.push(&node->nod_impure);
}
rseNode->pass2Rse(tdbb, csb);
ExprNode::pass2(tdbb, csb);
node->nod_impure = CMP_impure(csb, sizeof(impure_value_ex));
if (blrOp == blr_average)
node->nod_flags |= nod_double;
else if (blrOp == blr_total)
{
dsc desc;
getDesc(tdbb, csb, &desc);
}
// Bind values of invariant nodes to top-level RSE (if present).
if ((node->nod_flags & nod_invariant) && csb->csb_current_nodes.hasData())
{
LegacyNodeOrRseNode& topRseNode = csb->csb_current_nodes[0];
fb_assert(topRseNode.rseNode);
if (!topRseNode.rseNode->rse_invariants)
{
topRseNode.rseNode->rse_invariants =
FB_NEW(*tdbb->getDefaultPool()) VarInvariantArray(*tdbb->getDefaultPool());
}
topRseNode.rseNode->rse_invariants->add(node->nod_impure);
}
// Finish up processing of record selection expressions.
rsb = CMP_post_rse(tdbb, csb, rseNode);
csb->csb_fors.add(rsb);
return this;
}
// Evaluate a subquery expression.
dsc* SubQueryNode::execute(thread_db* tdbb, jrd_req* request) const
{
impure_value* impure = request->getImpure<impure_value>(node->nod_impure);
request->req_flags &= ~req_null;
dsc* desc = &impure->vlu_desc;
USHORT* invariant_flags;
if (node->nod_flags & nod_invariant)
{
invariant_flags = &impure->vlu_flags;
if (*invariant_flags & VLU_computed)
{
// An invariant node has already been computed.
if (*invariant_flags & VLU_null)
request->req_flags |= req_null;
else
request->req_flags &= ~req_null;
return desc;
}
}
impure->vlu_misc.vlu_long = 0;
impure->vlu_desc.dsc_dtype = dtype_long;
impure->vlu_desc.dsc_length = sizeof(SLONG);
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_long;
ULONG flag = req_null;
try
{
rsb->open(tdbb);
SLONG count = 0;
double d;
// Handle each variety separately
switch (blrOp)
{
case blr_count:
flag = 0;
while (rsb->getRecord(tdbb))
++impure->vlu_misc.vlu_long;
break;
case blr_minimum:
case blr_maximum:
while (rsb->getRecord(tdbb))
{
dsc* value = EVL_expr(tdbb, value1);
if (request->req_flags & req_null)
continue;
int result;
if (flag || ((result = MOV_compare(value, desc)) < 0 && blrOp == blr_minimum) ||
(blrOp != blr_minimum && result > 0))
{
flag = 0;
EVL_make_value(tdbb, value, impure);
}
}
break;
case blr_average: // total or average with dialect-1 semantics
case blr_total:
while (rsb->getRecord(tdbb))
{
desc = EVL_expr(tdbb, value1);
if (request->req_flags & req_null)
continue;
// Note: if the field being SUMed or AVERAGEd is short or long,
// impure will stay long, and the first add() will
// set the correct scale; if it is approximate numeric,
// the first add() will convert impure to double.
ArithmeticNode::add(desc, impure, node, blr_add);
++count;
}
desc = &impure->vlu_desc;
if (blrOp == blr_total)
{
flag = 0;
break;
}
if (!count)
break;
d = MOV_get_double(&impure->vlu_desc);
impure->vlu_misc.vlu_double = d / count;
impure->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
impure->vlu_desc.dsc_length = sizeof(double);
impure->vlu_desc.dsc_scale = 0;
flag = 0;
break;
case blr_via:
if (rsb->getRecord(tdbb))
desc = EVL_expr(tdbb, value1);
else
{
if (value2)
desc = EVL_expr(tdbb, value2);
else
ERR_post(Arg::Gds(isc_from_no_match));
}
flag = request->req_flags;
break;
default:
BUGCHECK(233); // msg 233 eval_statistical: invalid operation
}
}
catch (const Exception&)
{
// Close stream, ignoring any error during it to keep the original error.
try
{
rsb->close(tdbb);
request->req_flags &= ~req_null;
request->req_flags |= flag;
}
catch (const Exception&)
{
}
throw;
}
// Close stream and return value.
rsb->close(tdbb);
request->req_flags &= ~req_null;
request->req_flags |= flag;
// If this is an invariant node, save the return value. If the descriptor does not point to the
// impure area for this node then point this node's descriptor to the correct place;
// Copy the whole structure to be absolutely sure.
if (node->nod_flags & nod_invariant)
{
*invariant_flags |= VLU_computed;
if (request->req_flags & req_null)
*invariant_flags |= VLU_null;
if (desc && (desc != &impure->vlu_desc))
impure->vlu_desc = *desc;
}
return (request->req_flags & req_null) ? NULL : desc;
}
//--------------------
static RegisterNode<SubstringNode> regSubstringNode(blr_substring);
SubstringNode::SubstringNode(MemoryPool& pool, dsql_nod* aExpr, dsql_nod* aStart, dsql_nod* aLength)

View File

@ -32,6 +32,7 @@ class SysFunction;
namespace Jrd {
struct ItemInfo;
class RecordSource;
class ArithmeticNode : public TypedNode<ValueExprNode, ExprNode::TYPE_ARITHMETIC>
@ -619,6 +620,71 @@ public:
};
// This node is used for DSQL subqueries and for legacy (BLR-only) functionality.
class SubQueryNode : public TypedNode<ValueExprNode, ExprNode::TYPE_SUBQUERY>
{
public:
explicit SubQueryNode(MemoryPool& pool, UCHAR aBlrOp, dsql_nod* aDsqlRse = NULL,
dsql_nod* aValue1 = NULL, dsql_nod* aValue2 = NULL);
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, UCHAR blrOp);
virtual void print(Firebird::string& text, Firebird::Array<dsql_nod*>& nodes) const;
virtual ValueExprNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void setParameterName(dsql_par* parameter) const;
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual void make(DsqlCompilerScratch* dsqlScratch, dsql_nod* thisNode, dsc* desc);
virtual bool dsqlAggregateFinder(AggregateFinder& visitor);
virtual bool dsqlAggregate2Finder(Aggregate2Finder& visitor);
virtual bool dsqlSubSelectFinder(SubSelectFinder& visitor);
virtual bool dsqlFieldFinder(FieldFinder& visitor);
virtual bool dsqlFieldRemapper(FieldRemapper& visitor);
virtual bool jrdVisit(JrdNodeVisitor& visitor)
{
return false;
}
virtual bool jrdUnmappedNodeGetter(UnmappedNodeGetter& visitor)
{
return false;
}
virtual bool jrdPossibleUnknownFinder(PossibleUnknownFinder& /*visitor*/)
{
return true;
}
virtual bool jrdStreamFinder(StreamFinder& visitor);
virtual bool jrdStreamsCollector(StreamsCollector& visitor);
virtual bool computable(CompilerScratch* csb, SSHORT stream, bool idxUse,
bool allowOnlyCurrentStream);
virtual void findDependentFromStreams(const OptimizerRetrieval* optRet,
SortedStreamList* streamList);
virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc);
virtual ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier);
virtual bool expressionEqual(thread_db* tdbb, CompilerScratch* csb, /*const*/ ExprNode* other,
USHORT stream) /*const*/;
virtual ExprNode* pass1(thread_db* tdbb, CompilerScratch* csb);
virtual ExprNode* pass2(thread_db* tdbb, CompilerScratch* csb);
virtual dsc* execute(thread_db* tdbb, jrd_req* request) const;
public:
UCHAR blrOp;
dsql_nod* dsqlRse;
dsql_nod* dsqlValue1;
dsql_nod* dsqlValue2;
NestConst<jrd_nod> rse;
NestConst<jrd_nod> value1;
NestConst<jrd_nod> value2;
NestConst<RecordSource> rsb;
};
class SubstringNode : public TypedNode<ValueExprNode, ExprNode::TYPE_SUBSTRING>
{
public:

View File

@ -278,6 +278,7 @@ public:
TYPE_RSE_BOOL,
TYPE_STR_CASE,
TYPE_STR_LEN,
TYPE_SUBQUERY,
TYPE_SUBSTRING,
TYPE_SUBSTRING_SIMILAR,
TYPE_SYSFUNC_CALL,

View File

@ -384,7 +384,6 @@ public:
return StreamFinder(csb, stream).visit(node);
}
protected:
virtual bool visit(const JrdNode& node);
private:
@ -404,7 +403,6 @@ public:
return StreamsCollector(streams).visit(node);
}
protected:
virtual bool visit(const JrdNode& node);
private:

View File

@ -270,10 +270,6 @@ void GEN_expr(DsqlCompilerScratch* dsqlScratch, dsql_nod* node)
GEN_rse(dsqlScratch, node->nod_arg[e_derived_table_rse]);
return;
case nod_via:
blr_operator = blr_via;
break;
case nod_coalesce:
gen_coalesce(dsqlScratch, node);
return;

View File

@ -478,17 +478,6 @@ void MAKE_desc(DsqlCompilerScratch* dsqlScratch, dsc* desc, dsql_nod* node)
}
return;
case nod_via:
MAKE_desc(dsqlScratch, desc, node->nod_arg[e_via_value_1]);
// Set the descriptor flag as nullable. The
// select expression may or may not return
// this row based on the WHERE clause. Setting this
// flag warns the client to expect null values.
// (bug 10379)
desc->dsc_flags |= DSC_nullable;
return;
case nod_hidden_var:
MAKE_desc(dsqlScratch, desc, node->nod_arg[e_hidden_var_expr]);
return;
@ -962,10 +951,6 @@ void MAKE_parameter_names(dsql_par* parameter, const dsql_nod* item)
context = (dsql_ctx*) alias->nod_arg[0]->nod_arg[0];
}
break;
case nod_via:
// subquery, aka sub-select
MAKE_parameter_names(parameter, item->nod_arg[e_via_value_1]);
break;
case nod_derived_field:
string = (dsql_str*) item->nod_arg[e_derived_field_name];
parameter->par_alias = string->str_data;

View File

@ -122,7 +122,6 @@ enum nod_t
nod_flag,
nod_join,
nod_unique,
nod_via,
nod_field,
nod_dom_value,
nod_field_name,
@ -492,11 +491,6 @@ enum node_args {
e_join_boolean,
e_join_count,
e_via_rse = 0, // nod_via
e_via_value_1,
e_via_value_2,
e_via_count,
e_while_cond = 0, // nod_while
e_while_action,
e_while_label,

View File

@ -373,11 +373,6 @@ bool AggregateFinder::internalVisit(const dsql_nod* node)
return visit(&lmap->map_node);
}
case nod_via:
if (!ignoreSubSelects)
aggregate = visit(&node->nod_arg[e_via_rse]);
return aggregate;
case nod_rse:
++currentLevel;
aggregate |= visit(&node->nod_arg[e_rse_streams]);
@ -468,11 +463,6 @@ bool Aggregate2Finder::internalVisit(const dsql_nod* node)
break;
}
case nod_via:
// Pass only the rse from the nod_via
found |= visit(&node->nod_arg[e_via_rse]);
break;
case nod_rse:
{
AutoSetRestore<bool> autoCurrentScopeLevelEqual(&currentScopeLevelEqual, false);
@ -544,11 +534,6 @@ bool FieldFinder::internalVisit(const dsql_nod* node)
break;
}
case nod_via:
// Pass only the rse from the nod_via
found |= visit(&node->nod_arg[e_via_rse]);
break;
case nod_rse:
// Pass rse_boolean (where clause) and rse_items (select items)
found |= visit(&node->nod_arg[e_rse_boolean]);
@ -743,14 +728,6 @@ bool InvalidReferenceFinder::internalVisit(const dsql_nod* node)
}
break;
case nod_via:
{
const dsql_nod* const* ptr = node->nod_arg;
for (const dsql_nod* const* const end = ptr + node->nod_count; ptr < end; ptr++)
invalid |= visit(ptr);
break;
}
case nod_coalesce:
case nod_unique:
case nod_rse:
@ -864,11 +841,6 @@ bool FieldRemapper::internalVisit(dsql_nod* node)
break;
}
case nod_via:
visit(&node->nod_arg[e_via_rse]);
node->nod_arg[e_via_value_1] = node->nod_arg[e_via_rse]->nod_arg[e_rse_items]->nod_arg[0];
break;
case nod_rse:
{
AutoSetRestore<USHORT> autoCurrentLevel(&currentLevel, currentLevel + 1);
@ -944,9 +916,6 @@ bool SubSelectFinder::internalVisit(const dsql_nod* node)
break;
}
case nod_via:
return true;
case nod_aggregate:
case nod_map:
@ -1293,17 +1262,20 @@ dsql_nod* PASS1_node(DsqlCompilerScratch* dsqlScratch, dsql_nod* input)
case nod_select_expr:
{
const DsqlContextStack::iterator base(*dsqlScratch->context);
node = MAKE_node(nod_via, e_via_count);
dsql_nod* rse = PASS1_rse(dsqlScratch, input, NULL);
node->nod_arg[e_via_rse] = rse;
node->nod_arg[e_via_value_1] = rse->nod_arg[e_rse_items]->nod_arg[0];
node->nod_arg[e_via_value_2] = MAKE_node(nod_class_exprnode, 1);
node->nod_arg[e_via_value_2]->nod_arg[0] = reinterpret_cast<dsql_nod*>(
dsql_nod* rse = PASS1_rse(dsqlScratch, input, NULL);
SubQueryNode* subQueryNode = FB_NEW(*tdbb->getDefaultPool()) SubQueryNode(*tdbb->getDefaultPool(),
blr_via, rse, rse->nod_arg[e_rse_items]->nod_arg[0], MAKE_node(nod_class_exprnode, 1));
subQueryNode->dsqlValue2->nod_arg[0] = reinterpret_cast<dsql_nod*>(
FB_NEW(*tdbb->getDefaultPool()) NullNode(*tdbb->getDefaultPool()));
// Finish off by cleaning up contexts
dsqlScratch->context->clear(base);
node = MAKE_node(nod_class_exprnode, 1);
node->nod_arg[0] = reinterpret_cast<dsql_nod*>(subQueryNode);
return node;
}
@ -5405,19 +5377,24 @@ static dsql_nod* pass1_make_derived_field(DsqlCompilerScratch* dsqlScratch, thre
return select_item;
}
case nod_via:
case nod_class_exprnode:
{
SubQueryNode* subQueryNode;
if ((subQueryNode = ExprNode::as<SubQueryNode>(select_item)))
{
// Try to generate derived field from sub-select
dsql_nod* derived_field = pass1_make_derived_field(dsqlScratch, tdbb,
select_item->nod_arg[e_via_value_1]);
subQueryNode->dsqlValue1);
if (derived_field->nod_type == nod_derived_field)
{
derived_field->nod_arg[e_derived_field_value] = select_item;
return derived_field;
}
}
return select_item;
break;
}
default:
@ -8819,9 +8796,6 @@ void DSQL_pretty(const dsql_nod* node, int column)
case nod_unique:
verb = "unique";
break;
case nod_via:
verb = "via";
break;
case nod_coalesce:
verb = "coalesce";

View File

@ -175,28 +175,6 @@ bool OPT_computable(CompilerScratch* csb, jrd_nod* node, SSHORT stream,
return false;
}
return csb->csb_rpt[n].csb_flags & csb_active;
case nod_min:
case nod_max:
case nod_average:
case nod_total:
case nod_count:
case nod_from:
{
jrd_nod* sub;
if ((sub = node->nod_arg[e_stat_default]) &&
!OPT_computable(csb, sub, stream, idx_use, allowOnlyCurrentStream))
{
return false;
}
fb_assert(node->nod_arg[e_stat_rse]->nod_type == nod_class_recsrcnode_jrd);
RseNode* rse = reinterpret_cast<RseNode*>(node->nod_arg[e_stat_rse]->nod_arg[0]);
return rse->computable(csb, stream, idx_use, allowOnlyCurrentStream,
node->nod_arg[e_stat_value]);
}
}
return true;
@ -787,32 +765,6 @@ void OptimizerRetrieval::findDependentFromStreams(jrd_nod* node, SortedStreamLis
return;
}
case nod_min:
case nod_max:
case nod_average:
case nod_total:
case nod_count:
case nod_from:
{
jrd_nod* sub;
if (sub = node->nod_arg[e_stat_default])
findDependentFromStreams(sub, streamList);
fb_assert(node->nod_arg[e_stat_rse]->nod_type == nod_class_recsrcnode_jrd);
RseNode* rse = reinterpret_cast<RseNode*>(node->nod_arg[e_stat_rse]->nod_arg[0]);
rse->findDependentFromStreams(this, streamList);
jrd_nod* value = node->nod_arg[e_stat_value];
// Check value expression, if any
if (value)
findDependentFromStreams(value, streamList);
break;
}
}
}

View File

@ -412,83 +412,6 @@ void CMP_get_desc(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node, DSC* des
switch (node->nod_type)
{
case nod_max:
case nod_min:
case nod_from:
CMP_get_desc(tdbb, csb, node->nod_arg[e_stat_value], desc);
return;
case nod_total:
if (node->nod_type == nod_total)
CMP_get_desc(tdbb, csb, node->nod_arg[e_stat_value], desc);
else
CMP_get_desc(tdbb, csb, node->nod_arg[0], desc);
switch (dtype = desc->dsc_dtype)
{
case dtype_short:
desc->dsc_dtype = dtype_long;
desc->dsc_length = sizeof(SLONG);
node->nod_scale = desc->dsc_scale;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
return;
case dtype_unknown:
desc->dsc_dtype = dtype_unknown;
desc->dsc_length = 0;
node->nod_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
return;
case dtype_long:
case dtype_int64:
case dtype_real:
case dtype_double:
case dtype_text:
case dtype_cstring:
case dtype_varying:
desc->dsc_dtype = DEFAULT_DOUBLE;
desc->dsc_length = sizeof(double);
desc->dsc_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
node->nod_flags |= nod_double;
return;
case dtype_quad:
desc->dsc_dtype = dtype_quad;
desc->dsc_length = sizeof(SQUAD);
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
node->nod_scale = desc->dsc_scale;
node->nod_flags |= nod_quad;
#ifdef NATIVE_QUAD
return;
#endif
default:
fb_assert(false);
// FALLINTO
case dtype_sql_time:
case dtype_sql_date:
case dtype_timestamp:
case dtype_blob:
case dtype_array:
case dtype_dbkey:
// break to error reporting code
break;
}
break;
case nod_count:
desc->dsc_dtype = dtype_long;
desc->dsc_length = sizeof(SLONG);
desc->dsc_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
return;
case nod_field:
{
const USHORT stream = (USHORT) (IPTR) node->nod_arg[e_fld_stream];
@ -544,22 +467,6 @@ void CMP_get_desc(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node, DSC* des
return;
}
case nod_average:
CMP_get_desc(tdbb, csb, node->nod_arg[e_stat_value], desc);
if (!(DTYPE_IS_NUMERIC(desc->dsc_dtype) || DTYPE_IS_TEXT(desc->dsc_dtype)))
{
if (desc->dsc_dtype != dtype_unknown)
break;
}
desc->dsc_dtype = DEFAULT_DOUBLE;
desc->dsc_length = sizeof(double);
desc->dsc_scale = 0;
desc->dsc_sub_type = 0;
desc->dsc_flags = 0;
return;
case nod_class_exprnode_jrd:
{
ValueExprNode* exprNode = reinterpret_cast<ValueExprNode*>(node->nod_arg[0]);
@ -1125,15 +1032,6 @@ jrd_nod* NodeCopier::copy(thread_db* tdbb, jrd_nod* input)
return node;
}
case nod_count:
case nod_max:
case nod_min:
case nod_total:
case nod_average:
case nod_from:
args = e_stat_length;
break;
case nod_class_recsrcnode_jrd:
node = PAR_make_node(tdbb, 1);
node->nod_type = input->nod_type;
@ -1895,25 +1793,6 @@ jrd_nod* CMP_pass1(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node)
node->nod_arg[e_cursor_stmt_into] = CMP_pass1(tdbb, csb, node->nod_arg[e_cursor_stmt_into]);
break;
case nod_max:
case nod_min:
case nod_average:
case nod_from:
case nod_count:
case nod_total:
{
fb_assert(node->nod_arg[e_stat_rse]->nod_type == nod_class_recsrcnode_jrd);
RseNode* rseNode = reinterpret_cast<RseNode*>(node->nod_arg[e_stat_rse]->nod_arg[0]);
fb_assert(rseNode->type == RseNode::TYPE);
rseNode->ignoreDbKey(tdbb, csb, view);
rseNode->pass1(tdbb, csb, csb->csb_view);
csb->csb_current_nodes.push(rseNode);
node->nod_arg[e_stat_value] = CMP_pass1(tdbb, csb, node->nod_arg[e_stat_value]);
node->nod_arg[e_stat_default] = CMP_pass1(tdbb, csb, node->nod_arg[e_stat_default]);
csb->csb_current_nodes.pop();
}
return node;
case nod_class_recsrcnode_jrd:
reinterpret_cast<RecordSourceNode*>(node->nod_arg[0])->pass1(tdbb, csb, view);
break;
@ -2719,7 +2598,6 @@ jrd_nod* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, jrd_nod* const node, j
DEBUG;
RseNode* rse_node = NULL;
RecordSource** rsb_ptr = NULL;
Cursor** cursor_ptr = NULL;
switch (node->nod_type)
@ -2740,29 +2618,6 @@ jrd_nod* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, jrd_nod* const node, j
CMP_pass2(tdbb, csb, node->nod_arg[e_cursor_stmt_into], node);
break;
case nod_max:
case nod_min:
case nod_count:
case nod_average:
case nod_total:
case nod_from:
fb_assert(node->nod_arg[e_stat_rse]->nod_type == nod_class_recsrcnode_jrd);
rse_node = reinterpret_cast<RseNode*>(node->nod_arg[e_stat_rse]->nod_arg[0]);
if (!rse_node)
ERR_post(Arg::Gds(isc_wish_list));
fb_assert(rse_node->type == RseNode::TYPE);
if (!(rse_node->flags & RseNode::FLAG_VARIANT))
{
node->nod_flags |= nod_invariant;
csb->csb_invariants.push(&node->nod_impure);
}
rsb_ptr = (RecordSource**) &node->nod_arg[e_stat_rsb];
break;
case nod_src_info:
node->nod_arg[e_src_info_node] = CMP_pass2(tdbb, csb, node->nod_arg[e_src_info_node], node);
return node;
@ -2839,18 +2694,6 @@ jrd_nod* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, jrd_nod* const node, j
CMP_pass2(tdbb, csb, node->nod_arg[e_asgn_missing2], node);
break;
case nod_average:
node->nod_flags |= nod_double;
// FALL INTO
case nod_max:
case nod_min:
case nod_from:
case nod_count:
node->nod_count = 0;
node->nod_impure = CMP_impure(csb, sizeof(impure_value_ex));
break;
case nod_block:
node->nod_impure = CMP_impure(csb, sizeof(SLONG));
break;
@ -2862,15 +2705,6 @@ jrd_nod* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, jrd_nod* const node, j
}
break;
case nod_total:
{
node->nod_count = 0;
node->nod_impure = CMP_impure(csb, sizeof(impure_value_ex));
dsc descriptor_a;
CMP_get_desc(tdbb, csb, node, &descriptor_a);
}
break;
case nod_message:
{
const Format* format = (Format*) node->nod_arg[e_msg_format];
@ -2992,9 +2826,6 @@ jrd_nod* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, jrd_nod* const node, j
csb->csb_fors.add(rsb);
if (rsb_ptr)
*rsb_ptr = rsb;
if (cursor_ptr)
{
Cursor* const cursor = FB_NEW(*tdbb->getDefaultPool()) Cursor(

View File

@ -119,7 +119,6 @@ using namespace Jrd;
using namespace Firebird;
static dsc* dbkey(thread_db*, const jrd_nod*, impure_value*);
static dsc* eval_statistical(thread_db*, const jrd_nod*, impure_value*);
static dsc* record_version(thread_db*, const jrd_nod*, impure_value*);
static dsc* scalar(thread_db*, const jrd_nod*, impure_value*);
@ -462,14 +461,6 @@ dsc* EVL_expr(thread_db* tdbb, const jrd_nod* node)
return NULL;
}
case nod_max:
case nod_min:
case nod_count:
case nod_average:
case nod_total:
case nod_from:
return eval_statistical(tdbb, node, impure);
case nod_scalar:
return scalar(tdbb, node, impure);
@ -864,179 +855,6 @@ static dsc* dbkey(thread_db* tdbb, const jrd_nod* node, impure_value* impure)
}
static dsc* eval_statistical(thread_db* tdbb, const jrd_nod* node, impure_value* impure)
{
/**************************************
*
* e v a l _ s t a t i s t i c a l
*
**************************************
*
* Functional description
* Evaluate a statistical expression.
*
**************************************/
USHORT* invariant_flags;
SET_TDBB(tdbb);
DEV_BLKCHK(node, type_nod);
// Get started by opening stream
jrd_req* request = tdbb->getRequest();
dsc* desc = &impure->vlu_desc;
if (node->nod_flags & nod_invariant)
{
invariant_flags = &impure->vlu_flags;
if (*invariant_flags & VLU_computed)
{
// An invariant node has already been computed.
if (*invariant_flags & VLU_null)
request->req_flags |= req_null;
else
request->req_flags &= ~req_null;
return desc;
}
}
impure->vlu_misc.vlu_long = 0;
impure->vlu_desc.dsc_dtype = dtype_long;
impure->vlu_desc.dsc_length = sizeof(SLONG);
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_long;
const RecordSource* const rsb = reinterpret_cast<const RecordSource*>(node->nod_arg[e_stat_rsb]);
rsb->open(tdbb);
SLONG count = 0;
ULONG flag = req_null;
double d;
try
{
// Handle each variety separately
switch (node->nod_type)
{
case nod_count:
flag = 0;
while (rsb->getRecord(tdbb))
{
++impure->vlu_misc.vlu_long;
}
break;
case nod_min:
case nod_max:
while (rsb->getRecord(tdbb))
{
dsc* value = EVL_expr(tdbb, node->nod_arg[e_stat_value]);
if (request->req_flags & req_null) {
continue;
}
int result;
if (flag || ((result = MOV_compare(value, desc)) < 0 && node->nod_type == nod_min) ||
(node->nod_type != nod_min && result > 0))
{
flag = 0;
EVL_make_value(tdbb, value, impure);
}
}
break;
case nod_from:
if (rsb->getRecord(tdbb))
{
desc = EVL_expr(tdbb, node->nod_arg[e_stat_value]);
}
else
{
if (node->nod_arg[e_stat_default])
desc = EVL_expr(tdbb, node->nod_arg[e_stat_default]);
else
ERR_post(Arg::Gds(isc_from_no_match));
}
flag = request->req_flags;
break;
case nod_average: // total or average with dialect-1 semantics
case nod_total:
while (rsb->getRecord(tdbb))
{
desc = EVL_expr(tdbb, node->nod_arg[e_stat_value]);
if (request->req_flags & req_null) {
continue;
}
// Note: if the field being SUMed or AVERAGEd is short or long,
// impure will stay long, and the first add() will
// set the correct scale; if it is approximate numeric,
// the first add() will convert impure to double.
ArithmeticNode::add(desc, impure, node, blr_add);
count++;
}
desc = &impure->vlu_desc;
if (node->nod_type == nod_total)
{
flag = 0;
break;
}
if (!count)
break;
d = MOV_get_double(&impure->vlu_desc);
impure->vlu_misc.vlu_double = d / count;
impure->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
impure->vlu_desc.dsc_length = sizeof(double);
impure->vlu_desc.dsc_scale = 0;
flag = 0;
break;
default:
BUGCHECK(233); // msg 233 eval_statistical: invalid operation
}
}
catch (const Firebird::Exception&)
{
// close stream
// ignore any error during it to keep original
try
{
rsb->close(tdbb);
request->req_flags &= ~req_null;
request->req_flags |= flag;
}
catch (const Firebird::Exception&)
{} // no-op
throw;
}
// Close stream and return value
rsb->close(tdbb);
request->req_flags &= ~req_null;
request->req_flags |= flag;
// If this is an invariant node, save the return value. If the
// descriptor does not point to the impure area for this node then
// point this node's descriptor to the correct place; copy the whole
// structure to be absolutely sure
if (node->nod_flags & nod_invariant)
{
*invariant_flags |= VLU_computed;
if (request->req_flags & req_null)
*invariant_flags |= VLU_null;
if (desc && (desc != &impure->vlu_desc))
impure->vlu_desc = *desc;
}
return desc;
}
static dsc* record_version(thread_db* tdbb, const jrd_nod* node, impure_value* impure)
{
/**************************************

View File

@ -222,14 +222,6 @@ const int e_val_boolean = 0;
const int e_val_value = 1;
const int e_val_length = 2;
// Statistical expressions
const int e_stat_rse = 0;
const int e_stat_value = 1;
const int e_stat_default = 2;
const int e_stat_rsb = 3;
const int e_stat_length = 4;
// Execute stored procedure
const int e_esp_inputs = 0;

View File

@ -59,7 +59,6 @@ NODE(nod_set_generator2, set_generator, "")
NODE(nod_dbkey, dbkey, "ROWID")
NODE(nod_field, field, "")
NODE(nod_from, from, "")
NODE(nod_scalar, scalar, "")
NODE(nod_rec_version, record_version, "RECORD VERSION")
NODE(nod_domain_validation, domain_validation, "")
@ -67,12 +66,6 @@ NODE(nod_derived_expr, derived_expr, "derived_expr")
NODE(nod_stmt_expr, stmt_expr, "stmt_expr")
NODE(nod_average, average, "AVG")
NODE(nod_count, count, "COUNT")
NODE(nod_max, max, "MAX")
NODE(nod_min, min, "MIN")
NODE(nod_total, total, "SUM")
NODE(nod_class_exprnode_jrd, class_exprnode_jrd, "class_exprnode_jrd")
NODE(nod_class_stmtnode_jrd, class_stmtnode_jrd, "class_stmtnode_jrd")
NODE(nod_class_recsrcnode_jrd, class_recsrcnode_jrd, "class_recsrcnode_jrd")

View File

@ -231,24 +231,6 @@ bool StreamFinder::visit(const JrdNode& node)
break;
}
case nod_average:
case nod_count:
case nod_from:
case nod_max:
case nod_min:
case nod_total:
{
jrd_nod* nodeDefault = jrdNode->nod_arg[e_stat_rse];
if (nodeDefault && visit(nodeDefault))
return true;
jrd_nod* value = jrdNode->nod_arg[e_stat_value];
if (value && visit(value))
return true;
return false;
}
default:
return visitChildren(node);
}
@ -320,16 +302,6 @@ bool StreamsCollector::visit(const JrdNode& node)
break;
}
case nod_average:
case nod_count:
case nod_from:
case nod_max:
case nod_min:
case nod_total:
visit(jrdNode->nod_arg[e_stat_rse]);
visit(jrdNode->nod_arg[e_stat_value]);
break;
default:
return visitChildren(node);
}
@ -1579,19 +1551,16 @@ static bool check_for_nod_from(const jrd_nod* node)
* Check for nod_from under >=0 CastNode nodes.
*
**************************************/
const CastNode* castNode = ExprNode::as<CastNode>(node);
const CastNode* castNode;
const SubQueryNode* subQueryNode;
if (castNode)
if ((castNode = ExprNode::as<CastNode>(node)))
return check_for_nod_from(castNode->source);
switch (node->nod_type)
{
case nod_from:
else if ((subQueryNode = ExprNode::as<SubQueryNode>(node)) && subQueryNode->blrOp == blr_via)
return true;
default:
return false;
}
}
static SLONG decompose(thread_db* tdbb, BoolExprNode* boolNode, BoolExprNodeStack& stack,
CompilerScratch* csb)

View File

@ -2582,20 +2582,6 @@ jrd_nod* PAR_parse_node(thread_db* tdbb, CompilerScratch* csb, USHORT expected)
node->nod_arg[0] = (jrd_nod*)(IPTR) csb->csb_blr_reader.getByte();
break;
case blr_maximum:
case blr_minimum:
case blr_count:
case blr_average:
case blr_total:
case blr_from:
case blr_via:
node->nod_arg[e_stat_rse] = PAR_parse_node(tdbb, csb, TYPE_RSE);
if (blr_operator != blr_count)
node->nod_arg[e_stat_value] = PAR_parse_node(tdbb, csb, VALUE);
if (blr_operator == blr_via)
node->nod_arg[e_stat_default] = PAR_parse_node(tdbb, csb, VALUE);
break;
case blr_init_variable:
{
n = csb->csb_blr_reader.getWord();

View File

@ -115,23 +115,20 @@ static const VERB verbs[] =
PAIR(nod_class_exprnode_jrd, blr_parameter3, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_variable, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_user_name, 1, 0, VALUE, VALUE),
PAIR(nod_average, blr_average, e_stat_length, 2, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_average, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_concatenate, 1, 0, VALUE, VALUE),
PAIR(nod_count, blr_count, e_stat_length, 1, VALUE, VALUE),
/* count2
, PAIR (nod_count2, blr_count2, e_stat_length, 2, VALUE, VALUE)
*/
PAIR(nod_class_exprnode_jrd, blr_count, 1, 0, VALUE, VALUE),
PAIR(nod_dbkey, blr_dbkey, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_divide, 1, 0, VALUE, VALUE),
PAIR(nod_field, blr_fid, 0, 0, VALUE, VALUE),
PAIR(nod_field, blr_field, 0, 0, VALUE, VALUE),
PAIR(nod_from, blr_via, e_stat_length, 3, VALUE, OTHER),
PAIR(nod_from, blr_from, e_stat_length, 2, VALUE, OTHER),
PAIR(nod_class_exprnode_jrd, blr_via, 1, 0, VALUE, OTHER),
PAIR(nod_class_exprnode_jrd, blr_from, 1, 0, VALUE, OTHER),
PAIR(nod_class_exprnode_jrd, blr_function, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_literal, 1, 0, VALUE, OTHER),
PAIR(nod_scalar, blr_index, 2, 2, VALUE, VALUE),
PAIR(nod_max, blr_maximum, e_stat_length, 2, VALUE, VALUE),
PAIR(nod_min, blr_minimum, e_stat_length, 2, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_maximum, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_minimum, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_null, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_multiply, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_negate, 1, 0, VALUE, VALUE),
@ -139,7 +136,7 @@ static const VERB verbs[] =
PAIR(nod_class_exprnode_jrd, blr_upcase, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_substring, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_subtract, 1, 0, VALUE, VALUE),
PAIR(nod_total, blr_total, e_stat_length, 2, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_total, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_value_if, 1, 0, VALUE, OTHER),
PAIR(nod_class_exprnode_jrd, blr_equiv, 1, 0, TYPE_BOOL, VALUE),
PAIR(nod_class_exprnode_jrd, blr_eql, 1, 0, TYPE_BOOL, VALUE),