mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-29 06:43:03 +01:00
11421 lines
306 KiB
C++
11421 lines
306 KiB
C++
/*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.Inprise.com/IPL.html
|
|
*
|
|
* Software distributed under the License is distributed on an
|
|
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
* Adriano dos Santos Fernandes - refactored from pass1.cpp, gen.cpp, cmp.cpp, par.cpp and evl.cpp
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include "../common/classes/FpeControl.h"
|
|
#include "../common/classes/VaryStr.h"
|
|
#include "../dsql/ExprNodes.h"
|
|
#include "../dsql/BoolNodes.h"
|
|
#include "../dsql/StmtNodes.h"
|
|
#include "../jrd/align.h"
|
|
#include "../jrd/blr.h"
|
|
#include "../jrd/tra.h"
|
|
#include "../jrd/Function.h"
|
|
#include "../jrd/SysFunction.h"
|
|
#include "../jrd/recsrc/RecordSource.h"
|
|
#include "../jrd/Optimizer.h"
|
|
#include "../jrd/recsrc/Cursor.h"
|
|
#include "../jrd/blb_proto.h"
|
|
#include "../jrd/cmp_proto.h"
|
|
#include "../jrd/cvt_proto.h"
|
|
#include "../jrd/dpm_proto.h"
|
|
#include "../common/dsc_proto.h"
|
|
#include "../jrd/evl_proto.h"
|
|
#include "../jrd/exe_proto.h"
|
|
#include "../jrd/fun_proto.h"
|
|
#include "../jrd/intl_proto.h"
|
|
#include "../jrd/met_proto.h"
|
|
#include "../jrd/mov_proto.h"
|
|
#include "../jrd/pag_proto.h"
|
|
#include "../jrd/par_proto.h"
|
|
#include "../dsql/ddl_proto.h"
|
|
#include "../dsql/errd_proto.h"
|
|
#include "../dsql/gen_proto.h"
|
|
#include "../dsql/make_proto.h"
|
|
#include "../dsql/metd_proto.h"
|
|
#include "../dsql/pass1_proto.h"
|
|
#include "../dsql/utld_proto.h"
|
|
#include "../dsql/DSqlDataTypeUtil.h"
|
|
#include "../jrd/DataTypeUtil.h"
|
|
#include "../jrd/Collation.h"
|
|
#include "../jrd/trace/TraceManager.h"
|
|
#include "../jrd/trace/TraceObjects.h"
|
|
#include "../jrd/trace/TraceJrdHelpers.h"
|
|
|
|
using namespace Firebird;
|
|
using namespace Jrd;
|
|
|
|
namespace Jrd {
|
|
|
|
|
|
static const long LONG_POS_MAX = 2147483647;
|
|
static const SINT64 MAX_INT64_LIMIT = MAX_SINT64 / 10;
|
|
static const SINT64 MIN_INT64_LIMIT = MIN_SINT64 / 10;
|
|
static const SINT64 SECONDS_PER_DAY = 24 * 60 * 60;
|
|
static const SINT64 ISC_TICKS_PER_DAY = SECONDS_PER_DAY * ISC_TIME_SECONDS_PRECISION;
|
|
static const SCHAR DIALECT_3_TIMESTAMP_SCALE = -9;
|
|
static const SCHAR DIALECT_1_TIMESTAMP_SCALE = 0;
|
|
|
|
static bool couldBeDate(const dsc desc);
|
|
static SINT64 getDayFraction(const dsc* d);
|
|
static SINT64 getTimeStampToIscTicks(const dsc* d);
|
|
static bool isDateAndTime(const dsc& d1, const dsc& d2);
|
|
static void setParameterInfo(dsql_par* parameter, const dsql_ctx* context);
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
void NodeRef::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
internalPass2(tdbb, csb);
|
|
|
|
ExprNode* node = getExpr();
|
|
|
|
// Bind values of invariant nodes to top-level RSE (if present)
|
|
if (node && (node->nodFlags & ExprNode::FLAG_INVARIANT))
|
|
{
|
|
if (csb->csb_current_nodes.hasData())
|
|
{
|
|
RseOrExprNode& 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->impureOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
void ExprNode::print(string& /*text*/) const
|
|
{
|
|
}
|
|
|
|
bool ExprNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (other->type != type)
|
|
return false;
|
|
|
|
size_t count = dsqlChildNodes.getCount();
|
|
if (other->dsqlChildNodes.getCount() != count)
|
|
return false;
|
|
|
|
const NodeRef* const* j = other->dsqlChildNodes.begin();
|
|
|
|
for (const NodeRef* const* i = dsqlChildNodes.begin(); i != dsqlChildNodes.end(); ++i, ++j)
|
|
{
|
|
if (!**i != !**j || !PASS1_node_match((*i)->getExpr(), (*j)->getExpr(), ignoreMapCast))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ExprNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (other->type != type)
|
|
return false;
|
|
|
|
size_t count = jrdChildNodes.getCount();
|
|
if (other->jrdChildNodes.getCount() != count)
|
|
return false;
|
|
|
|
const NodeRef* const* j = other->jrdChildNodes.begin();
|
|
|
|
for (const NodeRef* const* i = jrdChildNodes.begin(); i != jrdChildNodes.end(); ++i, ++j)
|
|
{
|
|
if (!**i && !**j)
|
|
continue;
|
|
|
|
if (!**i || !**j || !(*i)->getExpr()->sameAs((*j)->getExpr(), ignoreStreams))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ExprNode::computable(CompilerScratch* csb, StreamType stream,
|
|
bool allowOnlyCurrentStream, ValueExprNode* /*value*/)
|
|
{
|
|
for (NodeRef** i = jrdChildNodes.begin(); i != jrdChildNodes.end(); ++i)
|
|
{
|
|
if (**i && !(*i)->getExpr()->computable(csb, stream, allowOnlyCurrentStream))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ExprNode::findDependentFromStreams(const OptimizerRetrieval* optRet, SortedStreamList* streamList)
|
|
{
|
|
for (NodeRef** i = jrdChildNodes.begin(); i != jrdChildNodes.end(); ++i)
|
|
{
|
|
if (**i)
|
|
(*i)->getExpr()->findDependentFromStreams(optRet, streamList);
|
|
}
|
|
}
|
|
|
|
ExprNode* ExprNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
for (NodeRef** i = jrdChildNodes.begin(); i != jrdChildNodes.end(); ++i)
|
|
{
|
|
if (**i)
|
|
(*i)->pass1(tdbb, csb);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
ExprNode* ExprNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
for (NodeRef** i = jrdChildNodes.begin(); i != jrdChildNodes.end(); ++i)
|
|
{
|
|
if (**i)
|
|
(*i)->pass2(tdbb, csb);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<ArithmeticNode> regArithmeticNodeAdd(blr_add);
|
|
static RegisterNode<ArithmeticNode> regArithmeticNodeSubtract(blr_subtract);
|
|
static RegisterNode<ArithmeticNode> regArithmeticNodeMultiply(blr_multiply);
|
|
static RegisterNode<ArithmeticNode> regArithmeticNodeDivide(blr_divide);
|
|
|
|
ArithmeticNode::ArithmeticNode(MemoryPool& pool, UCHAR aBlrOp, bool aDialect1,
|
|
ValueExprNode* aArg1, ValueExprNode* aArg2)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_ARITHMETIC>(pool),
|
|
blrOp(aBlrOp),
|
|
dialect1(aDialect1),
|
|
label(pool),
|
|
arg1(aArg1),
|
|
arg2(aArg2)
|
|
{
|
|
switch (blrOp)
|
|
{
|
|
case blr_add:
|
|
dsqlCompatDialectVerb = "add";
|
|
break;
|
|
|
|
case blr_subtract:
|
|
dsqlCompatDialectVerb = "subtract";
|
|
break;
|
|
|
|
case blr_multiply:
|
|
dsqlCompatDialectVerb = "multiply";
|
|
break;
|
|
|
|
case blr_divide:
|
|
dsqlCompatDialectVerb = "divide";
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
label = dsqlCompatDialectVerb;
|
|
label.upper();
|
|
|
|
addChildNode(arg1, arg1);
|
|
addChildNode(arg2, arg2);
|
|
}
|
|
|
|
DmlNode* ArithmeticNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
|
{
|
|
ArithmeticNode* node = FB_NEW(pool) ArithmeticNode(
|
|
pool, blrOp, (csb->blrVersion == 4));
|
|
node->arg1 = PAR_parse_value(tdbb, csb);
|
|
node->arg2 = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void ArithmeticNode::print(string& text) const
|
|
{
|
|
text.printf("ArithmeticNode %s (%d)", label.c_str(), (dialect1 ? 1 : 3));
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void ArithmeticNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = label;
|
|
}
|
|
|
|
bool ArithmeticNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, arg1, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, arg2, desc, forceVarChar);
|
|
}
|
|
|
|
void ArithmeticNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blrOp);
|
|
GEN_expr(dsqlScratch, arg1);
|
|
GEN_expr(dsqlScratch, arg2);
|
|
}
|
|
|
|
void ArithmeticNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
dsc desc1, desc2;
|
|
|
|
MAKE_desc(dsqlScratch, &desc1, arg1);
|
|
MAKE_desc(dsqlScratch, &desc2, arg2);
|
|
|
|
if (desc1.isNull())
|
|
{
|
|
desc1 = desc2;
|
|
desc1.setNull();
|
|
}
|
|
|
|
if (desc2.isNull())
|
|
{
|
|
desc2 = desc1;
|
|
desc2.setNull();
|
|
}
|
|
|
|
if (arg1->is<NullNode>() && arg2->is<NullNode>())
|
|
{
|
|
// NULL + NULL = NULL of INT
|
|
desc->makeLong(0);
|
|
desc->setNullable(true);
|
|
}
|
|
else if (dialect1)
|
|
makeDialect1(desc, desc1, desc2);
|
|
else
|
|
makeDialect3(desc, desc1, desc2);
|
|
}
|
|
|
|
void ArithmeticNode::makeDialect1(dsc* desc, dsc& desc1, dsc& desc2)
|
|
{
|
|
USHORT dtype, dtype1, dtype2;
|
|
|
|
switch (blrOp)
|
|
{
|
|
case blr_add:
|
|
case blr_subtract:
|
|
dtype1 = desc1.dsc_dtype;
|
|
if (dtype_int64 == dtype1 || DTYPE_IS_TEXT(dtype1))
|
|
dtype1 = dtype_double;
|
|
|
|
dtype2 = desc2.dsc_dtype;
|
|
if (dtype_int64 == dtype2 || DTYPE_IS_TEXT(dtype2))
|
|
dtype2 = dtype_double;
|
|
|
|
dtype = MAX(dtype1, dtype2);
|
|
|
|
if (DTYPE_IS_BLOB(dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_no_blob_array));
|
|
}
|
|
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_sql_time:
|
|
case dtype_sql_date:
|
|
// CVC: I don't see how this case can happen since dialect 1 doesn't accept
|
|
// DATE or TIME
|
|
// Forbid <date/time> +- <string>
|
|
if (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_nodateortime_pm_string));
|
|
}
|
|
// fall into
|
|
|
|
case dtype_timestamp:
|
|
|
|
// Allow <timestamp> +- <string> (historical)
|
|
if (couldBeDate(desc1) && couldBeDate(desc2))
|
|
{
|
|
if (blrOp == blr_subtract)
|
|
{
|
|
// <any date> - <any date>
|
|
|
|
// Legal permutations are:
|
|
// <timestamp> - <timestamp>
|
|
// <timestamp> - <date>
|
|
// <date> - <date>
|
|
// <date> - <timestamp>
|
|
// <time> - <time>
|
|
// <timestamp> - <string>
|
|
// <string> - <timestamp>
|
|
// <string> - <string>
|
|
|
|
if (DTYPE_IS_TEXT(desc1.dsc_dtype))
|
|
dtype = dtype_timestamp;
|
|
else if (DTYPE_IS_TEXT(desc2.dsc_dtype))
|
|
dtype = dtype_timestamp;
|
|
else if (desc1.dsc_dtype == desc2.dsc_dtype)
|
|
dtype = desc1.dsc_dtype;
|
|
else if (desc1.dsc_dtype == dtype_timestamp &&
|
|
desc2.dsc_dtype == dtype_sql_date)
|
|
{
|
|
dtype = dtype_timestamp;
|
|
}
|
|
else if (desc2.dsc_dtype == dtype_timestamp &&
|
|
desc1.dsc_dtype == dtype_sql_date)
|
|
{
|
|
dtype = dtype_timestamp;
|
|
}
|
|
else
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_datetime_subtract));
|
|
}
|
|
|
|
if (dtype == dtype_sql_date)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = 0;
|
|
}
|
|
else if (dtype == dtype_sql_time)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = ISC_TIME_SECONDS_PRECISION_SCALE;
|
|
desc->dsc_sub_type = dsc_num_type_numeric;
|
|
}
|
|
else
|
|
{
|
|
fb_assert(dtype == dtype_timestamp);
|
|
desc->dsc_dtype = dtype_double;
|
|
desc->dsc_length = sizeof(double);
|
|
desc->dsc_scale = 0;
|
|
}
|
|
}
|
|
else if (isDateAndTime(desc1, desc2))
|
|
{
|
|
// <date> + <time>
|
|
// <time> + <date>
|
|
desc->dsc_dtype = dtype_timestamp;
|
|
desc->dsc_length = type_lengths[dtype_timestamp];
|
|
desc->dsc_scale = 0;
|
|
}
|
|
else
|
|
{
|
|
// <date> + <date>
|
|
// <time> + <time>
|
|
// CVC: Hard to see it, since we are in dialect 1.
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_dateortime_add));
|
|
}
|
|
}
|
|
else if (DTYPE_IS_DATE(desc1.dsc_dtype) || blrOp == blr_add)
|
|
{
|
|
// <date> +/- <non-date>
|
|
// <non-date> + <date>
|
|
desc->dsc_dtype = desc1.dsc_dtype;
|
|
if (!DTYPE_IS_DATE(desc->dsc_dtype))
|
|
desc->dsc_dtype = desc2.dsc_dtype;
|
|
fb_assert(DTYPE_IS_DATE(desc->dsc_dtype));
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
}
|
|
else
|
|
{
|
|
// <non-date> - <date>
|
|
fb_assert(blrOp == blr_subtract);
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_type_minus_date));
|
|
}
|
|
break;
|
|
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
case dtype_double:
|
|
case dtype_real:
|
|
desc->dsc_dtype = dtype_double;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_length = sizeof(double);
|
|
break;
|
|
|
|
default:
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = MIN(NUMERIC_SCALE(desc1), NUMERIC_SCALE(desc2));
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case blr_multiply:
|
|
// Arrays and blobs can never partipate in multiplication
|
|
if (DTYPE_IS_BLOB(desc1.dsc_dtype) || DTYPE_IS_BLOB(desc2.dsc_dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_no_blob_array));
|
|
}
|
|
|
|
dtype = DSC_multiply_blr4_result[desc1.dsc_dtype][desc2.dsc_dtype];
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_double:
|
|
desc->dsc_dtype = dtype_double;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_length = sizeof(double);
|
|
break;
|
|
|
|
case dtype_long:
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = NUMERIC_SCALE(desc1) + NUMERIC_SCALE(desc2);
|
|
break;
|
|
|
|
default:
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_type_multip_dial1));
|
|
}
|
|
|
|
break;
|
|
|
|
case blr_divide:
|
|
// Arrays and blobs can never partipate in division
|
|
if (DTYPE_IS_BLOB(desc1.dsc_dtype) || DTYPE_IS_BLOB(desc2.dsc_dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_no_blob_array));
|
|
}
|
|
|
|
dtype1 = desc1.dsc_dtype;
|
|
if (dtype_int64 == dtype1 || DTYPE_IS_TEXT(dtype1))
|
|
dtype1 = dtype_double;
|
|
|
|
dtype2 = desc2.dsc_dtype;
|
|
if (dtype_int64 == dtype2 || DTYPE_IS_TEXT(dtype2))
|
|
dtype2 = dtype_double;
|
|
|
|
dtype = MAX(dtype1, dtype2);
|
|
|
|
if (!DTYPE_IS_NUMERIC(dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_mustuse_numeric_div_dial1));
|
|
}
|
|
|
|
desc->dsc_dtype = dtype_double;
|
|
desc->dsc_length = sizeof(double);
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArithmeticNode::makeDialect3(dsc* desc, dsc& desc1, dsc& desc2)
|
|
{
|
|
USHORT dtype, dtype1, dtype2;
|
|
|
|
switch (blrOp)
|
|
{
|
|
case blr_add:
|
|
case blr_subtract:
|
|
dtype1 = desc1.dsc_dtype;
|
|
dtype2 = desc2.dsc_dtype;
|
|
|
|
// Arrays and blobs can never partipate in addition/subtraction
|
|
if (DTYPE_IS_BLOB(dtype1) || DTYPE_IS_BLOB(dtype2))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_no_blob_array));
|
|
}
|
|
|
|
// In Dialect 2 or 3, strings can never partipate in addition / sub
|
|
// (use a specific cast instead)
|
|
if (DTYPE_IS_TEXT(dtype1) || DTYPE_IS_TEXT(dtype2))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_nostring_addsub_dial3));
|
|
}
|
|
|
|
// Determine the TYPE of arithmetic to perform, store it
|
|
// in dtype. Note: this is different from the result of
|
|
// the operation, as <timestamp>-<timestamp> uses
|
|
// <timestamp> arithmetic, but returns a <double>
|
|
if (DTYPE_IS_EXACT(dtype1) && DTYPE_IS_EXACT(dtype2))
|
|
dtype = dtype_int64;
|
|
else if (DTYPE_IS_NUMERIC(dtype1) && DTYPE_IS_NUMERIC(dtype2))
|
|
{
|
|
fb_assert(DTYPE_IS_APPROX(dtype1) || DTYPE_IS_APPROX(dtype2));
|
|
dtype = dtype_double;
|
|
}
|
|
else
|
|
{
|
|
// mixed numeric and non-numeric:
|
|
|
|
// The MAX(dtype) rule doesn't apply with dtype_int64
|
|
|
|
if (dtype_int64 == dtype1)
|
|
dtype1 = dtype_double;
|
|
if (dtype_int64 == dtype2)
|
|
dtype2 = dtype_double;
|
|
|
|
dtype = MAX(dtype1, dtype2);
|
|
}
|
|
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_sql_time:
|
|
case dtype_sql_date:
|
|
case dtype_timestamp:
|
|
if ((DTYPE_IS_DATE(dtype1) || dtype1 == dtype_unknown) &&
|
|
(DTYPE_IS_DATE(dtype2) || dtype2 == dtype_unknown))
|
|
{
|
|
if (blrOp == blr_subtract)
|
|
{
|
|
// <any date> - <any date>
|
|
// Legal permutations are:
|
|
// <timestamp> - <timestamp>
|
|
// <timestamp> - <date>
|
|
// <date> - <date>
|
|
// <date> - <timestamp>
|
|
// <time> - <time>
|
|
|
|
if (dtype1 == dtype2)
|
|
dtype = dtype1;
|
|
else if (dtype1 == dtype_timestamp && dtype2 == dtype_sql_date)
|
|
dtype = dtype_timestamp;
|
|
else if (dtype2 == dtype_timestamp && dtype1 == dtype_sql_date)
|
|
dtype = dtype_timestamp;
|
|
else
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_datetime_subtract));
|
|
}
|
|
|
|
if (dtype == dtype_sql_date)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = 0;
|
|
}
|
|
else if (dtype == dtype_sql_time)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = ISC_TIME_SECONDS_PRECISION_SCALE;
|
|
desc->dsc_sub_type = dsc_num_type_numeric;
|
|
}
|
|
else
|
|
{
|
|
fb_assert(dtype == dtype_timestamp);
|
|
desc->dsc_dtype = dtype_int64;
|
|
desc->dsc_length = sizeof(SINT64);
|
|
desc->dsc_scale = -9;
|
|
desc->dsc_sub_type = dsc_num_type_numeric;
|
|
}
|
|
}
|
|
else if (isDateAndTime(desc1, desc2))
|
|
{
|
|
// <date> + <time>
|
|
// <time> + <date>
|
|
desc->dsc_dtype = dtype_timestamp;
|
|
desc->dsc_length = type_lengths[dtype_timestamp];
|
|
desc->dsc_scale = 0;
|
|
}
|
|
else
|
|
{
|
|
// <date> + <date>
|
|
// <time> + <time>
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_dateortime_add));
|
|
}
|
|
}
|
|
else if (DTYPE_IS_DATE(desc1.dsc_dtype) || blrOp == blr_add)
|
|
{
|
|
// <date> +/- <non-date>
|
|
// <non-date> + <date>
|
|
desc->dsc_dtype = desc1.dsc_dtype;
|
|
if (!DTYPE_IS_DATE(desc->dsc_dtype))
|
|
desc->dsc_dtype = desc2.dsc_dtype;
|
|
fb_assert(DTYPE_IS_DATE(desc->dsc_dtype));
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
}
|
|
else
|
|
{
|
|
// <non-date> - <date>
|
|
fb_assert(blrOp == blr_subtract);
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_type_minus_date));
|
|
}
|
|
break;
|
|
|
|
case dtype_varying:
|
|
case dtype_cstring:
|
|
case dtype_text:
|
|
case dtype_double:
|
|
case dtype_real:
|
|
desc->dsc_dtype = dtype_double;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_length = sizeof(double);
|
|
break;
|
|
|
|
case dtype_short:
|
|
case dtype_long:
|
|
case dtype_int64:
|
|
desc->dsc_dtype = dtype_int64;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_length = sizeof(SINT64);
|
|
|
|
// The result type is int64 because both operands are
|
|
// exact numeric: hence we don't need the NUMERIC_SCALE
|
|
// macro here.
|
|
fb_assert(desc1.dsc_dtype == dtype_unknown || DTYPE_IS_EXACT(desc1.dsc_dtype));
|
|
fb_assert(desc2.dsc_dtype == dtype_unknown || DTYPE_IS_EXACT(desc2.dsc_dtype));
|
|
|
|
desc->dsc_scale = MIN(desc1.dsc_scale, desc2.dsc_scale);
|
|
break;
|
|
|
|
default:
|
|
// a type which cannot participate in an add or subtract
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_type_addsub_dial3));
|
|
}
|
|
|
|
break;
|
|
|
|
case blr_multiply:
|
|
// In Dialect 2 or 3, strings can never partipate in multiplication
|
|
// (use a specific cast instead)
|
|
if (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_nostring_multip_dial3));
|
|
}
|
|
|
|
// Arrays and blobs can never partipate in multiplication
|
|
if (DTYPE_IS_BLOB(desc1.dsc_dtype) || DTYPE_IS_BLOB(desc2.dsc_dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_no_blob_array));
|
|
}
|
|
|
|
dtype = DSC_multiply_result[desc1.dsc_dtype][desc2.dsc_dtype];
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_double:
|
|
desc->dsc_dtype = dtype_double;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_length = sizeof(double);
|
|
break;
|
|
|
|
case dtype_int64:
|
|
desc->dsc_dtype = dtype_int64;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_length = sizeof(SINT64);
|
|
desc->dsc_scale = NUMERIC_SCALE(desc1) + NUMERIC_SCALE(desc2);
|
|
break;
|
|
|
|
default:
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_type_multip_dial3));
|
|
}
|
|
|
|
break;
|
|
|
|
case blr_divide:
|
|
// In Dialect 2 or 3, strings can never partipate in division
|
|
// (use a specific cast instead)
|
|
if (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_nostring_div_dial3));
|
|
}
|
|
|
|
// Arrays and blobs can never partipate in division
|
|
if (DTYPE_IS_BLOB(desc1.dsc_dtype) || DTYPE_IS_BLOB(desc2.dsc_dtype))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_no_blob_array));
|
|
}
|
|
|
|
dtype = DSC_multiply_result[desc1.dsc_dtype][desc2.dsc_dtype];
|
|
desc->dsc_dtype = static_cast<UCHAR>(dtype);
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_int64:
|
|
desc->dsc_length = sizeof(SINT64);
|
|
desc->dsc_scale = NUMERIC_SCALE(desc1) + NUMERIC_SCALE(desc2);
|
|
break;
|
|
|
|
case dtype_double:
|
|
desc->dsc_length = sizeof(double);
|
|
desc->dsc_scale = 0;
|
|
break;
|
|
|
|
default:
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_type_div_dial3));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArithmeticNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
dsc desc1, desc2;
|
|
|
|
arg1->getDesc(tdbb, csb, &desc1);
|
|
arg2->getDesc(tdbb, csb, &desc2);
|
|
|
|
if (desc1.isNull())
|
|
{
|
|
desc1 = desc2;
|
|
desc1.setNull();
|
|
}
|
|
|
|
if (desc2.isNull())
|
|
{
|
|
desc2 = desc1;
|
|
desc2.setNull();
|
|
}
|
|
|
|
if (dialect1)
|
|
getDescDialect1(tdbb, desc, desc1, desc2);
|
|
else
|
|
getDescDialect3(tdbb, desc, desc1, desc2);
|
|
}
|
|
|
|
void ArithmeticNode::getDescDialect1(thread_db* /*tdbb*/, dsc* desc, dsc& desc1, dsc& desc2)
|
|
{
|
|
USHORT dtype = 0;
|
|
|
|
switch (blrOp)
|
|
{
|
|
case blr_add:
|
|
case blr_subtract:
|
|
{
|
|
/* 92/05/29 DAVES - don't understand why this is done for ONLY
|
|
dtype_text (eg: not dtype_cstring or dtype_varying) Doesn't
|
|
appear to hurt.
|
|
|
|
94/04/04 DAVES - NOW I understand it! QLI will pass floating
|
|
point values to the engine as text. All other numeric constants
|
|
it turns into either integers or longs (with scale). */
|
|
|
|
USHORT dtype1 = desc1.dsc_dtype;
|
|
if (dtype_int64 == dtype1)
|
|
dtype1 = dtype_double;
|
|
|
|
USHORT dtype2 = desc2.dsc_dtype;
|
|
if (dtype_int64 == dtype2)
|
|
dtype2 = dtype_double;
|
|
|
|
if (dtype1 == dtype_text || dtype2 == dtype_text)
|
|
dtype = MAX(MAX(dtype1, dtype2), (UCHAR) DEFAULT_DOUBLE);
|
|
else
|
|
dtype = MAX(dtype1, dtype2);
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_short:
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
if (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype))
|
|
desc->dsc_scale = 0;
|
|
else
|
|
desc->dsc_scale = MIN(desc1.dsc_scale, desc2.dsc_scale);
|
|
|
|
nodScale = desc->dsc_scale;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
if (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype))
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
// fall into
|
|
|
|
case dtype_timestamp:
|
|
nodFlags |= FLAG_DATE;
|
|
|
|
fb_assert(DTYPE_IS_DATE(desc1.dsc_dtype) || DTYPE_IS_DATE(desc2.dsc_dtype));
|
|
|
|
if (couldBeDate(desc1) && couldBeDate(desc2))
|
|
{
|
|
if (blrOp == blr_subtract)
|
|
{
|
|
// <any date> - <any date>
|
|
|
|
/* Legal permutations are:
|
|
<timestamp> - <timestamp>
|
|
<timestamp> - <date>
|
|
<date> - <date>
|
|
<date> - <timestamp>
|
|
<time> - <time>
|
|
<timestamp> - <string>
|
|
<string> - <timestamp>
|
|
<string> - <string> */
|
|
|
|
if (DTYPE_IS_TEXT(dtype1))
|
|
dtype = dtype_timestamp;
|
|
else if (DTYPE_IS_TEXT(dtype2))
|
|
dtype = dtype_timestamp;
|
|
else if (dtype1 == dtype2)
|
|
dtype = dtype1;
|
|
else if (dtype1 == dtype_timestamp && dtype2 == dtype_sql_date)
|
|
dtype = dtype_timestamp;
|
|
else if (dtype2 == dtype_timestamp && dtype1 == dtype_sql_date)
|
|
dtype = dtype_timestamp;
|
|
else
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
|
|
if (dtype == dtype_sql_date)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else if (dtype == dtype_sql_time)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = ISC_TIME_SECONDS_PRECISION_SCALE;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else
|
|
{
|
|
fb_assert(dtype == dtype_timestamp);
|
|
desc->dsc_dtype = DEFAULT_DOUBLE;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
}
|
|
else if (isDateAndTime(desc1, desc2))
|
|
{
|
|
// <date> + <time>
|
|
// <time> + <date>
|
|
desc->dsc_dtype = dtype_timestamp;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else
|
|
{
|
|
// <date> + <date>
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
}
|
|
}
|
|
else if (DTYPE_IS_DATE(desc1.dsc_dtype) || blrOp == blr_add)
|
|
{
|
|
// <date> +/- <non-date> || <non-date> + <date>
|
|
desc->dsc_dtype = desc1.dsc_dtype;
|
|
if (!DTYPE_IS_DATE(desc->dsc_dtype))
|
|
desc->dsc_dtype = desc2.dsc_dtype;
|
|
|
|
fb_assert(DTYPE_IS_DATE(desc->dsc_dtype));
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else
|
|
{
|
|
// <non-date> - <date>
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
}
|
|
return;
|
|
|
|
case dtype_text:
|
|
case dtype_cstring:
|
|
case dtype_varying:
|
|
case dtype_long:
|
|
case dtype_real:
|
|
case dtype_double:
|
|
nodFlags |= FLAG_DOUBLE;
|
|
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 dtype_unknown:
|
|
desc->dsc_dtype = dtype_unknown;
|
|
desc->dsc_length = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
case dtype_quad:
|
|
case dtype_blob:
|
|
case dtype_array:
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case blr_multiply:
|
|
dtype = DSC_multiply_blr4_result[desc1.dsc_dtype][desc2.dsc_dtype];
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_long:
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = nodScale = NUMERIC_SCALE(desc1) + NUMERIC_SCALE(desc2);
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
case dtype_double:
|
|
nodFlags |= FLAG_DOUBLE;
|
|
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 dtype_unknown:
|
|
desc->dsc_dtype = dtype_unknown;
|
|
desc->dsc_length = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
// FALLINTO
|
|
|
|
case DTYPE_CANNOT:
|
|
// break to error reporting code
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case blr_divide:
|
|
// for compatibility with older versions of the product, we accept
|
|
// text types for division in blr_version4 (dialect <= 1) only
|
|
if (!(DTYPE_IS_NUMERIC(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc1.dsc_dtype)))
|
|
{
|
|
if (desc1.dsc_dtype != dtype_unknown)
|
|
break; // error, dtype not supported by arithmetic
|
|
}
|
|
|
|
if (!(DTYPE_IS_NUMERIC(desc2.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype)))
|
|
{
|
|
if (desc2.dsc_dtype != dtype_unknown)
|
|
break; // error, dtype not supported by arithmetic
|
|
}
|
|
|
|
desc->dsc_dtype = DEFAULT_DOUBLE;
|
|
desc->dsc_length = sizeof(double);
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
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)
|
|
{
|
|
USHORT dtype;
|
|
|
|
switch (blrOp)
|
|
{
|
|
case blr_add:
|
|
case blr_subtract:
|
|
{
|
|
USHORT dtype1 = desc1.dsc_dtype;
|
|
USHORT dtype2 = desc2.dsc_dtype;
|
|
|
|
// In Dialect 2 or 3, strings can never partipate in addition / sub
|
|
// (use a specific cast instead)
|
|
if (DTYPE_IS_TEXT(dtype1) || DTYPE_IS_TEXT(dtype2))
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
|
|
// Because dtype_int64 > dtype_double, we cannot just use the MAX macro to set
|
|
// the result dtype. The rule is that two exact numeric operands yield an int64
|
|
// result, while an approximate numeric and anything yield a double result.
|
|
|
|
if (DTYPE_IS_EXACT(desc1.dsc_dtype) && DTYPE_IS_EXACT(desc2.dsc_dtype))
|
|
dtype = dtype_int64;
|
|
else if (DTYPE_IS_NUMERIC(desc1.dsc_dtype) && DTYPE_IS_NUMERIC(desc2.dsc_dtype))
|
|
dtype = dtype_double;
|
|
else
|
|
{
|
|
// mixed numeric and non-numeric:
|
|
|
|
fb_assert(couldBeDate(desc1) || couldBeDate(desc2));
|
|
|
|
// the MAX(dtype) rule doesn't apply with dtype_int64
|
|
|
|
if (dtype_int64 == dtype1)
|
|
dtype1 = dtype_double;
|
|
|
|
if (dtype_int64 == dtype2)
|
|
dtype2 = dtype_double;
|
|
|
|
dtype = MAX(dtype1, dtype2);
|
|
}
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_timestamp:
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
nodFlags |= FLAG_DATE;
|
|
|
|
fb_assert(DTYPE_IS_DATE(desc1.dsc_dtype) || DTYPE_IS_DATE(desc2.dsc_dtype));
|
|
|
|
if ((DTYPE_IS_DATE(dtype1) || dtype1 == dtype_unknown) &&
|
|
(DTYPE_IS_DATE(dtype2) || dtype2 == dtype_unknown))
|
|
{
|
|
if (blrOp == blr_subtract)
|
|
{
|
|
// <any date> - <any date>
|
|
|
|
/* Legal permutations are:
|
|
<timestamp> - <timestamp>
|
|
<timestamp> - <date>
|
|
<date> - <date>
|
|
<date> - <timestamp>
|
|
<time> - <time> */
|
|
|
|
if (dtype1 == dtype_unknown)
|
|
dtype1 = dtype2;
|
|
else if (dtype2 == dtype_unknown)
|
|
dtype2 = dtype1;
|
|
|
|
if (dtype1 == dtype2)
|
|
dtype = dtype1;
|
|
else if ((dtype1 == dtype_timestamp) && (dtype2 == dtype_sql_date))
|
|
dtype = dtype_timestamp;
|
|
else if ((dtype2 == dtype_timestamp) && (dtype1 == dtype_sql_date))
|
|
dtype = dtype_timestamp;
|
|
else
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
|
|
if (dtype == dtype_sql_date)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else if (dtype == dtype_sql_time)
|
|
{
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = ISC_TIME_SECONDS_PRECISION_SCALE;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else
|
|
{
|
|
fb_assert(dtype == dtype_timestamp || dtype == dtype_unknown);
|
|
desc->dsc_dtype = DEFAULT_DOUBLE;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
}
|
|
else if (isDateAndTime(desc1, desc2))
|
|
{
|
|
// <date> + <time>
|
|
// <time> + <date>
|
|
desc->dsc_dtype = dtype_timestamp;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else
|
|
{
|
|
// <date> + <date>
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
}
|
|
}
|
|
else if (DTYPE_IS_DATE(desc1.dsc_dtype) || blrOp == blr_add)
|
|
{
|
|
// <date> +/- <non-date> || <non-date> + <date>
|
|
desc->dsc_dtype = desc1.dsc_dtype;
|
|
if (!DTYPE_IS_DATE(desc->dsc_dtype))
|
|
desc->dsc_dtype = desc2.dsc_dtype;
|
|
fb_assert(DTYPE_IS_DATE(desc->dsc_dtype));
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
else
|
|
{
|
|
// <non-date> - <date>
|
|
ERR_post(Arg::Gds(isc_expression_eval_err));
|
|
}
|
|
return;
|
|
|
|
case dtype_text:
|
|
case dtype_cstring:
|
|
case dtype_varying:
|
|
case dtype_real:
|
|
case dtype_double:
|
|
nodFlags |= FLAG_DOUBLE;
|
|
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 dtype_short:
|
|
case dtype_long:
|
|
case dtype_int64:
|
|
desc->dsc_dtype = dtype_int64;
|
|
desc->dsc_length = sizeof(SINT64);
|
|
if (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype))
|
|
desc->dsc_scale = 0;
|
|
else
|
|
desc->dsc_scale = MIN(desc1.dsc_scale, desc2.dsc_scale);
|
|
nodScale = desc->dsc_scale;
|
|
desc->dsc_sub_type = MAX(desc1.dsc_sub_type, desc2.dsc_sub_type);
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
case dtype_unknown:
|
|
desc->dsc_dtype = dtype_unknown;
|
|
desc->dsc_length = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
case dtype_quad:
|
|
case dtype_blob:
|
|
case dtype_array:
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case blr_multiply:
|
|
case blr_divide:
|
|
dtype = DSC_multiply_result[desc1.dsc_dtype][desc2.dsc_dtype];
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_double:
|
|
nodFlags |= FLAG_DOUBLE;
|
|
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 dtype_int64:
|
|
desc->dsc_dtype = dtype_int64;
|
|
desc->dsc_length = sizeof(SINT64);
|
|
desc->dsc_scale = nodScale = NUMERIC_SCALE(desc1) + NUMERIC_SCALE(desc2);
|
|
desc->dsc_sub_type = MAX(desc1.dsc_sub_type, desc2.dsc_sub_type);
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
case dtype_unknown:
|
|
desc->dsc_dtype = dtype_unknown;
|
|
desc->dsc_length = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_flags = 0;
|
|
return;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
// FALLINTO
|
|
|
|
case DTYPE_CANNOT:
|
|
// break to error reporting code
|
|
break;
|
|
}
|
|
|
|
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) const
|
|
{
|
|
ArithmeticNode* node = FB_NEW(*tdbb->getDefaultPool()) ArithmeticNode(*tdbb->getDefaultPool(),
|
|
blrOp, dialect1);
|
|
node->nodScale = nodScale;
|
|
node->arg1 = copier.copy(tdbb, arg1);
|
|
node->arg2 = copier.copy(tdbb, arg2);
|
|
return node;
|
|
}
|
|
|
|
bool ArithmeticNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const ArithmeticNode* o = other->as<ArithmeticNode>();
|
|
fb_assert(o);
|
|
|
|
return dialect1 == o->dialect1 && blrOp == o->blrOp;
|
|
}
|
|
|
|
bool ArithmeticNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
const ArithmeticNode* const otherNode = other->as<ArithmeticNode>();
|
|
|
|
if (!otherNode || blrOp != otherNode->blrOp || dialect1 != otherNode->dialect1)
|
|
return false;
|
|
|
|
if (arg1->sameAs(otherNode->arg1, ignoreStreams) &&
|
|
arg2->sameAs(otherNode->arg2, ignoreStreams))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (blrOp == blr_add || blrOp == blr_multiply)
|
|
{
|
|
// A + B is equivalent to B + A, ditto for A * B and B * A.
|
|
// Note: If one expression is A + B + C, but the other is B + C + A we won't
|
|
// necessarily match them.
|
|
if (arg1->sameAs(otherNode->arg2, ignoreStreams) &&
|
|
arg2->sameAs(otherNode->arg1, ignoreStreams))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ValueExprNode* ArithmeticNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* ArithmeticNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
|
|
request->req_flags &= ~req_null;
|
|
|
|
// Evaluate arguments. If either is null, result is null, but in
|
|
// any case, evaluate both, since some expressions may later depend
|
|
// on mappings which are developed here
|
|
|
|
const dsc* desc1 = EVL_expr(tdbb, request, arg1);
|
|
const ULONG flags = request->req_flags;
|
|
request->req_flags &= ~req_null;
|
|
|
|
const dsc* desc2 = EVL_expr(tdbb, request, arg2);
|
|
|
|
// restore saved NULL state
|
|
|
|
if (flags & req_null)
|
|
request->req_flags |= req_null;
|
|
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
EVL_make_value(tdbb, desc1, impure);
|
|
|
|
if (dialect1) // dialect-1 semantics
|
|
{
|
|
switch (blrOp)
|
|
{
|
|
case blr_add:
|
|
case blr_subtract:
|
|
return add(desc2, impure, this, blrOp);
|
|
|
|
case blr_divide:
|
|
{
|
|
const double divisor = MOV_get_double(desc2);
|
|
|
|
if (divisor == 0)
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_exception_float_divide_by_zero));
|
|
}
|
|
|
|
impure->vlu_misc.vlu_double = MOV_get_double(desc1) / divisor;
|
|
|
|
if (isinf(impure->vlu_misc.vlu_double))
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_exception_float_overflow));
|
|
}
|
|
|
|
impure->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
|
|
impure->vlu_desc.dsc_length = sizeof(double);
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
case blr_multiply:
|
|
return multiply(desc2, impure);
|
|
}
|
|
}
|
|
else // with dialect-3 semantics
|
|
{
|
|
switch (blrOp)
|
|
{
|
|
case blr_add:
|
|
case blr_subtract:
|
|
return add2(desc2, impure, this, blrOp);
|
|
|
|
case blr_multiply:
|
|
return multiply2(desc2, impure);
|
|
|
|
case blr_divide:
|
|
return divide2(desc2, impure);
|
|
}
|
|
}
|
|
|
|
BUGCHECK(232); // msg 232 EVL_expr: invalid operation
|
|
return NULL;
|
|
}
|
|
|
|
// Add (or subtract) the contents of a descriptor to value block, with dialect-1 semantics.
|
|
// This function can be removed when dialect-3 becomes the lowest supported dialect. (Version 7.0?)
|
|
dsc* ArithmeticNode::add(const dsc* desc, impure_value* value, const ValueExprNode* node, const UCHAR blrOp)
|
|
{
|
|
const ArithmeticNode* arithmeticNode = node->as<ArithmeticNode>();
|
|
|
|
#ifdef DEV_BUILD
|
|
const SubQueryNode* subQueryNode = node->as<SubQueryNode>();
|
|
fb_assert(
|
|
(arithmeticNode && arithmeticNode->dialect1 &&
|
|
(arithmeticNode->blrOp == blr_add || arithmeticNode->blrOp == blr_subtract)) ||
|
|
node->is<AggNode>() ||
|
|
(subQueryNode && (subQueryNode->blrOp == blr_total || subQueryNode->blrOp == blr_average)));
|
|
#endif
|
|
|
|
dsc* const result = &value->vlu_desc;
|
|
|
|
// Handle date arithmetic
|
|
|
|
if (node->nodFlags & FLAG_DATE)
|
|
{
|
|
fb_assert(arithmeticNode);
|
|
return arithmeticNode->addDateTime(desc, value);
|
|
}
|
|
|
|
// Handle floating arithmetic
|
|
|
|
if (node->nodFlags & FLAG_DOUBLE)
|
|
{
|
|
const double d1 = MOV_get_double(desc);
|
|
const double d2 = MOV_get_double(&value->vlu_desc);
|
|
|
|
value->vlu_misc.vlu_double = (blrOp == blr_subtract) ? d2 - d1 : d1 + d2;
|
|
|
|
if (isinf(value->vlu_misc.vlu_double))
|
|
ERR_post(Arg::Gds(isc_arith_except) << Arg::Gds(isc_exception_float_overflow));
|
|
|
|
result->dsc_dtype = DEFAULT_DOUBLE;
|
|
result->dsc_length = sizeof(double);
|
|
result->dsc_scale = 0;
|
|
result->dsc_sub_type = 0;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_double;
|
|
|
|
return result;
|
|
}
|
|
|
|
// Everything else defaults to longword
|
|
|
|
// CVC: Maybe we should upgrade the sum to double if it doesn't fit?
|
|
// This is what was done for multiplicaton in dialect 1.
|
|
|
|
const SLONG l1 = MOV_get_long(desc, node->nodScale);
|
|
const SINT64 l2 = MOV_get_long(&value->vlu_desc, node->nodScale);
|
|
const SINT64 rc = (blrOp == blr_subtract) ? l2 - l1 : l2 + l1;
|
|
|
|
if (rc < MIN_SLONG || rc > MAX_SLONG)
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
|
|
value->make_long(rc, node->nodScale);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Add (or subtract) the contents of a descriptor to value block, with dialect-3 semantics, as in
|
|
// the blr_add, blr_subtract, and blr_agg_total verbs following a blr_version5.
|
|
dsc* ArithmeticNode::add2(const dsc* desc, impure_value* value, const ValueExprNode* node, const UCHAR blrOp)
|
|
{
|
|
const ArithmeticNode* arithmeticNode = node->as<ArithmeticNode>();
|
|
|
|
fb_assert(
|
|
(arithmeticNode && !arithmeticNode->dialect1 &&
|
|
(arithmeticNode->blrOp == blr_add || arithmeticNode->blrOp == blr_subtract)) ||
|
|
node->is<AggNode>());
|
|
|
|
dsc* result = &value->vlu_desc;
|
|
|
|
// Handle date arithmetic
|
|
|
|
if (node->nodFlags & FLAG_DATE)
|
|
{
|
|
fb_assert(arithmeticNode);
|
|
return arithmeticNode->addDateTime(desc, value);
|
|
}
|
|
|
|
// Handle floating arithmetic
|
|
|
|
if (node->nodFlags & FLAG_DOUBLE)
|
|
{
|
|
const double d1 = MOV_get_double(desc);
|
|
const double d2 = MOV_get_double(&value->vlu_desc);
|
|
|
|
value->vlu_misc.vlu_double = (blrOp == blr_subtract) ? d2 - d1 : d1 + d2;
|
|
|
|
if (isinf(value->vlu_misc.vlu_double))
|
|
ERR_post(Arg::Gds(isc_arith_except) << Arg::Gds(isc_exception_float_overflow));
|
|
|
|
result->dsc_dtype = DEFAULT_DOUBLE;
|
|
result->dsc_length = sizeof(double);
|
|
result->dsc_scale = 0;
|
|
result->dsc_sub_type = 0;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_double;
|
|
|
|
return result;
|
|
}
|
|
|
|
// Everything else defaults to int64
|
|
|
|
SINT64 i1 = MOV_get_int64(desc, node->nodScale);
|
|
const SINT64 i2 = MOV_get_int64(&value->vlu_desc, node->nodScale);
|
|
|
|
result->dsc_dtype = dtype_int64;
|
|
result->dsc_length = sizeof(SINT64);
|
|
result->dsc_scale = node->nodScale;
|
|
value->vlu_misc.vlu_int64 = (blrOp == blr_subtract) ? i2 - i1 : i1 + i2;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_int64;
|
|
result->dsc_sub_type = MAX(desc->dsc_sub_type, value->vlu_desc.dsc_sub_type);
|
|
|
|
/* If the operands of an addition have the same sign, and their sum has
|
|
the opposite sign, then overflow occurred. If the two addends have
|
|
opposite signs, then the result will lie between the two addends, and
|
|
overflow cannot occur.
|
|
If this is a subtraction, note that we invert the sign bit, rather than
|
|
negating the argument, so that subtraction of MIN_SINT64, which is
|
|
unchanged by negation, will be correctly treated like the addition of
|
|
a positive number for the purposes of this test.
|
|
|
|
Test cases for a Gedankenexperiment, considering the sign bits of the
|
|
operands and result after the inversion below: L Rt Sum
|
|
|
|
MIN_SINT64 - MIN_SINT64 == 0, with no overflow 1 0 0
|
|
-MAX_SINT64 - MIN_SINT64 == 1, with no overflow 1 0 0
|
|
1 - MIN_SINT64 == overflow 0 0 1
|
|
-1 - MIN_SINT64 == MAX_SINT64, no overflow 1 0 0
|
|
*/
|
|
|
|
if (blrOp == blr_subtract)
|
|
i1 ^= MIN_SINT64; // invert the sign bit
|
|
|
|
if ((i1 ^ i2) >= 0 && (i1 ^ value->vlu_misc.vlu_int64) < 0)
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
|
|
return result;
|
|
}
|
|
|
|
// Multiply two numbers, with SQL dialect-1 semantics.
|
|
// This function can be removed when dialect-3 becomes the lowest supported dialect. (Version 7.0?)
|
|
dsc* ArithmeticNode::multiply(const dsc* desc, impure_value* value) const
|
|
{
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
// Handle floating arithmetic
|
|
|
|
if (nodFlags & FLAG_DOUBLE)
|
|
{
|
|
const double d1 = MOV_get_double(desc);
|
|
const double d2 = MOV_get_double(&value->vlu_desc);
|
|
value->vlu_misc.vlu_double = d1 * d2;
|
|
|
|
if (isinf(value->vlu_misc.vlu_double))
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_exception_float_overflow));
|
|
}
|
|
|
|
value->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
|
|
value->vlu_desc.dsc_length = sizeof(double);
|
|
value->vlu_desc.dsc_scale = 0;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_double;
|
|
|
|
return &value->vlu_desc;
|
|
}
|
|
|
|
// Everything else defaults to longword
|
|
|
|
/* CVC: With so many problems cropping with dialect 1 and multiplication,
|
|
I decided to close this Pandora box by incurring in INT64 performance
|
|
overhead (if noticeable) and try to get the best result. When I read it,
|
|
this function didn't bother even to check for overflow! */
|
|
|
|
#define FIREBIRD_AVOID_DIALECT1_OVERFLOW
|
|
// SLONG l1, l2;
|
|
//{
|
|
const SSHORT scale = NUMERIC_SCALE(value->vlu_desc);
|
|
const SINT64 i1 = MOV_get_long(desc, nodScale - scale);
|
|
const SINT64 i2 = MOV_get_long(&value->vlu_desc, scale);
|
|
value->vlu_desc.dsc_dtype = dtype_long;
|
|
value->vlu_desc.dsc_length = sizeof(SLONG);
|
|
value->vlu_desc.dsc_scale = nodScale;
|
|
const SINT64 rc = i1 * i2;
|
|
if (rc < MIN_SLONG || rc > MAX_SLONG)
|
|
{
|
|
#ifdef FIREBIRD_AVOID_DIALECT1_OVERFLOW
|
|
value->vlu_misc.vlu_int64 = rc;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_int64;
|
|
value->vlu_desc.dsc_dtype = dtype_int64;
|
|
value->vlu_desc.dsc_length = sizeof(SINT64);
|
|
value->vlu_misc.vlu_double = MOV_get_double(&value->vlu_desc);
|
|
/* This is the Borland solution instead of the five lines above.
|
|
d1 = MOV_get_double (desc);
|
|
d2 = MOV_get_double (&value->vlu_desc);
|
|
value->vlu_misc.vlu_double = d1 * d2; */
|
|
value->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
|
|
value->vlu_desc.dsc_length = sizeof(double);
|
|
value->vlu_desc.dsc_scale = 0;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_double;
|
|
#else
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
value->vlu_misc.vlu_long = (SLONG) rc; // l1 * l2;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_long;
|
|
}
|
|
//}
|
|
|
|
return &value->vlu_desc;
|
|
}
|
|
|
|
// Multiply two numbers, with dialect-3 semantics, implementing blr_version5 ... blr_multiply.
|
|
dsc* ArithmeticNode::multiply2(const dsc* desc, impure_value* value) const
|
|
{
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
// Handle floating arithmetic
|
|
|
|
if (nodFlags & FLAG_DOUBLE)
|
|
{
|
|
const double d1 = MOV_get_double(desc);
|
|
const double d2 = MOV_get_double(&value->vlu_desc);
|
|
value->vlu_misc.vlu_double = d1 * d2;
|
|
|
|
if (isinf(value->vlu_misc.vlu_double))
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_exception_float_overflow));
|
|
}
|
|
|
|
value->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
|
|
value->vlu_desc.dsc_length = sizeof(double);
|
|
value->vlu_desc.dsc_scale = 0;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_double;
|
|
|
|
return &value->vlu_desc;
|
|
}
|
|
|
|
// Everything else defaults to int64
|
|
|
|
const SSHORT scale = NUMERIC_SCALE(value->vlu_desc);
|
|
const SINT64 i1 = MOV_get_int64(desc, nodScale - scale);
|
|
const SINT64 i2 = MOV_get_int64(&value->vlu_desc, scale);
|
|
|
|
/*
|
|
We need to report an overflow if
|
|
(i1 * i2 < MIN_SINT64) || (i1 * i2 > MAX_SINT64)
|
|
which is equivalent to
|
|
(i1 < MIN_SINT64 / i2) || (i1 > MAX_SINT64 / i2)
|
|
|
|
Unfortunately, a trial division to see whether the multiplication will
|
|
overflow is expensive: fortunately, we only need perform one division and
|
|
test for one of the two cases, depending on whether the factors have the
|
|
same or opposite signs.
|
|
|
|
Unfortunately, in C it is unspecified which way division rounds
|
|
when one or both arguments are negative. (ldiv() is guaranteed to
|
|
round towards 0, but the standard does not yet require an lldiv()
|
|
or whatever for 64-bit operands. This makes the problem messy.
|
|
We use FB_UINT64s for the checking, thus ensuring that our division rounds
|
|
down. This means that we have to check the sign of the product first
|
|
in order to know whether the maximum abs(i1*i2) is MAX_SINT64 or
|
|
(MAX_SINT64+1).
|
|
|
|
Of course, if a factor is 0, the product will also be 0, and we don't
|
|
need a trial-division to be sure the multiply won't overflow.
|
|
*/
|
|
|
|
const FB_UINT64 u1 = (i1 >= 0) ? i1 : -i1; // abs(i1)
|
|
const FB_UINT64 u2 = (i2 >= 0) ? i2 : -i2; // abs(i2)
|
|
// largest product
|
|
const FB_UINT64 u_limit = ((i1 ^ i2) >= 0) ? MAX_SINT64 : (FB_UINT64) MAX_SINT64 + 1;
|
|
|
|
if ((u1 != 0) && ((u_limit / u1) < u2)) {
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
}
|
|
|
|
value->vlu_desc.dsc_dtype = dtype_int64;
|
|
value->vlu_desc.dsc_length = sizeof(SINT64);
|
|
value->vlu_desc.dsc_scale = nodScale;
|
|
value->vlu_misc.vlu_int64 = i1 * i2;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_int64;
|
|
|
|
return &value->vlu_desc;
|
|
}
|
|
|
|
// Divide two numbers, with SQL dialect-3 semantics, as in the blr_version5 ... blr_divide or
|
|
// blr_version5 ... blr_average ....
|
|
dsc* ArithmeticNode::divide2(const dsc* desc, impure_value* value) const
|
|
{
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
// Handle floating arithmetic
|
|
|
|
if (nodFlags & FLAG_DOUBLE)
|
|
{
|
|
const double d2 = MOV_get_double(desc);
|
|
if (d2 == 0.0)
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_exception_float_divide_by_zero));
|
|
}
|
|
const double d1 = MOV_get_double(&value->vlu_desc);
|
|
value->vlu_misc.vlu_double = d1 / d2;
|
|
if (isinf(value->vlu_misc.vlu_double))
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_exception_float_overflow));
|
|
}
|
|
value->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
|
|
value->vlu_desc.dsc_length = sizeof(double);
|
|
value->vlu_desc.dsc_scale = 0;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_double;
|
|
return &value->vlu_desc;
|
|
}
|
|
|
|
// Everything else defaults to int64
|
|
|
|
/*
|
|
* In the SQL standard, the precision and scale of the quotient of exact
|
|
* numeric dividend and divisor are implementation-defined: we have defined
|
|
* the precision as 18 (in other words, an SINT64), and the scale as the
|
|
* sum of the scales of the two operands. To make this work, we have to
|
|
* multiply by pow(10, -2* (scale of divisor)).
|
|
*
|
|
* To see this, consider the operation n1 / n2, and represent the numbers
|
|
* by ordered pairs (v1, s1) and (v2, s2), representing respectively the
|
|
* integer value and the scale of each operation, so that
|
|
* n1 = v1 * pow(10, s1), and
|
|
* n2 = v2 * pow(10, s2)
|
|
* Then the quotient is ...
|
|
*
|
|
* v1 * pow(10,s1)
|
|
* ----------------- = (v1/v2) * pow(10, s1-s2)
|
|
* v2 * pow(10,s2)
|
|
*
|
|
* But we want the scale of the result to be (s1+s2), not (s1-s2)
|
|
* so we need to multiply by 1 in the form
|
|
* pow(10, -2 * s2) * pow(20, 2 * s2)
|
|
* which, after regrouping, gives us ...
|
|
* = ((v1 * pow(10, -2*s2))/v2) * pow(10, 2*s2) * pow(10, s1-s2)
|
|
* = ((v1 * pow(10, -2*s2))/v2) * pow(10, 2*s2 + s1 - s2)
|
|
* = ((v1 * pow(10, -2*s2))/v2) * pow(10, s1 + s2)
|
|
* or in our ordered-pair notation,
|
|
* ( v1 * pow(10, -2*s2) / v2, s1 + s2 )
|
|
*
|
|
* To maximize the amount of information in the result, we scale up
|
|
* the dividend as far as we can without causing overflow, then we perform
|
|
* the division, then do any additional required scaling.
|
|
*
|
|
* Who'da thunk that 9th-grade algebra would prove so useful.
|
|
* -- Chris Jewell, December 1998
|
|
*/
|
|
SINT64 i2 = MOV_get_int64(desc, desc->dsc_scale);
|
|
if (i2 == 0)
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_exception_integer_divide_by_zero));
|
|
}
|
|
|
|
SINT64 i1 = MOV_get_int64(&value->vlu_desc, nodScale - desc->dsc_scale);
|
|
|
|
// MIN_SINT64 / -1 = (MAX_SINT64 + 1), which overflows in SINT64.
|
|
if ((i1 == MIN_SINT64) && (i2 == -1))
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
|
|
// Scale the dividend by as many of the needed powers of 10 as possible
|
|
// without causing an overflow.
|
|
int addl_scale = 2 * desc->dsc_scale;
|
|
if (i1 >= 0)
|
|
{
|
|
while ((addl_scale < 0) && (i1 <= MAX_INT64_LIMIT))
|
|
{
|
|
i1 *= 10;
|
|
++addl_scale;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ((addl_scale < 0) && (i1 >= MIN_INT64_LIMIT))
|
|
{
|
|
i1 *= 10;
|
|
++addl_scale;
|
|
}
|
|
}
|
|
|
|
// If we couldn't use up all the additional scaling by multiplying the
|
|
// dividend by 10, but there are trailing zeroes in the divisor, we can
|
|
// get the same effect by dividing the divisor by 10 instead.
|
|
while ((addl_scale < 0) && (0 == (i2 % 10)))
|
|
{
|
|
i2 /= 10;
|
|
++addl_scale;
|
|
}
|
|
|
|
value->vlu_desc.dsc_dtype = dtype_int64;
|
|
value->vlu_desc.dsc_length = sizeof(SINT64);
|
|
value->vlu_desc.dsc_scale = nodScale;
|
|
value->vlu_misc.vlu_int64 = i1 / i2;
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc.vlu_int64;
|
|
|
|
// If we couldn't do all the required scaling beforehand without causing
|
|
// an overflow, do the rest of it now. If we get an overflow now, then
|
|
// the result is really too big to store in a properly-scaled SINT64,
|
|
// so report the error. For example, MAX_SINT64 / 1.00 overflows.
|
|
if (value->vlu_misc.vlu_int64 >= 0)
|
|
{
|
|
while ((addl_scale < 0) && (value->vlu_misc.vlu_int64 <= MAX_INT64_LIMIT))
|
|
{
|
|
value->vlu_misc.vlu_int64 *= 10;
|
|
addl_scale++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ((addl_scale < 0) && (value->vlu_misc.vlu_int64 >= MIN_INT64_LIMIT))
|
|
{
|
|
value->vlu_misc.vlu_int64 *= 10;
|
|
addl_scale++;
|
|
}
|
|
}
|
|
|
|
if (addl_scale < 0)
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_numeric_out_of_range));
|
|
}
|
|
|
|
return &value->vlu_desc;
|
|
}
|
|
|
|
// Vector out to one of the actual datetime addition routines.
|
|
dsc* ArithmeticNode::addDateTime(const dsc* desc, impure_value* value) const
|
|
{
|
|
BYTE dtype; // Which addition routine to use?
|
|
|
|
fb_assert(nodFlags & FLAG_DATE);
|
|
|
|
// Value is the LHS of the operand. desc is the RHS
|
|
|
|
if (blrOp == blr_add)
|
|
dtype = DSC_add_result[value->vlu_desc.dsc_dtype][desc->dsc_dtype];
|
|
else
|
|
{
|
|
fb_assert(blrOp == blr_subtract);
|
|
dtype = DSC_sub_result[value->vlu_desc.dsc_dtype][desc->dsc_dtype];
|
|
|
|
/* Is this a <date type> - <date type> construct?
|
|
chose the proper routine to do the subtract from the
|
|
LHS of expression
|
|
Thus: <TIME> - <TIMESTAMP> uses TIME arithmetic
|
|
<DATE> - <TIMESTAMP> uses DATE arithmetic
|
|
<TIMESTAMP> - <DATE> uses TIMESTAMP arithmetic */
|
|
if (DTYPE_IS_NUMERIC(dtype))
|
|
dtype = value->vlu_desc.dsc_dtype;
|
|
|
|
// Handle historical <timestamp> = <string> - <value> case
|
|
if (!DTYPE_IS_DATE(dtype) &&
|
|
(DTYPE_IS_TEXT(value->vlu_desc.dsc_dtype) || DTYPE_IS_TEXT(desc->dsc_dtype)))
|
|
{
|
|
dtype = dtype_timestamp;
|
|
}
|
|
}
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_sql_time:
|
|
return addSqlTime(desc, value);
|
|
|
|
case dtype_sql_date:
|
|
return addSqlDate(desc, value);
|
|
|
|
case DTYPE_CANNOT:
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_invalid_type_datetime_op));
|
|
break;
|
|
|
|
case dtype_timestamp:
|
|
default:
|
|
// This needs to handle a dtype_sql_date + dtype_sql_time
|
|
// For historical reasons prior to V6 - handle any types for timestamp arithmetic
|
|
return addTimeStamp(desc, value);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Perform date arithmetic.
|
|
// DATE - DATE Result is SLONG
|
|
// DATE +/- NUMERIC Numeric is interpreted as days DECIMAL(*,0).
|
|
// NUMERIC +/- TIME Numeric is interpreted as days DECIMAL(*,0).
|
|
dsc* ArithmeticNode::addSqlDate(const dsc* desc, impure_value* value) const
|
|
{
|
|
DEV_BLKCHK(node, type_nod);
|
|
fb_assert(blrOp == blr_add || blrOp == blr_subtract);
|
|
|
|
dsc* result = &value->vlu_desc;
|
|
|
|
fb_assert(value->vlu_desc.dsc_dtype == dtype_sql_date || desc->dsc_dtype == dtype_sql_date);
|
|
|
|
SINT64 d1;
|
|
// Coerce operand1 to a count of days
|
|
bool op1_is_date = false;
|
|
if (value->vlu_desc.dsc_dtype == dtype_sql_date)
|
|
{
|
|
d1 = *((GDS_DATE*) value->vlu_desc.dsc_address);
|
|
op1_is_date = true;
|
|
}
|
|
else
|
|
d1 = MOV_get_int64(&value->vlu_desc, 0);
|
|
|
|
SINT64 d2;
|
|
// Coerce operand2 to a count of days
|
|
bool op2_is_date = false;
|
|
if (desc->dsc_dtype == dtype_sql_date)
|
|
{
|
|
d2 = *((GDS_DATE*) desc->dsc_address);
|
|
op2_is_date = true;
|
|
}
|
|
else
|
|
d2 = MOV_get_int64(desc, 0);
|
|
|
|
if (blrOp == blr_subtract && op1_is_date && op2_is_date)
|
|
{
|
|
d2 = d1 - d2;
|
|
value->make_int64(d2);
|
|
return result;
|
|
}
|
|
|
|
fb_assert(op1_is_date || op2_is_date);
|
|
fb_assert(!(op1_is_date && op2_is_date));
|
|
|
|
// Perform the operation
|
|
|
|
if (blrOp == blr_subtract)
|
|
{
|
|
fb_assert(op1_is_date);
|
|
d2 = d1 - d2;
|
|
}
|
|
else
|
|
d2 = d1 + d2;
|
|
|
|
value->vlu_misc.vlu_sql_date = d2;
|
|
|
|
if (!TimeStamp::isValidDate(value->vlu_misc.vlu_sql_date))
|
|
ERR_post(Arg::Gds(isc_date_range_exceeded));
|
|
|
|
result->dsc_dtype = dtype_sql_date;
|
|
result->dsc_length = type_lengths[result->dsc_dtype];
|
|
result->dsc_scale = 0;
|
|
result->dsc_sub_type = 0;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_sql_date;
|
|
return result;
|
|
|
|
}
|
|
|
|
// Perform time arithmetic.
|
|
// TIME - TIME Result is SLONG, scale -4
|
|
// TIME +/- NUMERIC Numeric is interpreted as seconds DECIMAL(*,4).
|
|
// NUMERIC +/- TIME Numeric is interpreted as seconds DECIMAL(*,4).
|
|
dsc* ArithmeticNode::addSqlTime(const dsc* desc, impure_value* value) const
|
|
{
|
|
DEV_BLKCHK(node, type_nod);
|
|
fb_assert(blrOp == blr_add || blrOp == blr_subtract);
|
|
|
|
dsc* result = &value->vlu_desc;
|
|
|
|
fb_assert(value->vlu_desc.dsc_dtype == dtype_sql_time || desc->dsc_dtype == dtype_sql_time);
|
|
|
|
SINT64 d1;
|
|
// Coerce operand1 to a count of seconds
|
|
bool op1_is_time = false;
|
|
if (value->vlu_desc.dsc_dtype == dtype_sql_time)
|
|
{
|
|
d1 = *(GDS_TIME*) value->vlu_desc.dsc_address;
|
|
op1_is_time = true;
|
|
fb_assert(d1 >= 0 && d1 < ISC_TICKS_PER_DAY);
|
|
}
|
|
else
|
|
d1 = MOV_get_int64(&value->vlu_desc, ISC_TIME_SECONDS_PRECISION_SCALE);
|
|
|
|
SINT64 d2;
|
|
// Coerce operand2 to a count of seconds
|
|
bool op2_is_time = false;
|
|
if (desc->dsc_dtype == dtype_sql_time)
|
|
{
|
|
d2 = *(GDS_TIME*) desc->dsc_address;
|
|
op2_is_time = true;
|
|
fb_assert(d2 >= 0 && d2 < ISC_TICKS_PER_DAY);
|
|
}
|
|
else
|
|
d2 = MOV_get_int64(desc, ISC_TIME_SECONDS_PRECISION_SCALE);
|
|
|
|
if (blrOp == blr_subtract && op1_is_time && op2_is_time)
|
|
{
|
|
d2 = d1 - d2;
|
|
// Overflow cannot occur as the range of supported TIME values
|
|
// is less than the range of INTEGER
|
|
value->vlu_misc.vlu_long = d2;
|
|
result->dsc_dtype = dtype_long;
|
|
result->dsc_length = sizeof(SLONG);
|
|
result->dsc_scale = ISC_TIME_SECONDS_PRECISION_SCALE;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_long;
|
|
return result;
|
|
}
|
|
|
|
fb_assert(op1_is_time || op2_is_time);
|
|
fb_assert(!(op1_is_time && op2_is_time));
|
|
|
|
// Perform the operation
|
|
|
|
if (blrOp == blr_subtract)
|
|
{
|
|
fb_assert(op1_is_time);
|
|
d2 = d1 - d2;
|
|
}
|
|
else
|
|
d2 = d1 + d2;
|
|
|
|
// Make sure to use modulo 24 hour arithmetic
|
|
|
|
// Make the result positive
|
|
while (d2 < 0)
|
|
d2 += (ISC_TICKS_PER_DAY);
|
|
|
|
// And make it in the range of values for a day
|
|
d2 %= (ISC_TICKS_PER_DAY);
|
|
|
|
fb_assert(d2 >= 0 && d2 < ISC_TICKS_PER_DAY);
|
|
|
|
value->vlu_misc.vlu_sql_time = d2;
|
|
|
|
result->dsc_dtype = dtype_sql_time;
|
|
result->dsc_length = type_lengths[result->dsc_dtype];
|
|
result->dsc_scale = 0;
|
|
result->dsc_sub_type = 0;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_sql_time;
|
|
return result;
|
|
}
|
|
|
|
// Perform date&time arithmetic.
|
|
// TIMESTAMP - TIMESTAMP Result is INT64
|
|
// TIMESTAMP +/- NUMERIC Numeric is interpreted as days DECIMAL(*,*).
|
|
// NUMERIC +/- TIMESTAMP Numeric is interpreted as days DECIMAL(*,*).
|
|
// DATE + TIME
|
|
// TIME + DATE
|
|
dsc* ArithmeticNode::addTimeStamp(const dsc* desc, impure_value* value) const
|
|
{
|
|
fb_assert(blrOp == blr_add || blrOp == blr_subtract);
|
|
|
|
SINT64 d1, d2;
|
|
|
|
dsc* result = &value->vlu_desc;
|
|
|
|
// Operand 1 is Value -- Operand 2 is desc
|
|
|
|
if (value->vlu_desc.dsc_dtype == dtype_sql_date)
|
|
{
|
|
// DATE + TIME
|
|
if (desc->dsc_dtype == dtype_sql_time && blrOp == blr_add)
|
|
{
|
|
value->vlu_misc.vlu_timestamp.timestamp_date = value->vlu_misc.vlu_sql_date;
|
|
value->vlu_misc.vlu_timestamp.timestamp_time = *(GDS_TIME*) desc->dsc_address;
|
|
}
|
|
else
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_onlycan_add_timetodate));
|
|
}
|
|
else if (desc->dsc_dtype == dtype_sql_date)
|
|
{
|
|
// TIME + DATE
|
|
if (value->vlu_desc.dsc_dtype == dtype_sql_time && blrOp == blr_add)
|
|
{
|
|
value->vlu_misc.vlu_timestamp.timestamp_time = value->vlu_misc.vlu_sql_time;
|
|
value->vlu_misc.vlu_timestamp.timestamp_date = *(GDS_DATE*) desc->dsc_address;
|
|
}
|
|
else
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_onlycan_add_datetotime));
|
|
}
|
|
else
|
|
{
|
|
/* For historical reasons (behavior prior to V6),
|
|
there are times we will do timestamp arithmetic without a
|
|
timestamp being involved.
|
|
In such an event we need to convert a text type to a timestamp when
|
|
we don't already have one.
|
|
We assume any text string must represent a timestamp value. */
|
|
|
|
/* If we're subtracting, and the 2nd operand is a timestamp, or
|
|
something that looks & smells like it could be a timestamp, then
|
|
we must be doing <timestamp> - <timestamp> subtraction.
|
|
Notes that this COULD be as strange as <string> - <string>, but
|
|
because FLAG_DATE is set in the nod_flags we know we're supposed
|
|
to use some form of date arithmetic */
|
|
|
|
if (blrOp == blr_subtract &&
|
|
(desc->dsc_dtype == dtype_timestamp || DTYPE_IS_TEXT(desc->dsc_dtype)))
|
|
{
|
|
/* Handle cases of
|
|
<string> - <string>
|
|
<string> - <timestamp>
|
|
<timestamp> - <string>
|
|
<timestamp> - <timestamp>
|
|
in which cases we assume the string represents a timestamp value */
|
|
|
|
// If the first operand couldn't represent a timestamp, bomb out
|
|
|
|
if (!(value->vlu_desc.dsc_dtype == dtype_timestamp ||
|
|
DTYPE_IS_TEXT(value->vlu_desc.dsc_dtype)))
|
|
{
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_onlycansub_tstampfromtstamp));
|
|
}
|
|
|
|
d1 = getTimeStampToIscTicks(&value->vlu_desc);
|
|
d2 = getTimeStampToIscTicks(desc);
|
|
|
|
d2 = d1 - d2;
|
|
|
|
if (!dialect1)
|
|
{
|
|
/* multiply by 100,000 so that we can have the result as decimal (18,9)
|
|
* We have 10 ^-4; to convert this to 10^-9 we need to multiply by
|
|
* 100,000. Of course all this is true only because we are dividing
|
|
* by SECONDS_PER_DAY
|
|
* now divide by the number of seconds per day, this will give us the
|
|
* result as a int64 of type decimal (18, 9) in days instead of
|
|
* seconds.
|
|
*
|
|
* But SECONDS_PER_DAY has 2 trailing zeroes (because it is 24 * 60 *
|
|
* 60), so instead of calculating (X * 100000) / SECONDS_PER_DAY,
|
|
* use (X * (100000 / 100)) / (SECONDS_PER_DAY / 100), which can be
|
|
* simplified to (X * 1000) / (SECONDS_PER_DAY / 100)
|
|
* Since the largest possible difference in timestamps is about 3E11
|
|
* seconds or 3E15 isc_ticks, the product won't exceed approximately
|
|
* 3E18, which fits into an INT64.
|
|
*/
|
|
// 09-Apr-2004, Nickolay Samofatov. Adjust number before division to
|
|
// make sure we don't lose a tick as a result of remainder truncation
|
|
|
|
if (d2 >= 0)
|
|
d2 = (d2 * 1000 + (SECONDS_PER_DAY / 200)) / (SINT64) (SECONDS_PER_DAY / 100);
|
|
else
|
|
d2 = (d2 * 1000 - (SECONDS_PER_DAY / 200)) / (SINT64) (SECONDS_PER_DAY / 100);
|
|
|
|
value->vlu_misc.vlu_int64 = d2;
|
|
result->dsc_dtype = dtype_int64;
|
|
result->dsc_length = sizeof(SINT64);
|
|
result->dsc_scale = DIALECT_3_TIMESTAMP_SCALE;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_int64;
|
|
|
|
return result;
|
|
}
|
|
|
|
// This is dialect 1 subtraction returning double as before
|
|
value->vlu_misc.vlu_double = (double) d2 / ((double) ISC_TICKS_PER_DAY);
|
|
result->dsc_dtype = dtype_double;
|
|
result->dsc_length = sizeof(double);
|
|
result->dsc_scale = DIALECT_1_TIMESTAMP_SCALE;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_double;
|
|
|
|
return result;
|
|
}
|
|
|
|
/* From here we know our result must be a <timestamp>. The only
|
|
legal cases are:
|
|
<timestamp> +/- <numeric>
|
|
<numeric> + <timestamp>
|
|
However, the FLAG_DATE flag might have been set on any type of
|
|
nod_add / nod_subtract equation -- so we must detect any invalid
|
|
operands. Any <string> value is assumed to be convertable to
|
|
a timestamp */
|
|
|
|
// Coerce operand1 to a count of microseconds
|
|
|
|
bool op1_is_timestamp = false;
|
|
|
|
if (value->vlu_desc.dsc_dtype == dtype_timestamp ||
|
|
(DTYPE_IS_TEXT(value->vlu_desc.dsc_dtype)))
|
|
{
|
|
op1_is_timestamp = true;
|
|
}
|
|
|
|
// Coerce operand2 to a count of microseconds
|
|
|
|
bool op2_is_timestamp = false;
|
|
|
|
if ((desc->dsc_dtype == dtype_timestamp) || (DTYPE_IS_TEXT(desc->dsc_dtype)))
|
|
op2_is_timestamp = true;
|
|
|
|
// Exactly one of the operands must be a timestamp or
|
|
// convertable into a timestamp, otherwise it's one of
|
|
// <numeric> +/- <numeric>
|
|
// or <timestamp> +/- <timestamp>
|
|
// or <string> +/- <string>
|
|
// which are errors
|
|
|
|
if (op1_is_timestamp == op2_is_timestamp)
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) << Arg::Gds(isc_onlyoneop_mustbe_tstamp));
|
|
|
|
if (op1_is_timestamp)
|
|
{
|
|
d1 = getTimeStampToIscTicks(&value->vlu_desc);
|
|
d2 = getDayFraction(desc);
|
|
}
|
|
else
|
|
{
|
|
fb_assert(blrOp == blr_add);
|
|
fb_assert(op2_is_timestamp);
|
|
d1 = getDayFraction(&value->vlu_desc);
|
|
d2 = getTimeStampToIscTicks(desc);
|
|
}
|
|
|
|
// Perform the operation
|
|
|
|
if (blrOp == blr_subtract)
|
|
{
|
|
fb_assert(op1_is_timestamp);
|
|
d2 = d1 - d2;
|
|
}
|
|
else
|
|
d2 = d1 + d2;
|
|
|
|
// Convert the count of microseconds back to a date / time format
|
|
|
|
value->vlu_misc.vlu_timestamp.timestamp_date = d2 / (ISC_TICKS_PER_DAY);
|
|
value->vlu_misc.vlu_timestamp.timestamp_time = (d2 % ISC_TICKS_PER_DAY);
|
|
|
|
// Make sure the TIME portion is non-negative
|
|
|
|
if ((SLONG) value->vlu_misc.vlu_timestamp.timestamp_time < 0)
|
|
{
|
|
value->vlu_misc.vlu_timestamp.timestamp_time =
|
|
((SLONG) value->vlu_misc.vlu_timestamp.timestamp_time) + ISC_TICKS_PER_DAY;
|
|
value->vlu_misc.vlu_timestamp.timestamp_date -= 1;
|
|
}
|
|
|
|
if (!TimeStamp::isValidTimeStamp(value->vlu_misc.vlu_timestamp))
|
|
ERR_post(Arg::Gds(isc_datetime_range_exceeded));
|
|
}
|
|
|
|
fb_assert(value->vlu_misc.vlu_timestamp.timestamp_time >= 0 &&
|
|
value->vlu_misc.vlu_timestamp.timestamp_time < ISC_TICKS_PER_DAY);
|
|
|
|
result->dsc_dtype = dtype_timestamp;
|
|
result->dsc_length = type_lengths[result->dsc_dtype];
|
|
result->dsc_scale = 0;
|
|
result->dsc_sub_type = 0;
|
|
result->dsc_address = (UCHAR*) &value->vlu_misc.vlu_timestamp;
|
|
|
|
return result;
|
|
}
|
|
|
|
ValueExprNode* ArithmeticNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
return FB_NEW(getPool()) ArithmeticNode(getPool(), blrOp, dialect1,
|
|
doDsqlPass(dsqlScratch, arg1), doDsqlPass(dsqlScratch, arg2));
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
ArrayNode::ArrayNode(MemoryPool& pool, FieldNode* aField)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_ARRAY>(pool),
|
|
field(aField)
|
|
{
|
|
}
|
|
|
|
void ArrayNode::print(string& text) const
|
|
{
|
|
text = "ArrayNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* ArrayNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (dsqlScratch->isPsql())
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_dsql_invalid_array));
|
|
}
|
|
|
|
return field->internalDsqlPass(dsqlScratch, NULL);
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<BoolAsValueNode> regBoolAsValueNode(blr_bool_as_value);
|
|
|
|
BoolAsValueNode::BoolAsValueNode(MemoryPool& pool, BoolExprNode* aBoolean)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_BOOL_AS_VALUE>(pool),
|
|
boolean(aBoolean)
|
|
{
|
|
addChildNode(boolean, boolean);
|
|
}
|
|
|
|
DmlNode* BoolAsValueNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
BoolAsValueNode* node = FB_NEW(pool) BoolAsValueNode(pool);
|
|
node->boolean = PAR_parse_boolean(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void BoolAsValueNode::print(string& text) const
|
|
{
|
|
text = "BoolAsValueNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* BoolAsValueNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
BoolAsValueNode* node = FB_NEW(getPool()) BoolAsValueNode(getPool(),
|
|
doDsqlPass(dsqlScratch, boolean));
|
|
|
|
return node;
|
|
}
|
|
|
|
void BoolAsValueNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_bool_as_value);
|
|
GEN_expr(dsqlScratch, boolean);
|
|
}
|
|
|
|
void BoolAsValueNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
desc->makeBoolean();
|
|
desc->setNullable(true);
|
|
}
|
|
|
|
void BoolAsValueNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
desc->makeBoolean();
|
|
desc->setNullable(true);
|
|
}
|
|
|
|
ValueExprNode* BoolAsValueNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
BoolAsValueNode* node = FB_NEW(*tdbb->getDefaultPool()) BoolAsValueNode(*tdbb->getDefaultPool());
|
|
node->boolean = copier.copy(tdbb, boolean);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* BoolAsValueNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* BoolAsValueNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
UCHAR booleanVal = (UCHAR) boolean->execute(tdbb, request);
|
|
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
impure_value* impure = request->getImpure<impure_value>(impureOffset);
|
|
|
|
dsc desc;
|
|
desc.makeBoolean(&booleanVal);
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<CastNode> regCastNode(blr_cast);
|
|
|
|
CastNode::CastNode(MemoryPool& pool, ValueExprNode* aSource, dsql_fld* aDsqlField)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_CAST>(pool),
|
|
dsqlAlias("CAST"),
|
|
dsqlField(aDsqlField),
|
|
source(aSource),
|
|
itemInfo(NULL)
|
|
{
|
|
castDesc.clear();
|
|
addChildNode(source, source);
|
|
}
|
|
|
|
// Parse a datatype cast.
|
|
DmlNode* CastNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
CastNode* node = FB_NEW(pool) CastNode(pool);
|
|
|
|
ItemInfo itemInfo;
|
|
PAR_desc(tdbb, csb, &node->castDesc, &itemInfo);
|
|
|
|
node->source = PAR_parse_value(tdbb, csb);
|
|
|
|
if (itemInfo.isSpecial())
|
|
node->itemInfo = FB_NEW(*tdbb->getDefaultPool()) ItemInfo(*tdbb->getDefaultPool(), itemInfo);
|
|
|
|
if (itemInfo.explicitCollation)
|
|
{
|
|
CompilerScratch::Dependency dependency(obj_collation);
|
|
dependency.number = INTL_TEXT_TYPE(node->castDesc);
|
|
csb->csb_dependencies.push(dependency);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void CastNode::print(string& text) const
|
|
{
|
|
text.printf("CastNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* CastNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
CastNode* node = FB_NEW(getPool()) CastNode(getPool());
|
|
node->dsqlAlias = dsqlAlias;
|
|
node->source = doDsqlPass(dsqlScratch, source);
|
|
node->dsqlField = dsqlField;
|
|
|
|
DDL_resolve_intl_type(dsqlScratch, node->dsqlField, NULL);
|
|
node->setParameterType(dsqlScratch, NULL, false);
|
|
|
|
MAKE_desc_from_field(&node->castDesc, node->dsqlField);
|
|
MAKE_desc(dsqlScratch, &node->source->nodDesc, node->source);
|
|
|
|
node->castDesc.dsc_flags = node->source->nodDesc.dsc_flags & DSC_nullable;
|
|
|
|
return node;
|
|
}
|
|
|
|
void CastNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = dsqlAlias;
|
|
}
|
|
|
|
bool CastNode::setParameterType(DsqlCompilerScratch* /*dsqlScratch*/,
|
|
const dsc* /*desc*/, bool /*forceVarChar*/)
|
|
{
|
|
// ASF: Attention: CastNode::dsqlPass calls us with NULL node.
|
|
|
|
ParameterNode* paramNode = source->as<ParameterNode>();
|
|
|
|
if (paramNode)
|
|
{
|
|
dsql_par* parameter = paramNode->dsqlParameter;
|
|
|
|
if (parameter)
|
|
{
|
|
parameter->par_node = source;
|
|
MAKE_desc_from_field(¶meter->par_desc, dsqlField);
|
|
if (!dsqlField->fullDomain)
|
|
parameter->par_desc.setNullable(true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Generate BLR for a data-type cast operation.
|
|
void CastNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_cast);
|
|
dsqlScratch->putDtype(dsqlField, true);
|
|
GEN_expr(dsqlScratch, source);
|
|
}
|
|
|
|
void CastNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
*desc = castDesc;
|
|
}
|
|
|
|
void CastNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
// ASF: Commented out code here appears correct and makes the expression
|
|
// "1 + NULLIF(NULL, 0)" (failing since v2.1) to work. While this is natural as others
|
|
// nodes calling getDesc on sub nodes, it's causing some problem with contexts of
|
|
// views.
|
|
|
|
dsc desc1;
|
|
////source->getDesc(tdbb, csb, &desc1);
|
|
|
|
*desc = castDesc;
|
|
|
|
if ((desc->dsc_dtype <= dtype_any_text && !desc->dsc_length) ||
|
|
(desc->dsc_dtype == dtype_varying && desc->dsc_length <= sizeof(USHORT)))
|
|
{
|
|
// Remove this call if enable the one above.
|
|
source->getDesc(tdbb, csb, &desc1);
|
|
|
|
desc->dsc_length = DSC_string_length(&desc1);
|
|
|
|
if (desc->dsc_dtype == dtype_cstring)
|
|
desc->dsc_length++;
|
|
else if (desc->dsc_dtype == dtype_varying)
|
|
desc->dsc_length += sizeof(USHORT);
|
|
}
|
|
|
|
////if (desc1.isNull())
|
|
//// desc->setNull();
|
|
}
|
|
|
|
ValueExprNode* CastNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
CastNode* node = FB_NEW(getPool()) CastNode(getPool());
|
|
|
|
node->source = copier.copy(tdbb, source);
|
|
node->castDesc = castDesc;
|
|
node->itemInfo = itemInfo;
|
|
|
|
return node;
|
|
}
|
|
|
|
bool CastNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const CastNode* o = other->as<CastNode>();
|
|
fb_assert(o);
|
|
|
|
return dsqlField == o->dsqlField;
|
|
}
|
|
|
|
bool CastNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const CastNode* const otherNode = other->as<CastNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return DSC_EQUIV(&castDesc, &otherNode->castDesc, true);
|
|
}
|
|
|
|
ValueExprNode* CastNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass1(tdbb, csb);
|
|
|
|
const USHORT ttype = INTL_TEXT_TYPE(castDesc);
|
|
|
|
// Are we using a collation?
|
|
if (TTYPE_TO_COLLATION(ttype) != 0)
|
|
{
|
|
CMP_post_resource(&csb->csb_resources, INTL_texttype_lookup(tdbb, ttype),
|
|
Resource::rsc_collation, ttype);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* CastNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// Cast from one datatype to another.
|
|
dsc* CastNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
dsc* value = EVL_expr(tdbb, request, source);
|
|
if (request->req_flags & req_null)
|
|
value = NULL;
|
|
|
|
impure_value* impure = request->getImpure<impure_value>(impureOffset);
|
|
|
|
impure->vlu_desc = castDesc;
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc;
|
|
|
|
if (DTYPE_IS_TEXT(impure->vlu_desc.dsc_dtype))
|
|
{
|
|
USHORT length = DSC_string_length(&impure->vlu_desc);
|
|
|
|
if (length <= 0 && value)
|
|
{
|
|
// cast is a subtype cast only
|
|
|
|
length = DSC_string_length(value);
|
|
|
|
if (impure->vlu_desc.dsc_dtype == dtype_cstring)
|
|
++length; // for NULL byte
|
|
else if (impure->vlu_desc.dsc_dtype == dtype_varying)
|
|
length += sizeof(USHORT);
|
|
|
|
impure->vlu_desc.dsc_length = length;
|
|
}
|
|
|
|
length = impure->vlu_desc.dsc_length;
|
|
|
|
// Allocate a string block of sufficient size.
|
|
|
|
VaryingString* string = impure->vlu_string;
|
|
|
|
if (string && string->str_length < length)
|
|
{
|
|
delete string;
|
|
string = NULL;
|
|
}
|
|
|
|
if (!string)
|
|
{
|
|
string = impure->vlu_string = FB_NEW_RPT(*tdbb->getDefaultPool(), length) VaryingString();
|
|
string->str_length = length;
|
|
}
|
|
|
|
impure->vlu_desc.dsc_address = string->str_data;
|
|
}
|
|
|
|
EVL_validate(tdbb, Item(Item::TYPE_CAST), itemInfo,
|
|
value, value == NULL || (value->dsc_flags & DSC_null));
|
|
|
|
if (!value)
|
|
return NULL;
|
|
|
|
dsc desc;
|
|
char* text;
|
|
UCHAR tempByte;
|
|
|
|
if (value->dsc_dtype == dtype_boolean &&
|
|
(DTYPE_IS_TEXT(impure->vlu_desc.dsc_dtype) || DTYPE_IS_BLOB(impure->vlu_desc.dsc_dtype)))
|
|
{
|
|
text = const_cast<char*>(MOV_get_boolean(value) ? "TRUE" : "FALSE");
|
|
desc.makeText(static_cast<USHORT>(strlen(text)), CS_ASCII, reinterpret_cast<UCHAR*>(text));
|
|
value = &desc;
|
|
}
|
|
else if (impure->vlu_desc.dsc_dtype == dtype_boolean &&
|
|
(DTYPE_IS_TEXT(value->dsc_dtype) || DTYPE_IS_BLOB(value->dsc_dtype)))
|
|
{
|
|
desc.makeBoolean(&tempByte);
|
|
|
|
MoveBuffer buffer;
|
|
UCHAR* address;
|
|
int len = MOV_make_string2(tdbb, value, CS_ASCII, &address, buffer);
|
|
|
|
// Remove heading and trailing spaces.
|
|
|
|
while (len > 0 && isspace(*address))
|
|
{
|
|
++address;
|
|
--len;
|
|
}
|
|
|
|
while (len > 0 && isspace(address[len - 1]))
|
|
--len;
|
|
|
|
if (len == 4 && fb_utils::strnicmp(reinterpret_cast<char*>(address), "TRUE", len) == 0)
|
|
{
|
|
tempByte = '\1';
|
|
value = &desc;
|
|
}
|
|
else if (len == 5 && fb_utils::strnicmp(reinterpret_cast<char*>(address), "FALSE", len) == 0)
|
|
{
|
|
tempByte = '\0';
|
|
value = &desc;
|
|
}
|
|
}
|
|
|
|
if (DTYPE_IS_BLOB(value->dsc_dtype) || DTYPE_IS_BLOB(impure->vlu_desc.dsc_dtype))
|
|
blb::move(tdbb, value, &impure->vlu_desc, NULL);
|
|
else
|
|
MOV_move(tdbb, value, &impure->vlu_desc);
|
|
|
|
if (impure->vlu_desc.dsc_dtype == dtype_text)
|
|
INTL_adjust_text_descriptor(tdbb, &impure->vlu_desc);
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<CoalesceNode> regCoalesceNode(blr_coalesce);
|
|
|
|
DmlNode* CoalesceNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
CoalesceNode* node = FB_NEW(pool) CoalesceNode(pool);
|
|
node->args = PAR_args(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void CoalesceNode::print(string& text) const
|
|
{
|
|
text = "CoalesceNode\n";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* CoalesceNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
CoalesceNode* node = FB_NEW(getPool()) CoalesceNode(getPool(),
|
|
doDsqlPass(dsqlScratch, args));
|
|
node->make(dsqlScratch, &node->nodDesc); // Set descriptor for output node.
|
|
node->setParameterType(dsqlScratch, &node->nodDesc, false);
|
|
return node;
|
|
}
|
|
|
|
void CoalesceNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "COALESCE";
|
|
}
|
|
|
|
bool CoalesceNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool /*forceVarChar*/)
|
|
{
|
|
bool ret = false;
|
|
|
|
for (NestConst<ValueExprNode>* ptr = args->items.begin(); ptr != args->items.end(); ++ptr)
|
|
ret |= PASS1_set_parameter_type(dsqlScratch, *ptr, desc, false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void CoalesceNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsc desc;
|
|
make(dsqlScratch, &desc);
|
|
dsqlScratch->appendUChar(blr_cast);
|
|
GEN_descriptor(dsqlScratch, &desc, true);
|
|
|
|
dsqlScratch->appendUChar(blr_coalesce);
|
|
dsqlScratch->appendUChar(args->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* ptr = args->items.begin(); ptr != args->items.end(); ++ptr)
|
|
GEN_expr(dsqlScratch, *ptr);
|
|
}
|
|
|
|
void CoalesceNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc_from_list(dsqlScratch, desc, args, "COALESCE");
|
|
}
|
|
|
|
void CoalesceNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
Array<dsc> descs;
|
|
descs.resize(args->items.getCount());
|
|
|
|
unsigned i = 0;
|
|
Array<const dsc*> descPtrs;
|
|
descPtrs.resize(args->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* p = args->items.begin(); p != args->items.end(); ++p, ++i)
|
|
{
|
|
(*p)->getDesc(tdbb, csb, &descs[i]);
|
|
descPtrs[i] = &descs[i];
|
|
}
|
|
|
|
DataTypeUtil(tdbb).makeFromList(desc, "COALESCE", descPtrs.getCount(), descPtrs.begin());
|
|
}
|
|
|
|
ValueExprNode* CoalesceNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
CoalesceNode* node = FB_NEW(*tdbb->getDefaultPool()) CoalesceNode(*tdbb->getDefaultPool());
|
|
node->args = copier.copy(tdbb, args);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* CoalesceNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* CoalesceNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
const NestConst<ValueExprNode>* ptr = args->items.begin();
|
|
const NestConst<ValueExprNode>* end = args->items.end();
|
|
|
|
for (; ptr != end; ++ptr)
|
|
{
|
|
dsc* desc = EVL_expr(tdbb, request, *ptr);
|
|
|
|
if (desc && !(request->req_flags & req_null))
|
|
return desc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
CollateNode::CollateNode(MemoryPool& pool, ValueExprNode* aArg, const Firebird::MetaName& aCollation)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_COLLATE>(pool),
|
|
arg(aArg),
|
|
collation(pool, aCollation)
|
|
{
|
|
addDsqlChildNode(arg);
|
|
}
|
|
|
|
void CollateNode::print(string& text) const
|
|
{
|
|
text = "CollateNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
// Turn a collate clause into a cast clause.
|
|
// If the source is not already text, report an error. (SQL 92: Section 13.1, pg 308, item 11).
|
|
ValueExprNode* CollateNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
ValueExprNode* nod = doDsqlPass(dsqlScratch, arg);
|
|
return pass1Collate(dsqlScratch, nod, collation);
|
|
}
|
|
|
|
ValueExprNode* CollateNode::pass1Collate(DsqlCompilerScratch* dsqlScratch, ValueExprNode* input,
|
|
const MetaName& collation)
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
MemoryPool& pool = *tdbb->getDefaultPool();
|
|
|
|
dsql_fld* field = FB_NEW(pool) dsql_fld(pool);
|
|
CastNode* castNode = FB_NEW(pool) CastNode(pool, input, field);
|
|
|
|
MAKE_desc(dsqlScratch, &input->nodDesc, input);
|
|
|
|
if (input->nodDesc.dsc_dtype <= dtype_any_text ||
|
|
(input->nodDesc.dsc_dtype == dtype_blob && input->nodDesc.dsc_sub_type == isc_blob_text))
|
|
{
|
|
assignFieldDtypeFromDsc(field, &input->nodDesc);
|
|
field->charLength = 0;
|
|
}
|
|
else
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_collation_requires_text));
|
|
}
|
|
|
|
DDL_resolve_intl_type(dsqlScratch, field, collation);
|
|
MAKE_desc_from_field(&castNode->castDesc, field);
|
|
|
|
return castNode;
|
|
}
|
|
|
|
// Set a field's descriptor from a DSC.
|
|
// (If dsql_fld* is ever redefined this can be removed)
|
|
void CollateNode::assignFieldDtypeFromDsc(dsql_fld* field, const dsc* desc)
|
|
{
|
|
DEV_BLKCHK(field, dsql_type_fld);
|
|
|
|
field->dtype = desc->dsc_dtype;
|
|
field->scale = desc->dsc_scale;
|
|
field->subType = desc->dsc_sub_type;
|
|
field->length = desc->dsc_length;
|
|
|
|
if (desc->dsc_dtype <= dtype_any_text)
|
|
{
|
|
field->collationId = DSC_GET_COLLATE(desc);
|
|
field->charSetId = DSC_GET_CHARSET(desc);
|
|
}
|
|
else if (desc->dsc_dtype == dtype_blob)
|
|
{
|
|
field->charSetId = desc->dsc_scale;
|
|
field->collationId = desc->dsc_flags >> 8;
|
|
}
|
|
|
|
if (desc->dsc_flags & DSC_nullable)
|
|
field->flags |= FLD_nullable;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<ConcatenateNode> regConcatenateNode(blr_concatenate);
|
|
|
|
ConcatenateNode::ConcatenateNode(MemoryPool& pool, ValueExprNode* aArg1, ValueExprNode* aArg2)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_CONCATENATE>(pool),
|
|
arg1(aArg1),
|
|
arg2(aArg2)
|
|
{
|
|
addChildNode(arg1, arg1);
|
|
addChildNode(arg2, arg2);
|
|
}
|
|
|
|
DmlNode* ConcatenateNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
ConcatenateNode* node = FB_NEW(pool) ConcatenateNode(pool);
|
|
node->arg1 = PAR_parse_value(tdbb, csb);
|
|
node->arg2 = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void ConcatenateNode::print(string& text) const
|
|
{
|
|
text = "ConcatenateNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void ConcatenateNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "CONCATENATION";
|
|
}
|
|
|
|
bool ConcatenateNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, arg1, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, arg2, desc, forceVarChar);
|
|
}
|
|
|
|
void ConcatenateNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_concatenate);
|
|
GEN_expr(dsqlScratch, arg1);
|
|
GEN_expr(dsqlScratch, arg2);
|
|
}
|
|
|
|
void ConcatenateNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
dsc desc1, desc2;
|
|
|
|
MAKE_desc(dsqlScratch, &desc1, arg1);
|
|
MAKE_desc(dsqlScratch, &desc2, arg2);
|
|
|
|
if (desc1.isNull())
|
|
{
|
|
desc1.makeText(0, desc2.getTextType());
|
|
desc1.setNull();
|
|
}
|
|
|
|
if (desc2.isNull())
|
|
{
|
|
desc2.makeText(0, desc1.getTextType());
|
|
desc2.setNull();
|
|
}
|
|
|
|
DSqlDataTypeUtil(dsqlScratch).makeConcatenate(desc, &desc1, &desc2);
|
|
}
|
|
|
|
void ConcatenateNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
dsc desc1, desc2;
|
|
|
|
arg1->getDesc(tdbb, csb, &desc1);
|
|
arg2->getDesc(tdbb, csb, &desc2);
|
|
|
|
DataTypeUtil(tdbb).makeConcatenate(desc, &desc1, &desc2);
|
|
}
|
|
|
|
ValueExprNode* ConcatenateNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
ConcatenateNode* node = FB_NEW(*tdbb->getDefaultPool()) ConcatenateNode(*tdbb->getDefaultPool());
|
|
node->arg1 = copier.copy(tdbb, arg1);
|
|
node->arg2 = copier.copy(tdbb, arg2);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* ConcatenateNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* ConcatenateNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
const dsc* value1 = EVL_expr(tdbb, request, arg1);
|
|
const ULONG flags = request->req_flags;
|
|
const dsc* value2 = EVL_expr(tdbb, request, arg2);
|
|
|
|
// restore saved NULL state
|
|
|
|
if (flags & req_null)
|
|
request->req_flags |= req_null;
|
|
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
impure_value* impure = request->getImpure<impure_value>(impureOffset);
|
|
dsc desc;
|
|
|
|
if (value1->dsc_dtype == dtype_dbkey && value2->dsc_dtype == dtype_dbkey)
|
|
{
|
|
if ((ULONG) value1->dsc_length + (ULONG) value2->dsc_length > MAX_COLUMN_SIZE - sizeof(USHORT))
|
|
{
|
|
ERR_post(Arg::Gds(isc_concat_overflow));
|
|
return NULL;
|
|
}
|
|
|
|
desc.dsc_dtype = dtype_dbkey;
|
|
desc.dsc_length = value1->dsc_length + value2->dsc_length;
|
|
desc.dsc_address = NULL;
|
|
|
|
VaryingString* string = NULL;
|
|
if (value1->dsc_address == impure->vlu_desc.dsc_address ||
|
|
value2->dsc_address == impure->vlu_desc.dsc_address)
|
|
{
|
|
string = impure->vlu_string;
|
|
impure->vlu_string = NULL;
|
|
}
|
|
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
UCHAR* p = impure->vlu_desc.dsc_address;
|
|
|
|
memcpy(p, value1->dsc_address, value1->dsc_length);
|
|
p += value1->dsc_length;
|
|
memcpy(p, value2->dsc_address, value2->dsc_length);
|
|
|
|
delete string;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
DataTypeUtil(tdbb).makeConcatenate(&desc, value1, value2);
|
|
|
|
// Both values are present; build the concatenation
|
|
|
|
MoveBuffer temp1;
|
|
UCHAR* address1 = NULL;
|
|
USHORT length1 = 0;
|
|
|
|
if (!value1->isBlob())
|
|
length1 = MOV_make_string2(tdbb, value1, desc.getTextType(), &address1, temp1);
|
|
|
|
MoveBuffer temp2;
|
|
UCHAR* address2 = NULL;
|
|
USHORT length2 = 0;
|
|
|
|
if (!value2->isBlob())
|
|
length2 = MOV_make_string2(tdbb, value2, desc.getTextType(), &address2, temp2);
|
|
|
|
if (address1 && address2)
|
|
{
|
|
fb_assert(desc.dsc_dtype == dtype_varying);
|
|
|
|
if ((ULONG) length1 + (ULONG) length2 > MAX_COLUMN_SIZE - sizeof(USHORT))
|
|
{
|
|
ERR_post(Arg::Gds(isc_concat_overflow));
|
|
return NULL;
|
|
}
|
|
|
|
desc.dsc_dtype = dtype_text;
|
|
desc.dsc_length = length1 + length2;
|
|
desc.dsc_address = NULL;
|
|
|
|
VaryingString* string = NULL;
|
|
if (value1->dsc_address == impure->vlu_desc.dsc_address ||
|
|
value2->dsc_address == impure->vlu_desc.dsc_address)
|
|
{
|
|
string = impure->vlu_string;
|
|
impure->vlu_string = NULL;
|
|
}
|
|
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
UCHAR* p = impure->vlu_desc.dsc_address;
|
|
|
|
if (length1)
|
|
{
|
|
memcpy(p, address1, length1);
|
|
p += length1;
|
|
}
|
|
|
|
if (length2)
|
|
memcpy(p, address2, length2);
|
|
|
|
delete string;
|
|
}
|
|
else
|
|
{
|
|
fb_assert(desc.isBlob());
|
|
|
|
desc.dsc_address = (UCHAR*)&impure->vlu_misc.vlu_bid;
|
|
|
|
blb* newBlob = blb::create(tdbb, tdbb->getRequest()->req_transaction,
|
|
&impure->vlu_misc.vlu_bid);
|
|
|
|
HalfStaticArray<UCHAR, BUFFER_SMALL> buffer;
|
|
|
|
if (address1)
|
|
newBlob->BLB_put_data(tdbb, address1, length1); // first value is not a blob
|
|
else
|
|
{
|
|
UCharBuffer bpb;
|
|
BLB_gen_bpb_from_descs(value1, &desc, bpb);
|
|
|
|
blb* blob = blb::open2(tdbb, tdbb->getRequest()->req_transaction,
|
|
reinterpret_cast<bid*>(value1->dsc_address), bpb.getCount(), bpb.begin());
|
|
|
|
while (!(blob->blb_flags & BLB_eof))
|
|
{
|
|
SLONG len = blob->BLB_get_data(tdbb, buffer.begin(), buffer.getCapacity(), false);
|
|
|
|
if (len)
|
|
newBlob->BLB_put_data(tdbb, buffer.begin(), len);
|
|
}
|
|
|
|
blob->BLB_close(tdbb);
|
|
}
|
|
|
|
if (address2)
|
|
newBlob->BLB_put_data(tdbb, address2, length2); // second value is not a blob
|
|
else
|
|
{
|
|
UCharBuffer bpb;
|
|
BLB_gen_bpb_from_descs(value2, &desc, bpb);
|
|
|
|
blb* blob = blb::open2(tdbb, tdbb->getRequest()->req_transaction,
|
|
reinterpret_cast<bid*>(value2->dsc_address), bpb.getCount(), bpb.begin());
|
|
|
|
while (!(blob->blb_flags & BLB_eof))
|
|
{
|
|
SLONG len = blob->BLB_get_data(tdbb, buffer.begin(), buffer.getCapacity(), false);
|
|
|
|
if (len)
|
|
newBlob->BLB_put_data(tdbb, buffer.begin(), len);
|
|
}
|
|
|
|
blob->BLB_close(tdbb);
|
|
}
|
|
|
|
newBlob->BLB_close(tdbb);
|
|
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
}
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
ValueExprNode* ConcatenateNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
return FB_NEW(getPool()) ConcatenateNode(getPool(),
|
|
doDsqlPass(dsqlScratch, arg1), doDsqlPass(dsqlScratch, arg2));
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<CurrentDateNode> regCurrentDateNode(blr_current_date);
|
|
|
|
DmlNode* CurrentDateNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* /*csb*/,
|
|
const UCHAR /*blrOp*/)
|
|
{
|
|
return FB_NEW(pool) CurrentDateNode(pool);
|
|
}
|
|
|
|
void CurrentDateNode::print(string& text) const
|
|
{
|
|
text.printf("CurrentDateNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void CurrentDateNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "CURRENT_DATE";
|
|
}
|
|
|
|
void CurrentDateNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_current_date);
|
|
}
|
|
|
|
void CurrentDateNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_sql_date;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
}
|
|
|
|
void CurrentDateNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_sql_date;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
}
|
|
|
|
ValueExprNode* CurrentDateNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
return FB_NEW(*tdbb->getDefaultPool()) CurrentDateNode(*tdbb->getDefaultPool());
|
|
}
|
|
|
|
ValueExprNode* CurrentDateNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* CurrentDateNode::execute(thread_db* /*tdbb*/, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
// Use the request timestamp.
|
|
fb_assert(!request->req_timestamp.isEmpty());
|
|
ISC_TIMESTAMP encTimes = request->req_timestamp.value();
|
|
|
|
memset(&impure->vlu_desc, 0, sizeof(impure->vlu_desc));
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_timestamp;
|
|
|
|
impure->vlu_desc.dsc_dtype = dtype_sql_date;
|
|
impure->vlu_desc.dsc_length = type_lengths[dtype_sql_date];
|
|
*(ULONG*) impure->vlu_desc.dsc_address = encTimes.timestamp_date;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<CurrentTimeNode> regCurrentTimeNode(blr_current_time);
|
|
static RegisterNode<CurrentTimeNode> regCurrentTimeNode2(blr_current_time2);
|
|
|
|
DmlNode* CurrentTimeNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
|
{
|
|
unsigned precision = DEFAULT_TIME_PRECISION;
|
|
|
|
fb_assert(blrOp == blr_current_time || blrOp == blr_current_time2);
|
|
|
|
if (blrOp == blr_current_time2)
|
|
{
|
|
precision = csb->csb_blr_reader.getByte();
|
|
|
|
if (precision > MAX_TIME_PRECISION)
|
|
ERR_post(Arg::Gds(isc_invalid_time_precision) << Arg::Num(MAX_TIME_PRECISION));
|
|
}
|
|
|
|
return FB_NEW(pool) CurrentTimeNode(pool, precision);
|
|
}
|
|
|
|
void CurrentTimeNode::print(string& text) const
|
|
{
|
|
text.printf("CurrentTimeNode (%d)", precision);
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void CurrentTimeNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "CURRENT_TIME";
|
|
}
|
|
|
|
void CurrentTimeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (precision == DEFAULT_TIME_PRECISION)
|
|
dsqlScratch->appendUChar(blr_current_time);
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_current_time2);
|
|
dsqlScratch->appendUChar(precision);
|
|
}
|
|
}
|
|
|
|
void CurrentTimeNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_sql_time;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
}
|
|
|
|
void CurrentTimeNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_sql_time;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
}
|
|
|
|
ValueExprNode* CurrentTimeNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
return FB_NEW(*tdbb->getDefaultPool()) CurrentTimeNode(*tdbb->getDefaultPool(), precision);
|
|
}
|
|
|
|
ValueExprNode* CurrentTimeNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* CurrentTimeNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/)
|
|
{
|
|
if (precision > MAX_TIME_PRECISION)
|
|
ERRD_post(Arg::Gds(isc_invalid_time_precision) << Arg::Num(MAX_TIME_PRECISION));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* CurrentTimeNode::execute(thread_db* /*tdbb*/, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
// Use the request timestamp.
|
|
fb_assert(!request->req_timestamp.isEmpty());
|
|
ISC_TIMESTAMP encTimes = request->req_timestamp.value();
|
|
|
|
memset(&impure->vlu_desc, 0, sizeof(impure->vlu_desc));
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_timestamp;
|
|
|
|
TimeStamp::round_time(encTimes.timestamp_time, precision);
|
|
|
|
impure->vlu_desc.dsc_dtype = dtype_sql_time;
|
|
impure->vlu_desc.dsc_length = type_lengths[dtype_sql_time];
|
|
*(ULONG*) impure->vlu_desc.dsc_address = encTimes.timestamp_time;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<CurrentTimeStampNode> regCurrentTimeStampNode(blr_current_timestamp);
|
|
static RegisterNode<CurrentTimeStampNode> regCurrentTimeStampNode2(blr_current_timestamp2);
|
|
|
|
DmlNode* CurrentTimeStampNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* csb,
|
|
const UCHAR blrOp)
|
|
{
|
|
unsigned precision = DEFAULT_TIMESTAMP_PRECISION;
|
|
|
|
fb_assert(blrOp == blr_current_timestamp || blrOp == blr_current_timestamp2);
|
|
|
|
if (blrOp == blr_current_timestamp2)
|
|
{
|
|
precision = csb->csb_blr_reader.getByte();
|
|
|
|
if (precision > MAX_TIME_PRECISION)
|
|
ERR_post(Arg::Gds(isc_invalid_time_precision) << Arg::Num(MAX_TIME_PRECISION));
|
|
}
|
|
|
|
return FB_NEW(pool) CurrentTimeStampNode(pool, precision);
|
|
}
|
|
|
|
void CurrentTimeStampNode::print(string& text) const
|
|
{
|
|
text.printf("CurrentTimeStampNode (%d)", precision);
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void CurrentTimeStampNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "CURRENT_TIMESTAMP";
|
|
}
|
|
|
|
void CurrentTimeStampNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (precision == DEFAULT_TIMESTAMP_PRECISION)
|
|
dsqlScratch->appendUChar(blr_current_timestamp);
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_current_timestamp2);
|
|
dsqlScratch->appendUChar(precision);
|
|
}
|
|
}
|
|
|
|
void CurrentTimeStampNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_timestamp;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
}
|
|
|
|
void CurrentTimeStampNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_timestamp;
|
|
desc->dsc_sub_type = 0;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_length = type_lengths[desc->dsc_dtype];
|
|
}
|
|
|
|
ValueExprNode* CurrentTimeStampNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
return FB_NEW(*tdbb->getDefaultPool()) CurrentTimeStampNode(*tdbb->getDefaultPool(), precision);
|
|
}
|
|
|
|
ValueExprNode* CurrentTimeStampNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* CurrentTimeStampNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/)
|
|
{
|
|
if (precision > MAX_TIME_PRECISION)
|
|
ERRD_post(Arg::Gds(isc_invalid_time_precision) << Arg::Num(MAX_TIME_PRECISION));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* CurrentTimeStampNode::execute(thread_db* /*tdbb*/, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
// Use the request timestamp.
|
|
fb_assert(!request->req_timestamp.isEmpty());
|
|
ISC_TIMESTAMP encTimes = request->req_timestamp.value();
|
|
|
|
memset(&impure->vlu_desc, 0, sizeof(impure->vlu_desc));
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_timestamp;
|
|
|
|
TimeStamp::round_time(encTimes.timestamp_time, precision);
|
|
|
|
impure->vlu_desc.dsc_dtype = dtype_timestamp;
|
|
impure->vlu_desc.dsc_length = type_lengths[dtype_timestamp];
|
|
*((ISC_TIMESTAMP*) impure->vlu_desc.dsc_address) = encTimes;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<CurrentRoleNode> regCurrentRoleNode(blr_current_role);
|
|
|
|
DmlNode* CurrentRoleNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* /*csb*/,
|
|
const UCHAR /*blrOp*/)
|
|
{
|
|
return FB_NEW(pool) CurrentRoleNode(pool);
|
|
}
|
|
|
|
void CurrentRoleNode::print(string& text) const
|
|
{
|
|
text = "CurrentRoleNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void CurrentRoleNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "ROLE";
|
|
}
|
|
|
|
void CurrentRoleNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_current_role);
|
|
}
|
|
|
|
void CurrentRoleNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_varying;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_ttype() = ttype_metadata;
|
|
desc->dsc_length =
|
|
(USERNAME_LENGTH / METADATA_BYTES_PER_CHAR) *
|
|
METD_get_charset_bpc(dsqlScratch->getTransaction(), ttype_metadata) + sizeof(USHORT);
|
|
}
|
|
|
|
void CurrentRoleNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_text;
|
|
desc->dsc_ttype() = ttype_metadata;
|
|
desc->dsc_length = USERNAME_LENGTH;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
|
|
ValueExprNode* CurrentRoleNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
return FB_NEW(*tdbb->getDefaultPool()) CurrentRoleNode(*tdbb->getDefaultPool());
|
|
}
|
|
|
|
ValueExprNode* CurrentRoleNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// CVC: Current role will get a validated role; IE one that exists.
|
|
dsc* CurrentRoleNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
impure->vlu_desc.dsc_dtype = dtype_text;
|
|
impure->vlu_desc.dsc_sub_type = 0;
|
|
impure->vlu_desc.dsc_scale = 0;
|
|
impure->vlu_desc.setTextType(ttype_metadata);
|
|
const char* curRole = NULL;
|
|
|
|
if (tdbb->getAttachment()->att_user)
|
|
{
|
|
curRole = tdbb->getAttachment()->att_user->usr_sql_role_name.c_str();
|
|
impure->vlu_desc.dsc_address = reinterpret_cast<UCHAR*>(const_cast<char*>(curRole));
|
|
}
|
|
|
|
if (curRole)
|
|
impure->vlu_desc.dsc_length = static_cast<USHORT>(strlen(curRole));
|
|
else
|
|
impure->vlu_desc.dsc_length = 0;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
ValueExprNode* CurrentRoleNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/)
|
|
{
|
|
return FB_NEW(getPool()) CurrentRoleNode(getPool());
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<CurrentUserNode> regCurrentUserNode(blr_user_name);
|
|
|
|
DmlNode* CurrentUserNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* /*csb*/,
|
|
const UCHAR /*blrOp*/)
|
|
{
|
|
return FB_NEW(pool) CurrentUserNode(pool);
|
|
}
|
|
|
|
void CurrentUserNode::print(string& text) const
|
|
{
|
|
text = "CurrentUserNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void CurrentUserNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "USER";
|
|
}
|
|
|
|
void CurrentUserNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_user_name);
|
|
}
|
|
|
|
void CurrentUserNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_varying;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
desc->dsc_ttype() = ttype_metadata;
|
|
desc->dsc_length =
|
|
(USERNAME_LENGTH / METADATA_BYTES_PER_CHAR) *
|
|
METD_get_charset_bpc(dsqlScratch->getTransaction(), ttype_metadata) + sizeof(USHORT);
|
|
}
|
|
|
|
void CurrentUserNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = dtype_text;
|
|
desc->dsc_ttype() = ttype_metadata;
|
|
desc->dsc_length = USERNAME_LENGTH;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
|
|
ValueExprNode* CurrentUserNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
return FB_NEW(*tdbb->getDefaultPool()) CurrentUserNode(*tdbb->getDefaultPool());
|
|
}
|
|
|
|
ValueExprNode* CurrentUserNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* CurrentUserNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
impure->vlu_desc.dsc_dtype = dtype_text;
|
|
impure->vlu_desc.dsc_sub_type = 0;
|
|
impure->vlu_desc.dsc_scale = 0;
|
|
impure->vlu_desc.setTextType(ttype_metadata);
|
|
const char* curUser = NULL;
|
|
|
|
if (tdbb->getAttachment()->att_user)
|
|
{
|
|
curUser = tdbb->getAttachment()->att_user->usr_user_name.c_str();
|
|
impure->vlu_desc.dsc_address = reinterpret_cast<UCHAR*>(const_cast<char*>(curUser));
|
|
}
|
|
|
|
if (curUser)
|
|
impure->vlu_desc.dsc_length = static_cast<USHORT>(strlen(curUser));
|
|
else
|
|
impure->vlu_desc.dsc_length = 0;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
ValueExprNode* CurrentUserNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/)
|
|
{
|
|
return FB_NEW(getPool()) CurrentUserNode(getPool());
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<DecodeNode> regDecodeNode(blr_decode);
|
|
|
|
DmlNode* DecodeNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
DecodeNode* node = FB_NEW(pool) DecodeNode(pool);
|
|
node->test = PAR_parse_value(tdbb, csb);
|
|
node->conditions = PAR_args(tdbb, csb);
|
|
node->values = PAR_args(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void DecodeNode::print(string& text) const
|
|
{
|
|
text = "DecodeNode\n";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* DecodeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
DecodeNode* node = FB_NEW(getPool()) DecodeNode(getPool(), doDsqlPass(dsqlScratch, test),
|
|
doDsqlPass(dsqlScratch, conditions), doDsqlPass(dsqlScratch, values));
|
|
node->label = label;
|
|
node->make(dsqlScratch, &node->nodDesc); // Set descriptor for output node.
|
|
node->setParameterType(dsqlScratch, &node->nodDesc, false);
|
|
return node;
|
|
}
|
|
|
|
void DecodeNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = label;
|
|
}
|
|
|
|
bool DecodeNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool /*forceVarChar*/)
|
|
{
|
|
// Check if there is a parameter in the test/conditions.
|
|
bool setParameters = test->is<ParameterNode>();
|
|
|
|
if (!setParameters)
|
|
{
|
|
for (NestConst<ValueExprNode>* ptr = conditions->items.begin();
|
|
ptr != conditions->items.end();
|
|
++ptr)
|
|
{
|
|
if ((*ptr)->is<ParameterNode>())
|
|
{
|
|
setParameters = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (setParameters)
|
|
{
|
|
// Build list to make describe information for the test and conditions expressions.
|
|
AutoPtr<ValueListNode> node1(FB_NEW(getPool()) ValueListNode(getPool(),
|
|
conditions->items.getCount() + 1));
|
|
|
|
dsc node1Desc;
|
|
node1Desc.clear();
|
|
|
|
unsigned i = 0;
|
|
|
|
node1->items[i++] = test;
|
|
|
|
for (NestConst<ValueExprNode>* ptr = conditions->items.begin();
|
|
ptr != conditions->items.end();
|
|
++ptr, ++i)
|
|
{
|
|
node1->items[i] = *ptr;
|
|
}
|
|
|
|
MAKE_desc_from_list(dsqlScratch, &node1Desc, node1, label.c_str());
|
|
|
|
if (!node1Desc.isUnknown())
|
|
{
|
|
// Set parameter describe information.
|
|
PASS1_set_parameter_type(dsqlScratch, test, &node1Desc, false);
|
|
|
|
for (NestConst<ValueExprNode>* ptr = conditions->items.begin();
|
|
ptr != conditions->items.end();
|
|
++ptr)
|
|
{
|
|
PASS1_set_parameter_type(dsqlScratch, *ptr, &node1Desc, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ret = false;
|
|
|
|
for (NestConst<ValueExprNode>* ptr = values->items.begin(); ptr != values->items.end(); ++ptr)
|
|
ret |= PASS1_set_parameter_type(dsqlScratch, *ptr, desc, false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void DecodeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_decode);
|
|
GEN_expr(dsqlScratch, test);
|
|
|
|
dsqlScratch->appendUChar(conditions->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* ptr = conditions->items.begin();
|
|
ptr != conditions->items.end();
|
|
++ptr)
|
|
{
|
|
(*ptr)->genBlr(dsqlScratch);
|
|
}
|
|
|
|
dsqlScratch->appendUChar(values->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* ptr = values->items.begin(); ptr != values->items.end(); ++ptr)
|
|
(*ptr)->genBlr(dsqlScratch);
|
|
}
|
|
|
|
void DecodeNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc_from_list(dsqlScratch, desc, values, label.c_str());
|
|
desc->setNullable(true);
|
|
}
|
|
|
|
void DecodeNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
Array<dsc> descs;
|
|
descs.resize(values->items.getCount());
|
|
|
|
unsigned i = 0;
|
|
Array<const dsc*> descPtrs;
|
|
descPtrs.resize(values->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* p = values->items.begin(); p != values->items.end(); ++p, ++i)
|
|
{
|
|
(*p)->getDesc(tdbb, csb, &descs[i]);
|
|
descPtrs[i] = &descs[i];
|
|
}
|
|
|
|
DataTypeUtil(tdbb).makeFromList(desc, label.c_str(), descPtrs.getCount(), descPtrs.begin());
|
|
|
|
desc->setNullable(true);
|
|
}
|
|
|
|
ValueExprNode* DecodeNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
DecodeNode* node = FB_NEW(*tdbb->getDefaultPool()) DecodeNode(*tdbb->getDefaultPool());
|
|
node->test = copier.copy(tdbb, test);
|
|
node->conditions = copier.copy(tdbb, conditions);
|
|
node->values = copier.copy(tdbb, values);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* DecodeNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* DecodeNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
dsc* testDesc = EVL_expr(tdbb, request, test);
|
|
|
|
// The comparisons are done with "equal" operator semantics, so if the test value is
|
|
// NULL we have nothing to compare.
|
|
if (testDesc && !(request->req_flags & req_null))
|
|
{
|
|
const NestConst<ValueExprNode>* conditionsPtr = conditions->items.begin();
|
|
const NestConst<ValueExprNode>* conditionsEnd = conditions->items.end();
|
|
const NestConst<ValueExprNode>* valuesPtr = values->items.begin();
|
|
|
|
for (; conditionsPtr != conditionsEnd; ++conditionsPtr, ++valuesPtr)
|
|
{
|
|
dsc* desc = EVL_expr(tdbb, request, *conditionsPtr);
|
|
|
|
if (desc && !(request->req_flags & req_null) && MOV_compare(testDesc, desc) == 0)
|
|
return EVL_expr(tdbb, request, *valuesPtr);
|
|
}
|
|
}
|
|
|
|
if (values->items.getCount() > conditions->items.getCount())
|
|
return EVL_expr(tdbb, request, values->items.back());
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<DerivedExprNode> regDerivedExprNode(blr_derived_expr);
|
|
|
|
DmlNode* DerivedExprNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
DerivedExprNode* node = FB_NEW(pool) DerivedExprNode(pool);
|
|
|
|
// CVC: bottleneck
|
|
const StreamType streamCount = csb->csb_blr_reader.getByte();
|
|
|
|
for (StreamType i = 0; i < streamCount; ++i)
|
|
{
|
|
const USHORT n = csb->csb_blr_reader.getByte();
|
|
node->internalStreamList.add(csb->csb_rpt[n].csb_stream);
|
|
}
|
|
|
|
node->arg = PAR_parse_value(tdbb, csb);
|
|
|
|
return node;
|
|
}
|
|
|
|
void DerivedExprNode::collectStreams(SortedStreamList& streamList) const
|
|
{
|
|
arg->collectStreams(streamList);
|
|
|
|
for (const StreamType* i = internalStreamList.begin(); i != internalStreamList.end(); ++i)
|
|
{
|
|
if (!streamList.exist(*i))
|
|
streamList.add(*i);
|
|
}
|
|
}
|
|
|
|
bool DerivedExprNode::computable(CompilerScratch* csb, StreamType stream,
|
|
bool allowOnlyCurrentStream, ValueExprNode* /*value*/)
|
|
{
|
|
if (!arg->computable(csb, stream, allowOnlyCurrentStream))
|
|
return false;
|
|
|
|
SortedStreamList argStreams;
|
|
arg->collectStreams(argStreams);
|
|
|
|
for (StreamType* i = internalStreamList.begin(); i != internalStreamList.end(); ++i)
|
|
{
|
|
const StreamType n = *i;
|
|
|
|
if (argStreams.exist(n))
|
|
{
|
|
// We've already checked computability of the argument,
|
|
// so any stream it refers to is known to be active.
|
|
continue;
|
|
}
|
|
|
|
if (allowOnlyCurrentStream)
|
|
{
|
|
if (n != stream && !(csb->csb_rpt[n].csb_flags & csb_sub_stream))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (n == stream)
|
|
return false;
|
|
}
|
|
|
|
if (!(csb->csb_rpt[n].csb_flags & csb_active))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DerivedExprNode::findDependentFromStreams(const OptimizerRetrieval* optRet,
|
|
SortedStreamList* streamList)
|
|
{
|
|
arg->findDependentFromStreams(optRet, streamList);
|
|
|
|
for (StreamType* i = internalStreamList.begin(); i != internalStreamList.end(); ++i)
|
|
{
|
|
const StreamType derivedStream = *i;
|
|
|
|
if (derivedStream != optRet->stream &&
|
|
(optRet->csb->csb_rpt[derivedStream].csb_flags & csb_active))
|
|
{
|
|
if (!streamList->exist(derivedStream))
|
|
streamList->add(derivedStream);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DerivedExprNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
arg->getDesc(tdbb, csb, desc);
|
|
}
|
|
|
|
ValueExprNode* DerivedExprNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
DerivedExprNode* node = FB_NEW(*tdbb->getDefaultPool()) DerivedExprNode(*tdbb->getDefaultPool());
|
|
node->arg = copier.copy(tdbb, arg);
|
|
node->internalStreamList = internalStreamList;
|
|
|
|
if (copier.remap)
|
|
{
|
|
#ifdef CMP_DEBUG
|
|
csb->dump("remap nod_derived_expr:\n");
|
|
for (StreamType* i = node->streamList.begin(); i != node->streamList.end(); ++i)
|
|
csb->dump("\t%d: %d -> %d\n", i, *i, copier.remap[*i]);
|
|
#endif
|
|
|
|
for (StreamType* i = node->internalStreamList.begin(); i != node->internalStreamList.end(); ++i)
|
|
*i = copier.remap[*i];
|
|
}
|
|
|
|
fb_assert(!cursorNumber.specified);
|
|
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* DerivedExprNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNodeStack stack;
|
|
|
|
#ifdef CMP_DEBUG
|
|
csb->dump("expand nod_derived_expr:");
|
|
for (const StreamType* i = streamList.begin(); i != streamList.end(); ++i)
|
|
csb->dump(" %d", *i);
|
|
csb->dump("\n");
|
|
#endif
|
|
|
|
for (StreamType* i = internalStreamList.begin(); i != internalStreamList.end(); ++i)
|
|
{
|
|
CMP_mark_variant(csb, *i);
|
|
CMP_expand_view_nodes(tdbb, csb, *i, stack, blr_dbkey, true);
|
|
}
|
|
|
|
internalStreamList.clear();
|
|
|
|
#ifdef CMP_DEBUG
|
|
for (ExprValueNodeStack::iterator i(stack); i.hasData(); ++i)
|
|
csb->dump(" %d", i.object()->as<RecordKeyNode>()->recStream);
|
|
csb->dump("\n");
|
|
#endif
|
|
|
|
for (ValueExprNodeStack::iterator i(stack); i.hasData(); ++i)
|
|
internalStreamList.add(i.object()->as<RecordKeyNode>()->recStream);
|
|
|
|
return ValueExprNode::pass1(tdbb, csb);
|
|
}
|
|
|
|
ValueExprNode* DerivedExprNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
// As all streams belongs to the same cursor, we use only the first.
|
|
cursorNumber = csb->csb_rpt[internalStreamList[0]].csb_cursor_number;
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* DerivedExprNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
if (cursorNumber.specified)
|
|
request->req_cursors[cursorNumber.value]->checkState(request);
|
|
|
|
dsc* value = NULL;
|
|
|
|
for (const StreamType* i = internalStreamList.begin(); i != internalStreamList.end(); ++i)
|
|
{
|
|
if (request->req_rpb[*i].rpb_number.isValid())
|
|
{
|
|
value = EVL_expr(tdbb, request, arg);
|
|
|
|
if (request->req_flags & req_null)
|
|
value = NULL;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
void DomainValidationNode::print(Firebird::string& text) const
|
|
{
|
|
text.printf("DomainValidationNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* DomainValidationNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (dsqlScratch->domainValue.isUnknown())
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
|
|
Arg::Gds(isc_dsql_domain_err));
|
|
}
|
|
|
|
DomainValidationNode* node = FB_NEW(getPool()) DomainValidationNode(getPool());
|
|
node->domDesc = dsqlScratch->domainValue;
|
|
|
|
return node;
|
|
}
|
|
|
|
void DomainValidationNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_fid);
|
|
dsqlScratch->appendUChar(0); // context
|
|
dsqlScratch->appendUShort(0); // field id
|
|
}
|
|
|
|
void DomainValidationNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
*desc = domDesc;
|
|
}
|
|
|
|
void DomainValidationNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
*desc = domDesc;
|
|
}
|
|
|
|
ValueExprNode* DomainValidationNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
DomainValidationNode* node =
|
|
FB_NEW(*tdbb->getDefaultPool()) DomainValidationNode(*tdbb->getDefaultPool());
|
|
node->domDesc = domDesc;
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* DomainValidationNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* DomainValidationNode::execute(thread_db* /*tdbb*/, jrd_req* request) const
|
|
{
|
|
if (request->req_domain_validation == NULL ||
|
|
(request->req_domain_validation->dsc_flags & DSC_null))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return request->req_domain_validation;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<ExtractNode> regExtractNode(blr_extract);
|
|
|
|
ExtractNode::ExtractNode(MemoryPool& pool, UCHAR aBlrSubOp, ValueExprNode* aArg)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_EXTRACT>(pool),
|
|
blrSubOp(aBlrSubOp),
|
|
arg(aArg)
|
|
{
|
|
addChildNode(arg, arg);
|
|
}
|
|
|
|
DmlNode* ExtractNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
UCHAR blrSubOp = csb->csb_blr_reader.getByte();
|
|
|
|
ExtractNode* node = FB_NEW(pool) ExtractNode(pool, blrSubOp);
|
|
node->arg = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void ExtractNode::print(string& text) const
|
|
{
|
|
text.printf("ExtractNode (%d)", blrSubOp);
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* ExtractNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
// Figure out the data type of the sub parameter, and make
|
|
// sure the requested type of information can be extracted.
|
|
|
|
ValueExprNode* sub1 = doDsqlPass(dsqlScratch, arg);
|
|
MAKE_desc(dsqlScratch, &sub1->nodDesc, sub1);
|
|
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_extract_year:
|
|
case blr_extract_month:
|
|
case blr_extract_day:
|
|
case blr_extract_weekday:
|
|
case blr_extract_yearday:
|
|
case blr_extract_week:
|
|
if (!ExprNode::is<NullNode>(sub1) &&
|
|
sub1->nodDesc.dsc_dtype != dtype_sql_date &&
|
|
sub1->nodDesc.dsc_dtype != dtype_timestamp)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-105) <<
|
|
Arg::Gds(isc_extract_input_mismatch));
|
|
}
|
|
break;
|
|
|
|
case blr_extract_hour:
|
|
case blr_extract_minute:
|
|
case blr_extract_second:
|
|
case blr_extract_millisecond:
|
|
if (!ExprNode::is<NullNode>(sub1) &&
|
|
sub1->nodDesc.dsc_dtype != dtype_sql_time &&
|
|
sub1->nodDesc.dsc_dtype != dtype_timestamp)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-105) <<
|
|
Arg::Gds(isc_extract_input_mismatch));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
break;
|
|
}
|
|
|
|
return FB_NEW(getPool()) ExtractNode(getPool(), blrSubOp, sub1);
|
|
}
|
|
|
|
void ExtractNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "EXTRACT";
|
|
}
|
|
|
|
bool ExtractNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, arg, desc, forceVarChar);
|
|
}
|
|
|
|
void ExtractNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_extract);
|
|
dsqlScratch->appendUChar(blrSubOp);
|
|
GEN_expr(dsqlScratch, arg);
|
|
}
|
|
|
|
void ExtractNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
dsc desc1;
|
|
MAKE_desc(dsqlScratch, &desc1, arg);
|
|
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_extract_second:
|
|
// QUADDATE - maybe this should be DECIMAL(6,4)
|
|
desc->makeLong(ISC_TIME_SECONDS_PRECISION_SCALE);
|
|
break;
|
|
|
|
case blr_extract_millisecond:
|
|
desc->makeLong(ISC_TIME_SECONDS_PRECISION_SCALE + 3);
|
|
break;
|
|
|
|
default:
|
|
desc->makeShort(0);
|
|
break;
|
|
}
|
|
|
|
desc->setNullable(desc1.isNullable());
|
|
}
|
|
|
|
void ExtractNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_extract_second:
|
|
// QUADDATE - SECOND returns a float, or scaled!
|
|
desc->makeLong(ISC_TIME_SECONDS_PRECISION_SCALE);
|
|
break;
|
|
|
|
case blr_extract_millisecond:
|
|
desc->makeLong(ISC_TIME_SECONDS_PRECISION_SCALE + 3);
|
|
break;
|
|
|
|
default:
|
|
desc->makeShort(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ValueExprNode* ExtractNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
ExtractNode* node = FB_NEW(*tdbb->getDefaultPool()) ExtractNode(*tdbb->getDefaultPool(), blrSubOp);
|
|
node->arg = copier.copy(tdbb, arg);
|
|
return node;
|
|
}
|
|
|
|
bool ExtractNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const ExtractNode* o = other->as<ExtractNode>();
|
|
fb_assert(o);
|
|
|
|
return blrSubOp == o->blrSubOp;
|
|
}
|
|
|
|
bool ExtractNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const ExtractNode* const otherNode = other->as<ExtractNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return blrSubOp == otherNode->blrSubOp;
|
|
}
|
|
|
|
ValueExprNode* ExtractNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// Handles EXTRACT(part FROM date/time/timestamp)
|
|
dsc* ExtractNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
const dsc* value = EVL_expr(tdbb, request, arg);
|
|
|
|
if (!value || (request->req_flags & req_null))
|
|
return NULL;
|
|
|
|
impure->vlu_desc.makeShort(0, &impure->vlu_misc.vlu_short);
|
|
|
|
tm times = {0};
|
|
int fractions;
|
|
|
|
switch (value->dsc_dtype)
|
|
{
|
|
case dtype_sql_time:
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_extract_hour:
|
|
case blr_extract_minute:
|
|
case blr_extract_second:
|
|
case blr_extract_millisecond:
|
|
TimeStamp::decode_time(*(GDS_TIME*) value->dsc_address,
|
|
×.tm_hour, ×.tm_min, ×.tm_sec, &fractions);
|
|
break;
|
|
|
|
default:
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_invalid_extractpart_time));
|
|
}
|
|
break;
|
|
|
|
case dtype_sql_date:
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_extract_hour:
|
|
case blr_extract_minute:
|
|
case blr_extract_second:
|
|
case blr_extract_millisecond:
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_invalid_extractpart_date));
|
|
break;
|
|
|
|
default:
|
|
TimeStamp::decode_date(*(GDS_DATE*) value->dsc_address, ×);
|
|
}
|
|
break;
|
|
|
|
case dtype_timestamp:
|
|
TimeStamp::decode_timestamp(*(GDS_TIMESTAMP*) value->dsc_address, ×, &fractions);
|
|
break;
|
|
|
|
default:
|
|
ERR_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_invalidarg_extract));
|
|
break;
|
|
}
|
|
|
|
USHORT part;
|
|
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_extract_year:
|
|
part = times.tm_year + 1900;
|
|
break;
|
|
|
|
case blr_extract_month:
|
|
part = times.tm_mon + 1;
|
|
break;
|
|
|
|
case blr_extract_day:
|
|
part = times.tm_mday;
|
|
break;
|
|
|
|
case blr_extract_hour:
|
|
part = times.tm_hour;
|
|
break;
|
|
|
|
case blr_extract_minute:
|
|
part = times.tm_min;
|
|
break;
|
|
|
|
case blr_extract_second:
|
|
impure->vlu_desc.dsc_dtype = dtype_long;
|
|
impure->vlu_desc.dsc_length = sizeof(ULONG);
|
|
impure->vlu_desc.dsc_scale = ISC_TIME_SECONDS_PRECISION_SCALE;
|
|
impure->vlu_desc.dsc_address = reinterpret_cast<UCHAR*>(&impure->vlu_misc.vlu_long);
|
|
*(ULONG*) impure->vlu_desc.dsc_address = times.tm_sec * ISC_TIME_SECONDS_PRECISION + fractions;
|
|
return &impure->vlu_desc;
|
|
|
|
case blr_extract_millisecond:
|
|
impure->vlu_desc.dsc_dtype = dtype_long;
|
|
impure->vlu_desc.dsc_length = sizeof(ULONG);
|
|
impure->vlu_desc.dsc_scale = ISC_TIME_SECONDS_PRECISION_SCALE + 3;
|
|
impure->vlu_desc.dsc_address = reinterpret_cast<UCHAR*>(&impure->vlu_misc.vlu_long);
|
|
(*(ULONG*) impure->vlu_desc.dsc_address) = fractions;
|
|
return &impure->vlu_desc;
|
|
|
|
case blr_extract_week:
|
|
{
|
|
// Algorithm for Converting Gregorian Dates to ISO 8601 Week Date by Rick McCarty, 1999
|
|
// http://personal.ecu.edu/mccartyr/ISOwdALG.txt
|
|
|
|
const int y = times.tm_year + 1900;
|
|
const int dayOfYearNumber = times.tm_yday + 1;
|
|
|
|
// Find the jan1Weekday for y (Monday=1, Sunday=7)
|
|
const int yy = (y - 1) % 100;
|
|
const int c = (y - 1) - yy;
|
|
const int g = yy + yy / 4;
|
|
const int jan1Weekday = 1 + (((((c / 100) % 4) * 5) + g) % 7);
|
|
|
|
// Find the weekday for y m d
|
|
const int h = dayOfYearNumber + (jan1Weekday - 1);
|
|
const int weekday = 1 + ((h - 1) % 7);
|
|
|
|
// Find if y m d falls in yearNumber y-1, weekNumber 52 or 53
|
|
int yearNumber, weekNumber;
|
|
|
|
if ((dayOfYearNumber <= (8 - jan1Weekday)) && (jan1Weekday > 4))
|
|
{
|
|
yearNumber = y - 1;
|
|
weekNumber = ((jan1Weekday == 5) || ((jan1Weekday == 6) &&
|
|
TimeStamp::isLeapYear(yearNumber))) ? 53 : 52;
|
|
}
|
|
else
|
|
{
|
|
yearNumber = y;
|
|
|
|
// Find if y m d falls in yearNumber y+1, weekNumber 1
|
|
int i = TimeStamp::isLeapYear(y) ? 366 : 365;
|
|
|
|
if ((i - dayOfYearNumber) < (4 - weekday))
|
|
{
|
|
yearNumber = y + 1;
|
|
weekNumber = 1;
|
|
}
|
|
}
|
|
|
|
// Find if y m d falls in yearNumber y, weekNumber 1 through 53
|
|
if (yearNumber == y)
|
|
{
|
|
int j = dayOfYearNumber + (7 - weekday) + (jan1Weekday - 1);
|
|
weekNumber = j / 7;
|
|
if (jan1Weekday > 4)
|
|
weekNumber--;
|
|
}
|
|
|
|
part = weekNumber;
|
|
break;
|
|
}
|
|
|
|
case blr_extract_yearday:
|
|
part = times.tm_yday;
|
|
break;
|
|
|
|
case blr_extract_weekday:
|
|
part = times.tm_wday;
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
part = 0;
|
|
}
|
|
|
|
*(USHORT*) impure->vlu_desc.dsc_address = part;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<FieldNode> regFieldNodeFid(blr_fid);
|
|
static RegisterNode<FieldNode> regFieldNodeField(blr_field);
|
|
|
|
FieldNode::FieldNode(MemoryPool& pool, dsql_ctx* context, dsql_fld* field, ValueListNode* indices)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_FIELD>(pool),
|
|
dsqlQualifier(pool),
|
|
dsqlName(pool),
|
|
dsqlContext(context),
|
|
dsqlField(field),
|
|
dsqlIndices(indices),
|
|
fieldStream(0),
|
|
format(NULL),
|
|
fieldId(0),
|
|
byId(false),
|
|
dsqlCursorField(false)
|
|
{
|
|
}
|
|
|
|
FieldNode::FieldNode(MemoryPool& pool, StreamType stream, USHORT id, bool aById)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_FIELD>(pool),
|
|
dsqlQualifier(pool),
|
|
dsqlName(pool),
|
|
dsqlContext(NULL),
|
|
dsqlField(NULL),
|
|
dsqlIndices(NULL),
|
|
fieldStream(stream),
|
|
format(NULL),
|
|
fieldId(id),
|
|
byId(aById),
|
|
dsqlCursorField(false)
|
|
{
|
|
}
|
|
|
|
// Parse a field.
|
|
DmlNode* FieldNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
|
{
|
|
const USHORT context = csb->csb_blr_reader.getByte();
|
|
|
|
// check if this is a VALUE of domain's check constraint
|
|
if (!csb->csb_domain_validation.isEmpty() && context == 0 &&
|
|
(blrOp == blr_fid || blrOp == blr_field))
|
|
{
|
|
if (blrOp == blr_fid)
|
|
{
|
|
#ifdef DEV_BUILD
|
|
SSHORT id =
|
|
#endif
|
|
csb->csb_blr_reader.getWord();
|
|
fb_assert(id == 0);
|
|
}
|
|
else
|
|
{
|
|
MetaName name;
|
|
PAR_name(csb, name);
|
|
}
|
|
|
|
DomainValidationNode* domNode = FB_NEW(pool) DomainValidationNode(pool);
|
|
MET_get_domain(tdbb, csb->csb_pool, csb->csb_domain_validation, &domNode->domDesc, NULL);
|
|
|
|
// Cast to the target type - see CORE-3545.
|
|
CastNode* castNode = FB_NEW(pool) CastNode(pool);
|
|
castNode->castDesc = domNode->domDesc;
|
|
castNode->source = domNode;
|
|
|
|
return castNode;
|
|
}
|
|
|
|
if (context >= csb->csb_rpt.getCount())/* ||
|
|
!(csb->csb_rpt[context].csb_flags & csb_used) )
|
|
|
|
dimitr: commented out to support system triggers implementing
|
|
WITH CHECK OPTION. They reference the relation stream (#2)
|
|
directly, without a DSQL context. It breaks the layering,
|
|
but we must support legacy BLR.
|
|
*/
|
|
{
|
|
PAR_error(csb, Arg::Gds(isc_ctxnotdef));
|
|
}
|
|
|
|
MetaName name;
|
|
SSHORT id;
|
|
const StreamType stream = csb->csb_rpt[context].csb_stream;
|
|
bool is_column = false;
|
|
bool byId = false;
|
|
|
|
if (blrOp == blr_fid)
|
|
{
|
|
id = csb->csb_blr_reader.getWord();
|
|
byId = true;
|
|
is_column = true;
|
|
}
|
|
else if (blrOp == blr_field)
|
|
{
|
|
CompilerScratch::csb_repeat* tail = &csb->csb_rpt[stream];
|
|
const jrd_prc* procedure = tail->csb_procedure;
|
|
|
|
// make sure procedure has been scanned before using it
|
|
|
|
if (procedure && !procedure->isSubRoutine() &&
|
|
(!(procedure->flags & Routine::FLAG_SCANNED) ||
|
|
(procedure->flags & Routine::FLAG_BEING_SCANNED) ||
|
|
(procedure->flags & Routine::FLAG_BEING_ALTERED)))
|
|
{
|
|
const jrd_prc* scan_proc = MET_procedure(tdbb, procedure->getId(), false, 0);
|
|
|
|
if (scan_proc != procedure)
|
|
procedure = NULL;
|
|
}
|
|
|
|
if (procedure)
|
|
{
|
|
PAR_name(csb, name);
|
|
|
|
if ((id = PAR_find_proc_field(procedure, name)) == -1)
|
|
{
|
|
PAR_error(csb, Arg::Gds(isc_fldnotdef2) <<
|
|
Arg::Str(name) << Arg::Str(procedure->getName().toString()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
jrd_rel* relation = tail->csb_relation;
|
|
if (!relation)
|
|
PAR_error(csb, Arg::Gds(isc_ctxnotdef));
|
|
|
|
// make sure relation has been scanned before using it
|
|
|
|
if (!(relation->rel_flags & REL_scanned) || (relation->rel_flags & REL_being_scanned))
|
|
MET_scan_relation(tdbb, relation);
|
|
|
|
PAR_name(csb, name);
|
|
|
|
if ((id = MET_lookup_field(tdbb, relation, name)) < 0)
|
|
{
|
|
if (csb->csb_g_flags & csb_validation)
|
|
{
|
|
id = 0;
|
|
byId = true;
|
|
is_column = true;
|
|
}
|
|
else
|
|
{
|
|
if (relation->rel_flags & REL_system)
|
|
return FB_NEW(pool) NullNode(pool);
|
|
|
|
if (tdbb->getAttachment()->isGbak())
|
|
{
|
|
PAR_warning(Arg::Warning(isc_fldnotdef) << Arg::Str(name) <<
|
|
Arg::Str(relation->rel_name));
|
|
}
|
|
else if (!(relation->rel_flags & REL_deleted))
|
|
{
|
|
PAR_error(csb, Arg::Gds(isc_fldnotdef) << Arg::Str(name) <<
|
|
Arg::Str(relation->rel_name));
|
|
}
|
|
else
|
|
PAR_error(csb, Arg::Gds(isc_ctxnotdef));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for dependencies -- if a field name was given,
|
|
// use it because when restoring the database the field
|
|
// id's may not be valid yet
|
|
|
|
if (csb->csb_g_flags & csb_get_dependencies)
|
|
{
|
|
if (blrOp == blr_fid)
|
|
PAR_dependency(tdbb, csb, stream, id, "");
|
|
else
|
|
PAR_dependency(tdbb, csb, stream, id, name);
|
|
}
|
|
|
|
if (is_column)
|
|
{
|
|
const jrd_rel* const temp_rel = csb->csb_rpt[stream].csb_relation;
|
|
|
|
if (temp_rel)
|
|
{
|
|
fb_assert(id >= 0);
|
|
|
|
if (!temp_rel->rel_fields || id >= (int) temp_rel->rel_fields->count() ||
|
|
!(*temp_rel->rel_fields)[id])
|
|
{
|
|
if (temp_rel->rel_flags & REL_system)
|
|
return FB_NEW(pool) NullNode(pool);
|
|
}
|
|
}
|
|
}
|
|
|
|
return PAR_gen_field(tdbb, stream, id, byId);
|
|
}
|
|
|
|
void FieldNode::print(string& text) const
|
|
{
|
|
text = "FieldNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* FieldNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (dsqlContext)
|
|
{
|
|
// AB: This is an already processed node. This could be done in expand_select_list.
|
|
return this;
|
|
}
|
|
|
|
if (dsqlScratch->isPsql() && !dsqlQualifier.hasData())
|
|
{
|
|
VariableNode* node = FB_NEW(getPool()) VariableNode(getPool());
|
|
node->line = line;
|
|
node->column = column;
|
|
node->dsqlName = dsqlName;
|
|
return node->dsqlPass(dsqlScratch);
|
|
}
|
|
else
|
|
return internalDsqlPass(dsqlScratch, NULL);
|
|
}
|
|
|
|
// Resolve a field name to an available context.
|
|
// If list is true, then this function can detect and return a relation node if there is no name.
|
|
// This is used for cases of "SELECT <table_name>. ...".
|
|
// CVC: The function attempts to detect if an unqualified field appears in more than one context
|
|
// and hence it returns the number of occurrences. This was added to allow the caller to detect
|
|
// ambiguous commands like select from t1 join t2 on t1.f = t2.f order by common_field.
|
|
// While inoffensive on inner joins, it changes the result on outer joins.
|
|
ValueExprNode* FieldNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, RecordSourceNode** list)
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
if (list)
|
|
*list = NULL;
|
|
|
|
/* CVC: PLEASE READ THIS EXPLANATION IF YOU NEED TO CHANGE THIS CODE.
|
|
You should ensure that this function:
|
|
1.- Never returns NULL. In such case, it such fall back to an invocation
|
|
to PASS1_field_unknown() near the end of this function. None of the multiple callers
|
|
of this function (inside this same module) expect a null pointer, hence they
|
|
will crash the engine in such case.
|
|
2.- Doesn't allocate more than one field in "node". Either you put a break,
|
|
keep the current "continue" or call ALLD_release if you don't want nor the
|
|
continue neither the break if node is already allocated. If it isn't evident,
|
|
but this variable is initialized to zero in the declaration above. You
|
|
may write an explicit line to set it to zero here, before the loop.
|
|
|
|
3.- Doesn't waste cycles if qualifier is not null. The problem is not the cycles
|
|
themselves, but the fact that you'll detect an ambiguity that doesn't exist: if
|
|
the field appears in more than one context but it's always qualified, then
|
|
there's no ambiguity. There's PASS1_make_context() that prevents a context's
|
|
alias from being reused. However, other places in the code don't check that you
|
|
don't create a join or subselect with the same context without disambiguating it
|
|
with different aliases. This is the place where resolveContext() is called for
|
|
that purpose. In the future, it will be fine if we force the use of the alias as
|
|
the only allowed qualifier if the alias exists. Hopefully, we will eliminate
|
|
some day this construction: "select table.field from table t" because it
|
|
should be "t.field" instead.
|
|
|
|
AB: 2004-01-09
|
|
The explained query directly above doesn't work anymore, thus the day has come ;-)
|
|
It's allowed to use the same fieldname between different scope levels (sub-queries)
|
|
without being hit by the ambiguity check. The field uses the first match starting
|
|
from it's own level (of course ambiguity-check on each level is done).
|
|
|
|
4.- Doesn't verify code derived automatically from check constraints. They are
|
|
ill-formed by nature but making that code generation more orthodox is not a
|
|
priority. Typically, they only check a field against a contant. The problem
|
|
appears when they check a field against a subselect, for example. For now,
|
|
allow the user to write ambiguous subselects in check() statements.
|
|
Claudio Valderrama - 2001.1.29.
|
|
*/
|
|
|
|
// Try to resolve field against various contexts;
|
|
// if there is an alias, check only against the first matching
|
|
|
|
ValueExprNode* node = NULL; // This var must be initialized.
|
|
DsqlContextStack ambiguousCtxStack;
|
|
|
|
bool resolveByAlias = true;
|
|
const bool relaxedAliasChecking = Config::getRelaxedAliasChecking();
|
|
|
|
while (true)
|
|
{
|
|
// AB: Loop through the scope_levels starting by its own.
|
|
bool done = false;
|
|
USHORT currentScopeLevel = dsqlScratch->scopeLevel + 1;
|
|
for (; currentScopeLevel > 0 && !done; --currentScopeLevel)
|
|
{
|
|
// If we've found a node we're done.
|
|
if (node)
|
|
break;
|
|
|
|
for (DsqlContextStack::iterator stack(*dsqlScratch->context); stack.hasData(); ++stack)
|
|
{
|
|
dsql_ctx* context = stack.object();
|
|
|
|
if (context->ctx_scope_level != currentScopeLevel - 1 ||
|
|
((context->ctx_flags & CTX_cursor) && dsqlQualifier.isEmpty()) ||
|
|
(!(context->ctx_flags & CTX_cursor) && dsqlCursorField))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dsql_fld* field = resolveContext(dsqlScratch, dsqlQualifier, context, resolveByAlias);
|
|
|
|
// AB: When there's no relation and no procedure then we have a derived table.
|
|
const bool isDerivedTable =
|
|
(!context->ctx_procedure && !context->ctx_relation && context->ctx_rse);
|
|
|
|
if (field)
|
|
{
|
|
// If there's no name then we have most probable an asterisk that
|
|
// needs to be exploded. This should be handled by the caller and
|
|
// when the caller can handle this, list is true.
|
|
if (dsqlName.isEmpty())
|
|
{
|
|
if (list)
|
|
{
|
|
dsql_ctx* stackContext = stack.object();
|
|
|
|
if (context->ctx_relation)
|
|
{
|
|
RelationSourceNode* relNode = FB_NEW(*tdbb->getDefaultPool())
|
|
RelationSourceNode(*tdbb->getDefaultPool());
|
|
relNode->dsqlContext = stackContext;
|
|
*list = relNode;
|
|
}
|
|
else if (context->ctx_procedure)
|
|
{
|
|
ProcedureSourceNode* procNode = FB_NEW(*tdbb->getDefaultPool())
|
|
ProcedureSourceNode(*tdbb->getDefaultPool());
|
|
procNode->dsqlContext = stackContext;
|
|
*list = procNode;
|
|
}
|
|
|
|
fb_assert(*list);
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
NestConst<ValueExprNode> usingField = NULL;
|
|
|
|
for (; field; field = field->fld_next)
|
|
{
|
|
if (field->fld_name == dsqlName.c_str())
|
|
{
|
|
if (dsqlQualifier.isEmpty())
|
|
{
|
|
if (!context->getImplicitJoinField(field->fld_name, usingField))
|
|
{
|
|
field = NULL;
|
|
break;
|
|
}
|
|
|
|
if (usingField)
|
|
field = NULL;
|
|
}
|
|
|
|
ambiguousCtxStack.push(context);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((context->ctx_flags & CTX_view_with_check_store) && !field)
|
|
{
|
|
node = FB_NEW(*tdbb->getDefaultPool()) NullNode(*tdbb->getDefaultPool());
|
|
node->line = line;
|
|
node->column = column;
|
|
}
|
|
else if (dsqlQualifier.hasData() && !field)
|
|
{
|
|
if (!(context->ctx_flags & CTX_view_with_check_modify))
|
|
{
|
|
// If a qualifier was present and we didn't find
|
|
// a matching field then we should stop searching.
|
|
// Column unknown error will be raised at bottom of function.
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (field || usingField)
|
|
{
|
|
// Intercept any reference to a field with datatype that
|
|
// did not exist prior to V6 and post an error
|
|
|
|
// CVC: Stop here if this is our second or third iteration.
|
|
// Anyway, we can't report more than one ambiguity to the status vector.
|
|
// AB: But only if we're on different scope level, because a
|
|
// node inside the same context should have priority.
|
|
if (node)
|
|
continue;
|
|
|
|
ValueListNode* indices = dsqlIndices ?
|
|
doDsqlPass(dsqlScratch, dsqlIndices, false) : NULL;
|
|
|
|
if (context->ctx_flags & CTX_null)
|
|
node = FB_NEW(*tdbb->getDefaultPool()) NullNode(*tdbb->getDefaultPool());
|
|
else if (field)
|
|
node = MAKE_field(context, field, indices);
|
|
else
|
|
node = list ? usingField.getObject() : doDsqlPass(dsqlScratch, usingField, false);
|
|
|
|
node->line = line;
|
|
node->column = column;
|
|
}
|
|
}
|
|
else if (isDerivedTable)
|
|
{
|
|
// if an qualifier is present check if we have the same derived
|
|
// table else continue;
|
|
if (dsqlQualifier.hasData())
|
|
{
|
|
if (context->ctx_alias.hasData())
|
|
{
|
|
if (dsqlQualifier != context->ctx_alias)
|
|
continue;
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
|
|
// If there's no name then we have most probable a asterisk that
|
|
// needs to be exploded. This should be handled by the caller and
|
|
// when the caller can handle this, list is true.
|
|
if (dsqlName.isEmpty())
|
|
{
|
|
if (list)
|
|
{
|
|
// Return node which PASS1_expand_select_node() can deal with it.
|
|
*list = context->ctx_rse;
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Because every select item has an alias we can just walk
|
|
// through the list and return the correct node when found.
|
|
ValueListNode* rseItems = context->ctx_rse->dsqlSelectList;
|
|
NestConst<ValueExprNode>* ptr = rseItems->items.begin();
|
|
|
|
for (const NestConst<ValueExprNode>* const end = rseItems->items.end();
|
|
ptr != end; ++ptr)
|
|
{
|
|
DerivedFieldNode* selectItem = (*ptr)->as<DerivedFieldNode>();
|
|
|
|
// select-item should always be a alias!
|
|
if (selectItem)
|
|
{
|
|
NestConst<ValueExprNode> usingField = NULL;
|
|
|
|
if (dsqlQualifier.isEmpty())
|
|
{
|
|
if (!context->getImplicitJoinField(dsqlName, usingField))
|
|
break;
|
|
}
|
|
|
|
if (dsqlName == selectItem->name || usingField)
|
|
{
|
|
// This is a matching item so add the context to the ambiguous list.
|
|
ambiguousCtxStack.push(context);
|
|
|
|
// Stop here if this is our second or more iteration.
|
|
if (node)
|
|
break;
|
|
|
|
node = usingField ? usingField.getObject() : ptr->getObject();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Internal dsql error: alias type expected by pass1_field
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_dsql_command_err) <<
|
|
Arg::Gds(isc_dsql_derived_alias_field));
|
|
}
|
|
}
|
|
|
|
if (!node && dsqlQualifier.hasData())
|
|
{
|
|
// If a qualifier was present and we didn't find
|
|
// a matching field then we should stop searching.
|
|
// Column unknown error will be raised at bottom of function.
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node)
|
|
break;
|
|
|
|
if (resolveByAlias && !dsqlScratch->checkConstraintTrigger && relaxedAliasChecking)
|
|
resolveByAlias = false;
|
|
else
|
|
break;
|
|
}
|
|
|
|
// CVC: We can't return blindly if this is a check constraint, because there's
|
|
// the possibility of an invalid field that wasn't found. The multiple places that
|
|
// call this function pass1_field() don't expect a NULL pointer, hence will crash.
|
|
// Don't check ambiguity if we don't have a field.
|
|
|
|
if (node && dsqlName.hasData())
|
|
PASS1_ambiguity_check(dsqlScratch, dsqlName, ambiguousCtxStack);
|
|
|
|
// Clean up stack
|
|
ambiguousCtxStack.clear();
|
|
|
|
if (node)
|
|
return node;
|
|
|
|
PASS1_field_unknown(dsqlQualifier.nullStr(), dsqlName.nullStr(), this);
|
|
|
|
// CVC: PASS1_field_unknown() calls ERRD_post() that never returns, so the next line
|
|
// is only to make the compiler happy.
|
|
return NULL;
|
|
}
|
|
|
|
// Attempt to resolve field against context. Return first field in context if successful, NULL if not.
|
|
dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const MetaName& qualifier,
|
|
dsql_ctx* context, bool resolveByAlias)
|
|
{
|
|
// CVC: Warning: the second param, "name" is not used anymore and
|
|
// therefore it was removed. Thus, the local variable "table_name"
|
|
// is being stripped here to avoid mismatches due to trailing blanks.
|
|
|
|
DEV_BLKCHK(dsqlScratch, dsql_type_req);
|
|
DEV_BLKCHK(context, dsql_type_ctx);
|
|
|
|
if ((dsqlScratch->flags & DsqlCompilerScratch::FLAG_RETURNING_INTO) &&
|
|
(context->ctx_flags & CTX_returning))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
dsql_rel* relation = context->ctx_relation;
|
|
dsql_prc* procedure = context->ctx_procedure;
|
|
if (!relation && !procedure)
|
|
return NULL;
|
|
|
|
// if there is no qualifier, then we cannot match against
|
|
// a context of a different scoping level
|
|
// AB: Yes we can, but the scope level where the field is has priority.
|
|
/***
|
|
if (qualifier.isEmpty() && context->ctx_scope_level != dsqlScratch->scopeLevel)
|
|
return NULL;
|
|
***/
|
|
|
|
// AB: If this context is a system generated context as in NEW/OLD inside
|
|
// triggers, the qualifier by the field is mandatory. While we can't
|
|
// fall back from a higher scope-level to the NEW/OLD contexts without
|
|
// the qualifier present.
|
|
// An exception is a check-constraint that is allowed to reference fields
|
|
// without the qualifier.
|
|
if (!dsqlScratch->checkConstraintTrigger && (context->ctx_flags & CTX_system) && qualifier.isEmpty())
|
|
return NULL;
|
|
|
|
const TEXT* table_name = NULL;
|
|
if (context->ctx_internal_alias.hasData() && resolveByAlias)
|
|
table_name = context->ctx_internal_alias.c_str();
|
|
|
|
// AB: For a check constraint we should ignore the alias if the alias
|
|
// contains the "NEW" alias. This is because it is possible
|
|
// to reference a field by the complete table-name as alias
|
|
// (see EMPLOYEE table in examples for a example).
|
|
if (dsqlScratch->checkConstraintTrigger && table_name)
|
|
{
|
|
// If a qualifier is present and it's equal to the alias then we've already the right table-name
|
|
if (!(qualifier.hasData() && qualifier == table_name))
|
|
{
|
|
if (strcmp(table_name, NEW_CONTEXT_NAME) == 0)
|
|
table_name = NULL;
|
|
else if (strcmp(table_name, OLD_CONTEXT_NAME) == 0)
|
|
{
|
|
// Only use the OLD context if it is explicit used. That means the
|
|
// qualifer should hold the "OLD" alias.
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!table_name)
|
|
{
|
|
if (relation)
|
|
table_name = relation->rel_name.c_str();
|
|
else
|
|
table_name = procedure->prc_name.identifier.c_str();
|
|
}
|
|
|
|
// If a context qualifier is present, make sure this is the proper context
|
|
if (qualifier.hasData() && qualifier != table_name)
|
|
return NULL;
|
|
|
|
// Lookup field in relation or procedure
|
|
|
|
return relation ? relation->rel_fields : procedure->prc_outputs;
|
|
}
|
|
|
|
bool FieldNode::dsqlAggregateFinder(AggregateFinder& visitor)
|
|
{
|
|
if (visitor.deepestLevel < dsqlContext->ctx_scope_level)
|
|
visitor.deepestLevel = dsqlContext->ctx_scope_level;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FieldNode::dsqlAggregate2Finder(Aggregate2Finder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FieldNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor)
|
|
{
|
|
// Wouldn't it be better to call an error from this point where return is true?
|
|
// Then we could give the fieldname that's making the trouble.
|
|
|
|
// If we come here then this field is used inside a aggregate-function. The
|
|
// ctx_scope_level gives the info how deep the context is inside the statement.
|
|
|
|
// If the context-scope-level from this field is lower or the same as the scope-level
|
|
// from the given context then it is an invalid field.
|
|
if (dsqlContext->ctx_scope_level == visitor.context->ctx_scope_level)
|
|
{
|
|
// Return true (invalid) if this field isn't inside the GROUP BY clause, that
|
|
// should already been seen in the match_node test in that routine start.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FieldNode::dsqlSubSelectFinder(SubSelectFinder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FieldNode::dsqlFieldFinder(FieldFinder& visitor)
|
|
{
|
|
visitor.field = true;
|
|
|
|
switch (visitor.matchType)
|
|
{
|
|
case FIELD_MATCH_TYPE_EQUAL:
|
|
return dsqlContext->ctx_scope_level == visitor.checkScopeLevel;
|
|
|
|
case FIELD_MATCH_TYPE_LOWER:
|
|
return dsqlContext->ctx_scope_level < visitor.checkScopeLevel;
|
|
|
|
case FIELD_MATCH_TYPE_LOWER_EQUAL:
|
|
return dsqlContext->ctx_scope_level <= visitor.checkScopeLevel;
|
|
|
|
///case FIELD_MATCH_TYPE_HIGHER:
|
|
/// return dsqlContext->ctx_scope_level > visitor.checkScopeLevel;
|
|
|
|
///case FIELD_MATCH_TYPE_HIGHER_EQUAL:
|
|
/// return dsqlContext->ctx_scope_level >= visitor.checkScopeLevel;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ValueExprNode* FieldNode::dsqlFieldRemapper(FieldRemapper& visitor)
|
|
{
|
|
if (dsqlContext->ctx_scope_level == visitor.context->ctx_scope_level)
|
|
{
|
|
return PASS1_post_map(visitor.dsqlScratch, this, visitor.context,
|
|
visitor.partitionNode, visitor.orderNode);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
void FieldNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = dsqlField->fld_name.c_str();
|
|
setParameterInfo(parameter, dsqlContext);
|
|
}
|
|
|
|
// Generate blr for a field - field id's are preferred but not for trigger or view blr.
|
|
void FieldNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (dsqlIndices)
|
|
dsqlScratch->appendUChar(blr_index);
|
|
|
|
if (DDL_ids(dsqlScratch))
|
|
{
|
|
dsqlScratch->appendUChar(blr_fid);
|
|
GEN_stuff_context(dsqlScratch, dsqlContext);
|
|
dsqlScratch->appendUShort(dsqlField->fld_id);
|
|
}
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_field);
|
|
GEN_stuff_context(dsqlScratch, dsqlContext);
|
|
dsqlScratch->appendMetaString(dsqlField->fld_name.c_str());
|
|
}
|
|
|
|
if (dsqlIndices)
|
|
{
|
|
dsqlScratch->appendUChar(dsqlIndices->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* ptr = dsqlIndices->items.begin();
|
|
ptr != dsqlIndices->items.end();
|
|
++ptr)
|
|
{
|
|
GEN_expr(dsqlScratch, *ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FieldNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
if (nodDesc.dsc_dtype)
|
|
*desc = nodDesc;
|
|
else
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-203) <<
|
|
Arg::Gds(isc_dsql_field_ref));
|
|
}
|
|
}
|
|
|
|
bool FieldNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const FieldNode* o = other->as<FieldNode>();
|
|
fb_assert(o);
|
|
|
|
if (dsqlField != o->dsqlField || dsqlContext != o->dsqlContext)
|
|
return false;
|
|
|
|
if (dsqlIndices || o->dsqlIndices)
|
|
return PASS1_node_match(dsqlIndices, o->dsqlIndices, ignoreMapCast);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FieldNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const FieldNode* const otherNode = other->as<FieldNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return fieldId == otherNode->fieldId &&
|
|
(ignoreStreams || fieldStream == otherNode->fieldStream);
|
|
}
|
|
|
|
bool FieldNode::computable(CompilerScratch* csb, StreamType stream,
|
|
bool allowOnlyCurrentStream, ValueExprNode* /*value*/)
|
|
{
|
|
if (allowOnlyCurrentStream)
|
|
{
|
|
if (fieldStream != stream && !(csb->csb_rpt[fieldStream].csb_flags & csb_sub_stream))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (fieldStream == stream)
|
|
return false;
|
|
}
|
|
|
|
return csb->csb_rpt[fieldStream].csb_flags & csb_active;
|
|
}
|
|
|
|
void FieldNode::findDependentFromStreams(const OptimizerRetrieval* optRet, SortedStreamList* streamList)
|
|
{
|
|
// dimitr: OLD/NEW contexts shouldn't create any stream dependencies.
|
|
|
|
if (fieldStream != optRet->stream &&
|
|
(optRet->csb->csb_rpt[fieldStream].csb_flags & csb_active) &&
|
|
!(optRet->csb->csb_rpt[fieldStream].csb_flags & csb_trigger))
|
|
{
|
|
if (!streamList->exist(fieldStream))
|
|
streamList->add(fieldStream);
|
|
}
|
|
}
|
|
|
|
void FieldNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
const Format* const format = CMP_format(tdbb, csb, fieldStream);
|
|
|
|
if (fieldId >= format->fmt_count)
|
|
{
|
|
desc->clear();
|
|
}
|
|
else
|
|
{
|
|
*desc = format->fmt_desc[fieldId];
|
|
desc->dsc_address = NULL;
|
|
|
|
// Fix UNICODE_FSS wrong length used in system tables.
|
|
jrd_rel* relation = csb->csb_rpt[fieldStream].csb_relation;
|
|
|
|
if (relation && (relation->rel_flags & REL_system) &&
|
|
desc->isText() && desc->getCharSet() == CS_UNICODE_FSS)
|
|
{
|
|
USHORT adjust = 0;
|
|
|
|
if (desc->dsc_dtype == dtype_varying)
|
|
adjust = sizeof(USHORT);
|
|
else if (desc->dsc_dtype == dtype_cstring)
|
|
adjust = 1;
|
|
|
|
desc->dsc_length -= adjust;
|
|
desc->dsc_length *= 3;
|
|
desc->dsc_length += adjust;
|
|
}
|
|
}
|
|
}
|
|
|
|
ValueExprNode* FieldNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
USHORT fldId = copier.getFieldId(this);
|
|
StreamType stream = fieldStream;
|
|
|
|
fldId = copier.remapField(stream, fldId);
|
|
|
|
if (copier.remap)
|
|
{
|
|
#ifdef CMP_DEBUG
|
|
csb->dump("remap nod_field: %d -> %d\n", stream, copier.remap[stream]);
|
|
#endif
|
|
stream = copier.remap[stream];
|
|
}
|
|
|
|
fb_assert(!cursorNumber.specified);
|
|
return PAR_gen_field(tdbb, stream, fldId, byId);
|
|
}
|
|
|
|
ValueExprNode* FieldNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
StreamType stream = fieldStream;
|
|
|
|
CMP_mark_variant(csb, stream);
|
|
|
|
CompilerScratch::csb_repeat* tail = &csb->csb_rpt[stream];
|
|
jrd_rel* relation = tail->csb_relation;
|
|
jrd_fld* field;
|
|
|
|
if (!relation || !(field = MET_get_field(relation, fieldId)))
|
|
return ValueExprNode::pass1(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
|
|
const USHORT ttype = INTL_TEXT_TYPE(desc);
|
|
|
|
// Are we using a collation?
|
|
if (TTYPE_TO_COLLATION(ttype) != 0)
|
|
{
|
|
Collation* collation = NULL;
|
|
|
|
try
|
|
{
|
|
ThreadStatusGuard local_status(tdbb);
|
|
collation = INTL_texttype_lookup(tdbb, ttype);
|
|
}
|
|
catch (Exception&)
|
|
{
|
|
// ASF: Swallow the exception if we fail to load the collation here.
|
|
// This allows us to backup databases when the collation isn't available.
|
|
if (!tdbb->getAttachment()->isGbak())
|
|
throw;
|
|
}
|
|
|
|
if (collation)
|
|
CMP_post_resource(&csb->csb_resources, collation, Resource::rsc_collation, ttype);
|
|
}
|
|
|
|
// if this is a modify or store, check REFERENCES access to any foreign keys
|
|
|
|
/* CVC: This is against the SQL standard. REFERENCES should be enforced only at the
|
|
time the FK is defined in DDL, not when a DML is going to be executed.
|
|
if (((tail->csb_flags & csb_modify) || (tail->csb_flags & csb_store)) &&
|
|
!(relation->rel_view_rse || relation->rel_file))
|
|
{
|
|
IDX_check_access(tdbb, csb, tail->csb_view, relation);
|
|
}
|
|
*/
|
|
|
|
// posting the required privilege access to the current relation and field
|
|
|
|
// If this is in a "validate_subtree" then we must not
|
|
// post access checks to the table and the fields in the table.
|
|
// If any node of the parse tree is a nod_validate type node,
|
|
// the nodes in the subtree are involved in a validation
|
|
// clause only, the subtree is a validate_subtree in our notation.
|
|
|
|
const SLONG viewId = tail->csb_view ?
|
|
tail->csb_view->rel_id : (csb->csb_view ? csb->csb_view->rel_id : 0);
|
|
|
|
if (tail->csb_flags & csb_modify)
|
|
{
|
|
if (!csb->csb_validate_expr)
|
|
{
|
|
SecurityClass::flags_t priv = csb->csb_returning_expr ?
|
|
SCL_select : SCL_update;
|
|
CMP_post_access(tdbb, csb, relation->rel_security_name, viewId,
|
|
priv, SCL_object_table, relation->rel_name);
|
|
CMP_post_access(tdbb, csb, field->fld_security_name, viewId,
|
|
priv, SCL_object_column, field->fld_name, relation->rel_name);
|
|
}
|
|
}
|
|
else if (tail->csb_flags & csb_erase)
|
|
{
|
|
CMP_post_access(tdbb, csb, relation->rel_security_name, viewId,
|
|
SCL_delete, SCL_object_table, relation->rel_name);
|
|
}
|
|
else if (tail->csb_flags & csb_store)
|
|
{
|
|
CMP_post_access(tdbb, csb, relation->rel_security_name, viewId,
|
|
SCL_insert, SCL_object_table, relation->rel_name);
|
|
CMP_post_access(tdbb, csb, field->fld_security_name, viewId,
|
|
SCL_insert, SCL_object_column, field->fld_name, relation->rel_name);
|
|
}
|
|
else
|
|
{
|
|
CMP_post_access(tdbb, csb, relation->rel_security_name, viewId,
|
|
SCL_select, SCL_object_table, relation->rel_name);
|
|
CMP_post_access(tdbb, csb, field->fld_security_name, viewId,
|
|
SCL_select, SCL_object_column, field->fld_name, relation->rel_name);
|
|
}
|
|
|
|
ValueExprNode* sub;
|
|
|
|
if (!(sub = field->fld_computation) && !(sub = field->fld_source))
|
|
{
|
|
|
|
if (!relation->rel_view_rse)
|
|
return ValueExprNode::pass1(tdbb, csb);
|
|
|
|
// Msg 364 "cannot access column %s in view %s"
|
|
ERR_post(Arg::Gds(isc_no_field_access) << Arg::Str(field->fld_name) <<
|
|
Arg::Str(relation->rel_name));
|
|
}
|
|
|
|
// The previous test below is an apparent temporary fix
|
|
// put in by Root & Harrison in Summer/Fall 1991.
|
|
// Old Code:
|
|
// if (tail->csb_flags & (csb_view_update | csb_trigger))
|
|
// return ValueExprNode::pass1(tdbb, csb);
|
|
// If the field is a computed field - we'll go on and make
|
|
// the substitution.
|
|
// Comment 1994-August-08 David Schnepper
|
|
|
|
if (tail->csb_flags & (csb_view_update | csb_trigger))
|
|
{
|
|
// dimitr: added an extra check for views, because we don't
|
|
// want their old/new contexts to be substituted
|
|
if (relation->rel_view_rse || !field->fld_computation)
|
|
return ValueExprNode::pass1(tdbb, csb);
|
|
}
|
|
|
|
//StreamType local_map[JrdStatement::MAP_LENGTH];
|
|
AutoPtr<StreamType, ArrayDelete<StreamType> > localMap;
|
|
StreamType* map = tail->csb_map;
|
|
|
|
if (!map)
|
|
{
|
|
localMap = FB_NEW(*tdbb->getDefaultPool()) StreamType[STREAM_MAP_LENGTH];
|
|
map = localMap;
|
|
fb_assert(stream + 2u <= MAX_STREAMS);
|
|
localMap[0] = stream;
|
|
map[1] = stream + 1;
|
|
map[2] = stream + 2;
|
|
}
|
|
|
|
AutoSetRestore<USHORT> autoRemapVariable(&csb->csb_remap_variable,
|
|
(csb->csb_variables ? csb->csb_variables->count() : 0) + 1);
|
|
|
|
sub = NodeCopier::copy(tdbb, csb, sub, map);
|
|
|
|
if (relation->rel_view_rse)
|
|
{
|
|
// dimitr: if we reference view columns, we need to pass them
|
|
// as belonging to a view (in order to compute the access
|
|
// permissions properly).
|
|
AutoSetRestore<jrd_rel*> autoView(&csb->csb_view, relation);
|
|
AutoSetRestore<StreamType> autoViewStream(&csb->csb_view_stream, stream);
|
|
|
|
// ASF: If the view field doesn't reference any of the view streams,
|
|
// evaluate it based on the view dbkey - CORE-1245.
|
|
SortedStreamList streams;
|
|
sub->collectStreams(streams);
|
|
|
|
bool view_refs = false;
|
|
for (FB_SIZE_T i = 0; i < streams.getCount(); i++)
|
|
{
|
|
const CompilerScratch::csb_repeat* const sub_tail = &csb->csb_rpt[streams[i]];
|
|
|
|
if (sub_tail->csb_view && sub_tail->csb_view_stream == stream)
|
|
{
|
|
view_refs = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!view_refs)
|
|
{
|
|
ValueExprNodeStack stack;
|
|
CMP_expand_view_nodes(tdbb, csb, stream, stack, blr_dbkey, true);
|
|
const size_t streamCount = stack.getCount();
|
|
|
|
if (streamCount)
|
|
{
|
|
DerivedExprNode* derivedNode =
|
|
FB_NEW(*tdbb->getDefaultPool()) DerivedExprNode(*tdbb->getDefaultPool());
|
|
derivedNode->arg = sub;
|
|
|
|
for (ValueExprNodeStack::iterator i(stack); i.hasData(); ++i)
|
|
derivedNode->internalStreamList.add(i.object()->as<RecordKeyNode>()->recStream);
|
|
|
|
sub = derivedNode;
|
|
}
|
|
}
|
|
|
|
doPass1(tdbb, csb, &sub); // note: scope of AutoSetRestore
|
|
}
|
|
else
|
|
{
|
|
DerivedExprNode* derivedNode =
|
|
FB_NEW(*tdbb->getDefaultPool()) DerivedExprNode(*tdbb->getDefaultPool());
|
|
derivedNode->arg = sub;
|
|
derivedNode->internalStreamList.add(stream);
|
|
|
|
sub = derivedNode;
|
|
|
|
doPass1(tdbb, csb, &sub);
|
|
}
|
|
|
|
return sub;
|
|
}
|
|
|
|
ValueExprNode* FieldNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
// SMB_SET uses ULONG, not USHORT
|
|
SBM_SET(tdbb->getDefaultPool(), &csb->csb_rpt[fieldStream].csb_fields, fieldId);
|
|
|
|
if (csb->csb_rpt[fieldStream].csb_relation || csb->csb_rpt[fieldStream].csb_procedure)
|
|
format = CMP_format(tdbb, csb, fieldStream);
|
|
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value_ex));
|
|
cursorNumber = csb->csb_rpt[fieldStream].csb_cursor_number;
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* FieldNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
|
|
if (cursorNumber.specified)
|
|
request->req_cursors[cursorNumber.value]->checkState(request);
|
|
|
|
record_param& rpb = request->req_rpb[fieldStream];
|
|
Record* record = rpb.rpb_record;
|
|
jrd_rel* relation = rpb.rpb_relation;
|
|
|
|
// In order to "map a null to a default" value (in EVL_field()), the relation block is referenced.
|
|
// Reference: Bug 10116, 10424
|
|
|
|
if (!EVL_field(relation, record, fieldId, &impure->vlu_desc))
|
|
return NULL;
|
|
|
|
// ASF: CORE-1432 - If the record is not on the latest format, upgrade it.
|
|
// AP: for fields that are missing in original format use record's one.
|
|
if (format &&
|
|
record->rec_format->fmt_version != format->fmt_version &&
|
|
fieldId < format->fmt_desc.getCount() &&
|
|
!DSC_EQUIV(&impure->vlu_desc, &format->fmt_desc[fieldId], true))
|
|
{
|
|
dsc desc = impure->vlu_desc;
|
|
impure->vlu_desc = format->fmt_desc[fieldId];
|
|
|
|
if (impure->vlu_desc.isText())
|
|
{
|
|
// Allocate a string block of sufficient size.
|
|
VaryingString* string = impure->vlu_string;
|
|
|
|
if (string && string->str_length < impure->vlu_desc.dsc_length)
|
|
{
|
|
delete string;
|
|
string = NULL;
|
|
}
|
|
|
|
if (!string)
|
|
{
|
|
string = impure->vlu_string = FB_NEW_RPT(*tdbb->getDefaultPool(),
|
|
impure->vlu_desc.dsc_length) VaryingString();
|
|
string->str_length = impure->vlu_desc.dsc_length;
|
|
}
|
|
|
|
impure->vlu_desc.dsc_address = string->str_data;
|
|
}
|
|
else
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc;
|
|
|
|
MOV_move(tdbb, &desc, &impure->vlu_desc);
|
|
}
|
|
|
|
if (!relation || !(relation->rel_flags & REL_system))
|
|
{
|
|
if (impure->vlu_desc.dsc_dtype == dtype_text)
|
|
INTL_adjust_text_descriptor(tdbb, &impure->vlu_desc);
|
|
}
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<GenIdNode> regGenIdNode(blr_gen_id);
|
|
static RegisterNode<GenIdNode> regGenIdNode2(blr_gen_id2);
|
|
|
|
GenIdNode::GenIdNode(MemoryPool& pool, bool aDialect1,
|
|
const Firebird::MetaName& name,
|
|
ValueExprNode* aArg, bool aImplicit)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_GEN_ID>(pool),
|
|
dialect1(aDialect1),
|
|
generator(pool, name),
|
|
arg(aArg),
|
|
step(0),
|
|
sysGen(false),
|
|
implicit(aImplicit)
|
|
{
|
|
addChildNode(arg, arg);
|
|
}
|
|
|
|
DmlNode* GenIdNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
|
{
|
|
MetaName name;
|
|
PAR_name(csb, name);
|
|
|
|
ValueExprNode* explicitStep = (blrOp == blr_gen_id2) ? NULL : PAR_parse_value(tdbb, csb);
|
|
GenIdNode* const node =
|
|
FB_NEW(pool) GenIdNode(pool, (csb->blrVersion == 4), name, explicitStep,
|
|
(blrOp == blr_gen_id2));
|
|
|
|
// This check seems faster than ==, but assumes the special generator is named ""
|
|
if (name.length() == 0) //(name == MASTER_GENERATOR)
|
|
{
|
|
fb_assert(!MASTER_GENERATOR[0]);
|
|
if (!(csb->csb_g_flags & csb_internal))
|
|
PAR_error(csb, Arg::Gds(isc_gennotdef) << Arg::Str(name));
|
|
|
|
node->generator.id = 0;
|
|
}
|
|
else if (!MET_load_generator(tdbb, node->generator, &node->sysGen, &node->step))
|
|
PAR_error(csb, Arg::Gds(isc_gennotdef) << Arg::Str(name));
|
|
|
|
if (csb->csb_g_flags & csb_get_dependencies)
|
|
{
|
|
CompilerScratch::Dependency dependency(obj_generator);
|
|
dependency.number = node->generator.id;
|
|
csb->csb_dependencies.push(dependency);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void GenIdNode::print(string& text) const
|
|
{
|
|
text.printf("GenIdNode %s (%d)", generator.name.c_str(), (dialect1 ? 1 : 3));
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* GenIdNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
GenIdNode* const node = FB_NEW(getPool())
|
|
GenIdNode(getPool(), dialect1, generator.name, doDsqlPass(dsqlScratch, arg), implicit);
|
|
node->generator = generator;
|
|
node->step = step;
|
|
node->sysGen = sysGen;
|
|
return node;
|
|
}
|
|
|
|
void GenIdNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = (implicit ? "NEXT_VALUE" : "GEN_ID");
|
|
}
|
|
|
|
bool GenIdNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, arg, desc, forceVarChar);
|
|
}
|
|
|
|
void GenIdNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (implicit)
|
|
{
|
|
dsqlScratch->appendUChar(blr_gen_id2);
|
|
dsqlScratch->appendNullString(generator.name.c_str());
|
|
}
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_gen_id);
|
|
dsqlScratch->appendNullString(generator.name.c_str());
|
|
GEN_expr(dsqlScratch, arg);
|
|
}
|
|
}
|
|
|
|
void GenIdNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
if (!implicit)
|
|
{
|
|
dsc desc1;
|
|
MAKE_desc(dsqlScratch, &desc1, arg);
|
|
}
|
|
|
|
if (dialect1)
|
|
desc->makeLong(0);
|
|
else
|
|
desc->makeInt64(0);
|
|
|
|
desc->setNullable(!implicit); // blr_gen_id2 cannot return NULL
|
|
}
|
|
|
|
void GenIdNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
if (dialect1)
|
|
desc->makeLong(0);
|
|
else
|
|
desc->makeInt64(0);
|
|
}
|
|
|
|
ValueExprNode* GenIdNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
GenIdNode* const node = FB_NEW(*tdbb->getDefaultPool()) GenIdNode(
|
|
*tdbb->getDefaultPool(), dialect1, generator.name, copier.copy(tdbb, arg), implicit);
|
|
node->generator = generator;
|
|
node->step = step;
|
|
node->sysGen = sysGen;
|
|
return node;
|
|
}
|
|
|
|
bool GenIdNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const GenIdNode* o = other->as<GenIdNode>();
|
|
fb_assert(o);
|
|
|
|
// I'm not sure if I should include "implicit" in the comparison, but it means different BLR code
|
|
// and nullable v/s not nullable.
|
|
return dialect1 == o->dialect1 && generator.name == o->generator.name &&
|
|
implicit == o->implicit;
|
|
}
|
|
|
|
bool GenIdNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const GenIdNode* const otherNode = other->as<GenIdNode>();
|
|
fb_assert(otherNode);
|
|
|
|
// I'm not sure if I should include "implicit" in the comparison, but it means different BLR code
|
|
// and nullable v/s not nullable.
|
|
return dialect1 == otherNode->dialect1 && generator.id == otherNode->generator.id &&
|
|
implicit == otherNode->implicit;
|
|
}
|
|
|
|
ValueExprNode* GenIdNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass1(tdbb, csb);
|
|
|
|
CMP_post_access(tdbb, csb, generator.secName, 0,
|
|
SCL_usage, SCL_object_generator, generator.name);
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* GenIdNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* GenIdNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
request->req_flags &= ~req_null;
|
|
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
SINT64 change = step;
|
|
|
|
if (!implicit)
|
|
{
|
|
const dsc* const value = EVL_expr(tdbb, request, arg);
|
|
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
change = MOV_get_int64(value, 0);
|
|
}
|
|
|
|
if (sysGen && change != 0)
|
|
{
|
|
if (!request->hasInternalStatement() && !tdbb->getAttachment()->isRWGbak())
|
|
status_exception::raise(Arg::Gds(isc_cant_modify_sysobj) << "generator" << generator.name);
|
|
}
|
|
|
|
const SINT64 new_val = DPM_gen_id(tdbb, generator.id, false, change);
|
|
|
|
if (dialect1)
|
|
impure->make_long((SLONG) new_val);
|
|
else
|
|
impure->make_int64(new_val);
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<InternalInfoNode> regInternalInfoNode(blr_internal_info);
|
|
|
|
// CVC: If this list changes, gpre will need to be updated
|
|
const InternalInfoNode::InfoAttr InternalInfoNode::INFO_TYPE_ATTRIBUTES[MAX_INFO_TYPE] =
|
|
{
|
|
{"<UNKNOWN>", 0},
|
|
{"CURRENT_CONNECTION", 0},
|
|
{"CURRENT_TRANSACTION", 0},
|
|
{"GDSCODE", DsqlCompilerScratch::FLAG_BLOCK},
|
|
{"SQLCODE", DsqlCompilerScratch::FLAG_BLOCK},
|
|
{"ROW_COUNT", DsqlCompilerScratch::FLAG_BLOCK},
|
|
{"INSERTING/UPDATING/DELETING", DsqlCompilerScratch::FLAG_TRIGGER},
|
|
{"SQLSTATE", DsqlCompilerScratch::FLAG_BLOCK}
|
|
};
|
|
|
|
InternalInfoNode::InternalInfoNode(MemoryPool& pool, ValueExprNode* aArg)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_INTERNAL_INFO>(pool),
|
|
arg(aArg)
|
|
{
|
|
addChildNode(arg, arg);
|
|
}
|
|
|
|
DmlNode* InternalInfoNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
InternalInfoNode* node = FB_NEW(pool) InternalInfoNode(pool);
|
|
|
|
const UCHAR* blrOffset = csb->csb_blr_reader.getPos();
|
|
|
|
node->arg = PAR_parse_value(tdbb, csb);
|
|
|
|
LiteralNode* literal = node->arg->as<LiteralNode>();
|
|
|
|
if (!literal || literal->litDesc.dsc_dtype != dtype_long)
|
|
{
|
|
csb->csb_blr_reader.setPos(blrOffset + 1); // PAR_syntax_error seeks 1 backward.
|
|
PAR_syntax_error(csb, "integer literal");
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void InternalInfoNode::print(string& text) const
|
|
{
|
|
text = "InternalInfoNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void InternalInfoNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
SLONG infoType = arg->as<LiteralNode>()->getSlong();
|
|
parameter->par_name = parameter->par_alias = INFO_TYPE_ATTRIBUTES[infoType].alias;
|
|
}
|
|
|
|
void InternalInfoNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_internal_info);
|
|
GEN_expr(dsqlScratch, arg);
|
|
}
|
|
|
|
void InternalInfoNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
InfoType infoType = static_cast<InfoType>(arg->as<LiteralNode>()->getSlong());
|
|
|
|
if (infoType == INFO_TYPE_SQLSTATE)
|
|
desc->makeText(FB_SQLSTATE_LENGTH, ttype_ascii);
|
|
else
|
|
desc->makeLong(0);
|
|
}
|
|
|
|
void InternalInfoNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
fb_assert(arg->is<LiteralNode>());
|
|
|
|
dsc argDesc;
|
|
arg->getDesc(tdbb, csb, &argDesc);
|
|
fb_assert(argDesc.dsc_dtype == dtype_long);
|
|
|
|
InfoType infoType = static_cast<InfoType>(*reinterpret_cast<SLONG*>(argDesc.dsc_address));
|
|
|
|
if (infoType == INFO_TYPE_SQLSTATE)
|
|
desc->makeText(FB_SQLSTATE_LENGTH, ttype_ascii);
|
|
else
|
|
desc->makeLong(0);
|
|
}
|
|
|
|
ValueExprNode* InternalInfoNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
InternalInfoNode* node = FB_NEW(*tdbb->getDefaultPool()) InternalInfoNode(*tdbb->getDefaultPool());
|
|
node->arg = copier.copy(tdbb, arg);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* InternalInfoNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// Return a given element of the internal engine data.
|
|
dsc* InternalInfoNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
const dsc* value = EVL_expr(tdbb, request, arg);
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
fb_assert(value->dsc_dtype == dtype_long);
|
|
InfoType infoType = static_cast<InfoType>(*reinterpret_cast<SLONG*>(value->dsc_address));
|
|
|
|
if (infoType == INFO_TYPE_SQLSTATE)
|
|
{
|
|
FB_SQLSTATE_STRING sqlstate;
|
|
request->req_last_xcp.as_sqlstate(sqlstate);
|
|
|
|
dsc desc;
|
|
desc.makeText(FB_SQLSTATE_LENGTH, ttype_ascii, (UCHAR*) sqlstate);
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
SLONG result = 0;
|
|
|
|
switch (infoType)
|
|
{
|
|
case INFO_TYPE_CONNECTION_ID:
|
|
result = PAG_attachment_id(tdbb);
|
|
break;
|
|
case INFO_TYPE_TRANSACTION_ID:
|
|
//fb_assert(sizeof(result) == sizeof(tdbb->getTransaction()->tra_number));
|
|
// Conversion from unsigned to SLONG, big values will be reported as negative.
|
|
result = tdbb->getTransaction()->tra_number;
|
|
break;
|
|
case INFO_TYPE_GDSCODE:
|
|
result = request->req_last_xcp.as_gdscode();
|
|
break;
|
|
case INFO_TYPE_SQLCODE:
|
|
result = request->req_last_xcp.as_sqlcode();
|
|
break;
|
|
case INFO_TYPE_ROWS_AFFECTED:
|
|
// CVC: Not sure if this counter can overflow in extreme cases
|
|
result = request->req_records_affected.getCount();
|
|
break;
|
|
case INFO_TYPE_TRIGGER_ACTION:
|
|
result = request->req_trigger_action;
|
|
break;
|
|
default:
|
|
BUGCHECK(232); // msg 232 EVL_expr: invalid operation
|
|
}
|
|
|
|
dsc desc;
|
|
desc.makeLong(0, &result);
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
ValueExprNode* InternalInfoNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
SLONG infoType = arg->as<LiteralNode>()->getSlong();
|
|
const InfoAttr& attr = INFO_TYPE_ATTRIBUTES[infoType];
|
|
|
|
if (attr.mask && !(dsqlScratch->flags & attr.mask))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
// Token unknown
|
|
Arg::Gds(isc_token_err) <<
|
|
Arg::Gds(isc_random) << attr.alias);
|
|
}
|
|
|
|
return FB_NEW(getPool()) InternalInfoNode(getPool(), doDsqlPass(dsqlScratch, arg));
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<LiteralNode> regLiteralNode(blr_literal);
|
|
|
|
LiteralNode::LiteralNode(MemoryPool& pool)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_LITERAL>(pool),
|
|
dsqlStr(NULL)
|
|
{
|
|
litDesc.clear();
|
|
}
|
|
|
|
// Parse a literal value.
|
|
DmlNode* LiteralNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
LiteralNode* node = FB_NEW(pool) LiteralNode(pool);
|
|
|
|
PAR_desc(tdbb, csb, &node->litDesc);
|
|
|
|
UCHAR* p = FB_NEW(csb->csb_pool) UCHAR[node->litDesc.dsc_length];
|
|
node->litDesc.dsc_address = p;
|
|
node->litDesc.dsc_flags = 0;
|
|
const UCHAR* q = csb->csb_blr_reader.getPos();
|
|
USHORT l = node->litDesc.dsc_length;
|
|
|
|
switch (node->litDesc.dsc_dtype)
|
|
{
|
|
case dtype_short:
|
|
l = 2;
|
|
*(SSHORT*) p = (SSHORT) gds__vax_integer(q, l);
|
|
break;
|
|
|
|
case dtype_long:
|
|
case dtype_sql_date:
|
|
case dtype_sql_time:
|
|
l = 4;
|
|
*(SLONG*) p = gds__vax_integer(q, l);
|
|
break;
|
|
|
|
case dtype_timestamp:
|
|
l = 8;
|
|
*(SLONG*) p = gds__vax_integer(q, 4);
|
|
p += 4;
|
|
q += 4;
|
|
*(SLONG*) p = gds__vax_integer(q, 4);
|
|
break;
|
|
|
|
case dtype_int64:
|
|
l = sizeof(SINT64);
|
|
*(SINT64*) p = isc_portable_integer(q, l);
|
|
break;
|
|
|
|
case dtype_double:
|
|
{
|
|
SSHORT scale;
|
|
UCHAR dtype;
|
|
|
|
// The double literal could potentially be used for any numeric literal - the value is
|
|
// passed as if it were a text string. Convert the numeric string to its binary value
|
|
// (int64, long or double as appropriate).
|
|
|
|
l = csb->csb_blr_reader.getWord();
|
|
q = csb->csb_blr_reader.getPos();
|
|
dtype = CVT_get_numeric(q, l, &scale, (double*) p);
|
|
node->litDesc.dsc_dtype = dtype;
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_double:
|
|
node->litDesc.dsc_length = sizeof(double);
|
|
break;
|
|
case dtype_long:
|
|
node->litDesc.dsc_length = sizeof(SLONG);
|
|
node->litDesc.dsc_scale = (SCHAR) scale;
|
|
break;
|
|
default:
|
|
node->litDesc.dsc_length = sizeof(SINT64);
|
|
node->litDesc.dsc_scale = (SCHAR) scale;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case dtype_text:
|
|
memcpy(p, q, l);
|
|
break;
|
|
|
|
case dtype_boolean:
|
|
l = 1;
|
|
*p = *q;
|
|
break;
|
|
|
|
default:
|
|
fb_assert(FALSE);
|
|
}
|
|
|
|
csb->csb_blr_reader.seekForward(l);
|
|
|
|
return node;
|
|
}
|
|
|
|
// Generate BLR for a constant.
|
|
void LiteralNode::genConstant(DsqlCompilerScratch* dsqlScratch, const dsc* desc, bool negateValue)
|
|
{
|
|
SLONG value;
|
|
SINT64 i64value;
|
|
|
|
dsqlScratch->appendUChar(blr_literal);
|
|
|
|
const UCHAR* p = desc->dsc_address;
|
|
|
|
switch (desc->dsc_dtype)
|
|
{
|
|
case dtype_short:
|
|
GEN_descriptor(dsqlScratch, desc, true);
|
|
value = *(SSHORT*) p;
|
|
if (negateValue)
|
|
value = -value;
|
|
dsqlScratch->appendUShort(value);
|
|
break;
|
|
|
|
case dtype_long:
|
|
GEN_descriptor(dsqlScratch, desc, true);
|
|
value = *(SLONG*) p;
|
|
if (negateValue)
|
|
value = -value;
|
|
dsqlScratch->appendUShort(value);
|
|
dsqlScratch->appendUShort(value >> 16);
|
|
break;
|
|
|
|
case dtype_sql_time:
|
|
case dtype_sql_date:
|
|
GEN_descriptor(dsqlScratch, desc, true);
|
|
value = *(SLONG*) p;
|
|
dsqlScratch->appendUShort(value);
|
|
dsqlScratch->appendUShort(value >> 16);
|
|
break;
|
|
|
|
case dtype_double:
|
|
{
|
|
// this is used for approximate/large numeric literal
|
|
// which is transmitted to the engine as a string.
|
|
|
|
GEN_descriptor(dsqlScratch, desc, true);
|
|
// Length of string literal, cast because it could be > 127 bytes.
|
|
const USHORT l = (USHORT)(UCHAR) desc->dsc_scale;
|
|
|
|
if (negateValue)
|
|
{
|
|
dsqlScratch->appendUShort(l + 1);
|
|
dsqlScratch->appendUChar('-');
|
|
}
|
|
else
|
|
dsqlScratch->appendUShort(l);
|
|
|
|
if (l)
|
|
dsqlScratch->appendBytes(p, l);
|
|
|
|
break;
|
|
}
|
|
|
|
case dtype_int64:
|
|
i64value = *(SINT64*) p;
|
|
|
|
if (negateValue)
|
|
i64value = -i64value;
|
|
else if (i64value == MIN_SINT64)
|
|
{
|
|
// UH OH!
|
|
// yylex correctly recognized the digits as the most-negative
|
|
// possible INT64 value, but unfortunately, there was no
|
|
// preceding '-' (a fact which the lexer could not know).
|
|
// The value is too big for a positive INT64 value, and it
|
|
// didn't contain an exponent so it's not a valid DOUBLE
|
|
// PRECISION literal either, so we have to bounce it.
|
|
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_numeric_out_of_range));
|
|
}
|
|
|
|
// We and the lexer both agree that this is an SINT64 constant,
|
|
// and if the value needed to be negated, it already has been.
|
|
// If the value will fit into a 32-bit signed integer, generate
|
|
// it that way, else as an INT64.
|
|
|
|
if ((i64value >= (SINT64) MIN_SLONG) && (i64value <= (SINT64) MAX_SLONG))
|
|
{
|
|
dsqlScratch->appendUChar(blr_long);
|
|
dsqlScratch->appendUChar(desc->dsc_scale);
|
|
dsqlScratch->appendUShort(i64value);
|
|
dsqlScratch->appendUShort(i64value >> 16);
|
|
}
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_int64);
|
|
dsqlScratch->appendUChar(desc->dsc_scale);
|
|
dsqlScratch->appendUShort(i64value);
|
|
dsqlScratch->appendUShort(i64value >> 16);
|
|
dsqlScratch->appendUShort(i64value >> 32);
|
|
dsqlScratch->appendUShort(i64value >> 48);
|
|
}
|
|
break;
|
|
|
|
case dtype_quad:
|
|
case dtype_blob:
|
|
case dtype_array:
|
|
case dtype_timestamp:
|
|
GEN_descriptor(dsqlScratch, desc, true);
|
|
value = *(SLONG*) p;
|
|
dsqlScratch->appendUShort(value);
|
|
dsqlScratch->appendUShort(value >> 16);
|
|
value = *(SLONG*) (p + 4);
|
|
dsqlScratch->appendUShort(value);
|
|
dsqlScratch->appendUShort(value >> 16);
|
|
break;
|
|
|
|
case dtype_text:
|
|
{
|
|
const USHORT length = desc->dsc_length;
|
|
|
|
GEN_descriptor(dsqlScratch, desc, true);
|
|
if (length)
|
|
dsqlScratch->appendBytes(p, length);
|
|
|
|
break;
|
|
}
|
|
|
|
case dtype_boolean:
|
|
GEN_descriptor(dsqlScratch, desc, false);
|
|
dsqlScratch->appendUChar(*p != 0);
|
|
break;
|
|
|
|
default:
|
|
// gen_constant: datatype not understood
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-103) <<
|
|
Arg::Gds(isc_dsql_constant_err));
|
|
}
|
|
}
|
|
|
|
void LiteralNode::print(string& text) const
|
|
{
|
|
text.printf("LiteralNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
// Turn an international string reference into internal subtype ID.
|
|
ValueExprNode* LiteralNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
if (dsqlScratch->inOuterJoin)
|
|
litDesc.dsc_flags = DSC_nullable;
|
|
|
|
if (litDesc.dsc_dtype > dtype_any_text)
|
|
return this;
|
|
|
|
LiteralNode* constant = FB_NEW(getPool()) LiteralNode(getPool());
|
|
constant->dsqlStr = dsqlStr;
|
|
constant->litDesc = litDesc;
|
|
|
|
if (dsqlStr && dsqlStr->getCharSet().hasData())
|
|
{
|
|
const dsql_intlsym* resolved = METD_get_charset(dsqlScratch->getTransaction(),
|
|
dsqlStr->getCharSet().length(), dsqlStr->getCharSet().c_str());
|
|
|
|
if (!resolved)
|
|
{
|
|
// character set name is not defined
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
|
|
Arg::Gds(isc_charset_not_found) << dsqlStr->getCharSet());
|
|
}
|
|
|
|
constant->litDesc.setTextType(resolved->intlsym_ttype);
|
|
}
|
|
else
|
|
{
|
|
const MetaName charSetName = METD_get_charset_name(
|
|
dsqlScratch->getTransaction(), constant->litDesc.getCharSet());
|
|
|
|
const dsql_intlsym* sym = METD_get_charset(dsqlScratch->getTransaction(),
|
|
charSetName.length(), charSetName.c_str());
|
|
fb_assert(sym);
|
|
|
|
if (sym)
|
|
constant->litDesc.setTextType(sym->intlsym_ttype);
|
|
}
|
|
|
|
USHORT adjust = 0;
|
|
|
|
if (constant->litDesc.dsc_dtype == dtype_varying)
|
|
adjust = sizeof(USHORT);
|
|
else if (constant->litDesc.dsc_dtype == dtype_cstring)
|
|
adjust = 1;
|
|
|
|
constant->litDesc.dsc_length -= adjust;
|
|
|
|
CharSet* charSet = INTL_charset_lookup(tdbb, INTL_GET_CHARSET(&constant->litDesc));
|
|
|
|
if (!charSet->wellFormed(dsqlStr->getString().length(), constant->litDesc.dsc_address, NULL))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_malformed_string));
|
|
}
|
|
else
|
|
{
|
|
constant->litDesc.dsc_length =
|
|
charSet->length(dsqlStr->getString().length(), constant->litDesc.dsc_address, true) *
|
|
charSet->maxBytesPerChar();
|
|
}
|
|
|
|
constant->litDesc.dsc_length += adjust;
|
|
|
|
return constant;
|
|
}
|
|
|
|
void LiteralNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "CONSTANT";
|
|
}
|
|
|
|
bool LiteralNode::setParameterType(DsqlCompilerScratch* /*dsqlScratch*/,
|
|
const dsc* /*desc*/, bool /*forceVarChar*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void LiteralNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (litDesc.dsc_dtype == dtype_text)
|
|
litDesc.dsc_length = dsqlStr->getString().length();
|
|
|
|
genConstant(dsqlScratch, &litDesc, false);
|
|
}
|
|
|
|
void LiteralNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
*desc = litDesc;
|
|
}
|
|
|
|
void LiteralNode::getDesc(thread_db* tdbb, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
*desc = litDesc;
|
|
|
|
// ASF: I expect only dtype_text could occur here.
|
|
// But I'll treat all string types for sure.
|
|
if (DTYPE_IS_TEXT(desc->dsc_dtype))
|
|
{
|
|
const UCHAR* p;
|
|
USHORT adjust = 0;
|
|
|
|
if (desc->dsc_dtype == dtype_varying)
|
|
{
|
|
p = desc->dsc_address + sizeof(USHORT);
|
|
adjust = sizeof(USHORT);
|
|
}
|
|
else
|
|
{
|
|
p = desc->dsc_address;
|
|
|
|
if (desc->dsc_dtype == dtype_cstring)
|
|
adjust = 1;
|
|
}
|
|
|
|
// Do the same thing which DSQL does.
|
|
// Increase descriptor size to evaluate dependent expressions correctly.
|
|
CharSet* cs = INTL_charset_lookup(tdbb, desc->getCharSet());
|
|
desc->dsc_length = (cs->length(desc->dsc_length - adjust, p, true) *
|
|
cs->maxBytesPerChar()) + adjust;
|
|
}
|
|
}
|
|
|
|
ValueExprNode* LiteralNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
LiteralNode* node = FB_NEW(*tdbb->getDefaultPool()) LiteralNode(*tdbb->getDefaultPool());
|
|
node->litDesc = litDesc;
|
|
|
|
UCHAR* p = FB_NEW(*tdbb->getDefaultPool()) UCHAR[node->litDesc.dsc_length];
|
|
node->litDesc.dsc_address = p;
|
|
|
|
memcpy(p, litDesc.dsc_address, litDesc.dsc_length);
|
|
|
|
return node;
|
|
}
|
|
|
|
bool LiteralNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const LiteralNode* o = other->as<LiteralNode>();
|
|
fb_assert(o);
|
|
|
|
if (!DSC_EQUIV(&litDesc, &o->litDesc, true))
|
|
return false;
|
|
|
|
const USHORT len = (litDesc.dsc_dtype == dtype_text) ?
|
|
(USHORT) dsqlStr->getString().length() : litDesc.dsc_length;
|
|
return memcmp(litDesc.dsc_address, o->litDesc.dsc_address, len) == 0;
|
|
}
|
|
|
|
bool LiteralNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const LiteralNode* const otherNode = other->as<LiteralNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return DSC_EQUIV(&litDesc, &otherNode->litDesc, true) &&
|
|
memcmp(litDesc.dsc_address, otherNode->litDesc.dsc_address, litDesc.dsc_length) == 0;
|
|
}
|
|
|
|
ValueExprNode* LiteralNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* LiteralNode::execute(thread_db* /*tdbb*/, jrd_req* /*request*/) const
|
|
{
|
|
return const_cast<dsc*>(&litDesc);
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
void DsqlAliasNode::print(string& text) const
|
|
{
|
|
text.printf("DsqlAliasNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* DsqlAliasNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
DsqlAliasNode* node = FB_NEW(getPool()) DsqlAliasNode(getPool(), name,
|
|
doDsqlPass(dsqlScratch, value));
|
|
MAKE_desc(dsqlScratch, &node->value->nodDesc, node->value);
|
|
return node;
|
|
}
|
|
|
|
void DsqlAliasNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
value->setParameterName(parameter);
|
|
parameter->par_alias = name;
|
|
}
|
|
|
|
void DsqlAliasNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
GEN_expr(dsqlScratch, value);
|
|
}
|
|
|
|
void DsqlAliasNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, value);
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
DsqlMapNode::DsqlMapNode(MemoryPool& pool, dsql_ctx* aContext, dsql_map* aMap)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_MAP>(pool),
|
|
context(aContext),
|
|
map(aMap)
|
|
{
|
|
}
|
|
|
|
void DsqlMapNode::print(string& text) const
|
|
{
|
|
text.printf("DsqlMapNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* DsqlMapNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/)
|
|
{
|
|
return FB_NEW(getPool()) DsqlMapNode(getPool(), context, map);
|
|
}
|
|
|
|
bool DsqlMapNode::dsqlAggregateFinder(AggregateFinder& visitor)
|
|
{
|
|
if (visitor.window)
|
|
return false;
|
|
|
|
if (context->ctx_scope_level == visitor.dsqlScratch->scopeLevel)
|
|
return true;
|
|
|
|
return visitor.visit(map->map_node);
|
|
}
|
|
|
|
bool DsqlMapNode::dsqlAggregate2Finder(Aggregate2Finder& visitor)
|
|
{
|
|
return visitor.visit(map->map_node);
|
|
}
|
|
|
|
bool DsqlMapNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor)
|
|
{
|
|
// If that map is of the current scopeLevel, we prevent the visiting of the aggregate
|
|
// expression. This is because a field embedded in an aggregate function is valid even
|
|
// not being in the group by list. Examples:
|
|
// select count(n) from table group by m
|
|
// select count(n) from table
|
|
|
|
AutoSetRestore<bool> autoInsideOwnMap(&visitor.insideOwnMap,
|
|
context->ctx_scope_level == visitor.context->ctx_scope_level);
|
|
|
|
// If the context scope is greater than our own, someone should have already inspected
|
|
// nested aggregates, so set insideHigherMap to true.
|
|
|
|
AutoSetRestore<bool> autoInsideHigherMap(&visitor.insideHigherMap,
|
|
context->ctx_scope_level > visitor.context->ctx_scope_level);
|
|
|
|
return visitor.visit(map->map_node);
|
|
}
|
|
|
|
bool DsqlMapNode::dsqlSubSelectFinder(SubSelectFinder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool DsqlMapNode::dsqlFieldFinder(FieldFinder& visitor)
|
|
{
|
|
return visitor.visit(map->map_node);
|
|
}
|
|
|
|
ValueExprNode* DsqlMapNode::dsqlFieldRemapper(FieldRemapper& visitor)
|
|
{
|
|
if (context->ctx_scope_level != visitor.context->ctx_scope_level)
|
|
{
|
|
AutoSetRestore<USHORT> autoCurrentLevel(&visitor.currentLevel, context->ctx_scope_level);
|
|
doDsqlFieldRemapper(visitor, map->map_node);
|
|
}
|
|
|
|
if (visitor.window && context->ctx_scope_level == visitor.context->ctx_scope_level)
|
|
{
|
|
return PASS1_post_map(visitor.dsqlScratch, this,
|
|
visitor.context, visitor.partitionNode, visitor.orderNode);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
void DsqlMapNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
const ValueExprNode* nestNode = map->map_node;
|
|
const DsqlMapNode* mapNode;
|
|
|
|
while ((mapNode = ExprNode::as<DsqlMapNode>(nestNode)))
|
|
{
|
|
// Skip all the DsqlMapNodes.
|
|
nestNode = mapNode->map->map_node;
|
|
}
|
|
|
|
const char* nameAlias = NULL;
|
|
const FieldNode* fieldNode = NULL;
|
|
const ValueExprNode* alias;
|
|
|
|
const AggNode* aggNode;
|
|
const DsqlAliasNode* aliasNode;
|
|
const LiteralNode* literalNode;
|
|
const DerivedFieldNode* derivedField;
|
|
const RecordKeyNode* dbKeyNode;
|
|
|
|
if ((aggNode = ExprNode::as<AggNode>(nestNode)))
|
|
aggNode->setParameterName(parameter);
|
|
else if ((aliasNode = ExprNode::as<DsqlAliasNode>(nestNode)))
|
|
{
|
|
parameter->par_alias = aliasNode->name;
|
|
alias = aliasNode->value;
|
|
fieldNode = ExprNode::as<FieldNode>(alias);
|
|
}
|
|
else if ((literalNode = ExprNode::as<LiteralNode>(nestNode)))
|
|
literalNode->setParameterName(parameter);
|
|
else if ((dbKeyNode = ExprNode::as<RecordKeyNode>(nestNode)))
|
|
nameAlias = dbKeyNode->getAlias(false);
|
|
else if ((derivedField = ExprNode::as<DerivedFieldNode>(nestNode)))
|
|
{
|
|
parameter->par_alias = derivedField->name;
|
|
alias = derivedField->value;
|
|
fieldNode = ExprNode::as<FieldNode>(alias);
|
|
}
|
|
else if ((fieldNode = ExprNode::as<FieldNode>(nestNode)))
|
|
nameAlias = fieldNode->dsqlField->fld_name.c_str();
|
|
|
|
const dsql_ctx* context = NULL;
|
|
const dsql_fld* field;
|
|
|
|
if (fieldNode)
|
|
{
|
|
context = fieldNode->dsqlContext;
|
|
field = fieldNode->dsqlField;
|
|
parameter->par_name = field->fld_name.c_str();
|
|
}
|
|
|
|
if (nameAlias)
|
|
parameter->par_name = parameter->par_alias = nameAlias;
|
|
|
|
setParameterInfo(parameter, context);
|
|
}
|
|
|
|
void DsqlMapNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_fid);
|
|
|
|
if (map->map_partition)
|
|
dsqlScratch->appendUChar(map->map_partition->context);
|
|
else
|
|
GEN_stuff_context(dsqlScratch, context);
|
|
|
|
dsqlScratch->appendUShort(map->map_position);
|
|
}
|
|
|
|
void DsqlMapNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, map->map_node);
|
|
|
|
// ASF: We should mark nod_agg_count as nullable when it's in an outer join - CORE-2660.
|
|
if (context->ctx_flags & CTX_outer_join)
|
|
desc->setNullable(true);
|
|
}
|
|
|
|
bool DsqlMapNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
const DsqlMapNode* o = other->as<DsqlMapNode>();
|
|
return o && PASS1_node_match(map->map_node, o->map->map_node, ignoreMapCast);
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
DerivedFieldNode::DerivedFieldNode(MemoryPool& pool, const MetaName& aName, USHORT aScope,
|
|
ValueExprNode* aValue)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_DERIVED_FIELD>(pool),
|
|
name(aName),
|
|
scope(aScope),
|
|
value(aValue),
|
|
context(NULL)
|
|
{
|
|
addDsqlChildNode(value);
|
|
}
|
|
|
|
void DerivedFieldNode::print(string& text) const
|
|
{
|
|
text.printf("DerivedFieldNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* DerivedFieldNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
bool DerivedFieldNode::dsqlAggregateFinder(AggregateFinder& visitor)
|
|
{
|
|
// This is a derived table, so don't look further, but don't forget to check for the
|
|
// deepest scope level.
|
|
|
|
if (visitor.deepestLevel < scope)
|
|
visitor.deepestLevel = scope;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DerivedFieldNode::dsqlAggregate2Finder(Aggregate2Finder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool DerivedFieldNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor)
|
|
{
|
|
if (scope == visitor.context->ctx_scope_level)
|
|
return true;
|
|
|
|
if (visitor.context->ctx_scope_level < scope)
|
|
return visitor.visit(value);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DerivedFieldNode::dsqlSubSelectFinder(SubSelectFinder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool DerivedFieldNode::dsqlFieldFinder(FieldFinder& visitor)
|
|
{
|
|
// This is a "virtual" field
|
|
visitor.field = true;
|
|
|
|
const USHORT dfScopeLevel = scope;
|
|
|
|
switch (visitor.matchType)
|
|
{
|
|
case FIELD_MATCH_TYPE_EQUAL:
|
|
return dfScopeLevel == visitor.checkScopeLevel;
|
|
|
|
case FIELD_MATCH_TYPE_LOWER:
|
|
return dfScopeLevel < visitor.checkScopeLevel;
|
|
|
|
case FIELD_MATCH_TYPE_LOWER_EQUAL:
|
|
return dfScopeLevel <= visitor.checkScopeLevel;
|
|
|
|
///case FIELD_MATCH_TYPE_HIGHER:
|
|
/// return dfScopeLevel > visitor.checkScopeLevel;
|
|
|
|
///case FIELD_MATCH_TYPE_HIGHER_EQUAL:
|
|
/// return dfScopeLevel >= visitor.checkScopeLevel;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ValueExprNode* DerivedFieldNode::dsqlFieldRemapper(FieldRemapper& visitor)
|
|
{
|
|
// If we got a field from a derived table we should not remap anything
|
|
// deeper in the alias, but this "virtual" field should be mapped to
|
|
// the given context (of course only if we're in the same scope-level).
|
|
|
|
if (scope == visitor.context->ctx_scope_level)
|
|
{
|
|
return PASS1_post_map(visitor.dsqlScratch, this,
|
|
visitor.context, visitor.partitionNode, visitor.orderNode);
|
|
}
|
|
else if (visitor.context->ctx_scope_level < scope)
|
|
doDsqlFieldRemapper(visitor, value);
|
|
|
|
return this;
|
|
}
|
|
|
|
void DerivedFieldNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
const dsql_ctx* context = NULL;
|
|
const FieldNode* fieldNode;
|
|
const RecordKeyNode* dbKeyNode;
|
|
|
|
if ((fieldNode = value->as<FieldNode>()))
|
|
{
|
|
parameter->par_name = fieldNode->dsqlField->fld_name.c_str();
|
|
context = fieldNode->dsqlContext;
|
|
}
|
|
else if ((dbKeyNode = value->as<RecordKeyNode>()))
|
|
dbKeyNode->setParameterName(parameter);
|
|
|
|
parameter->par_alias = name;
|
|
setParameterInfo(parameter, context);
|
|
}
|
|
|
|
void DerivedFieldNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
// ASF: If we are not referencing a field, we should evaluate the expression based on
|
|
// a set (ORed) of contexts. If any of them are in a valid position the expression is
|
|
// evaluated, otherwise a NULL will be returned. This is fix for CORE-1246.
|
|
// Note that the field may be enclosed by an alias.
|
|
|
|
ValueExprNode* val = value;
|
|
|
|
while (val->is<DsqlAliasNode>())
|
|
val = val->as<DsqlAliasNode>()->value;
|
|
|
|
if (!val->is<FieldNode>() && !val->is<DerivedFieldNode>() &&
|
|
!val->is<RecordKeyNode>() && !val->is<DsqlMapNode>())
|
|
{
|
|
if (context->ctx_main_derived_contexts.hasData())
|
|
{
|
|
HalfStaticArray<USHORT, 4> derivedContexts;
|
|
|
|
for (DsqlContextStack::const_iterator stack(context->ctx_main_derived_contexts);
|
|
stack.hasData(); ++stack)
|
|
{
|
|
const dsql_ctx* const derivedContext = stack.object();
|
|
|
|
if (derivedContext->ctx_win_maps.hasData())
|
|
{
|
|
for (const PartitionMap* const* iter = derivedContext->ctx_win_maps.begin();
|
|
iter != derivedContext->ctx_win_maps.end(); ++iter)
|
|
{
|
|
// bottleneck
|
|
fb_assert((*iter)->context <= MAX_UCHAR);
|
|
derivedContexts.add((*iter)->context);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// bottleneck
|
|
fb_assert(derivedContext->ctx_context <= MAX_UCHAR);
|
|
derivedContexts.add(derivedContext->ctx_context);
|
|
}
|
|
}
|
|
|
|
const FB_SIZE_T derivedContextsCount = derivedContexts.getCount();
|
|
|
|
if (derivedContextsCount > MAX_UCHAR)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_imp_exc) <<
|
|
Arg::Gds(isc_ctx_too_big));
|
|
}
|
|
|
|
dsqlScratch->appendUChar(blr_derived_expr);
|
|
dsqlScratch->appendUChar(derivedContextsCount);
|
|
|
|
for (FB_SIZE_T i = 0; i < derivedContextsCount; i++)
|
|
dsqlScratch->appendUChar(derivedContexts[i]);
|
|
}
|
|
}
|
|
else if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_FETCH) &&
|
|
!(context->ctx_flags & CTX_system) &&
|
|
(context->ctx_flags & CTX_cursor) &&
|
|
val->is<FieldNode>())
|
|
{
|
|
// ASF: FieldNode::execute do not verify rpb_number.isValid(), and due to system triggers
|
|
// and also singular queries, we cannot start to do it. So to fix CORE-4488, we introduce
|
|
// the usage of blr_derived_expr for cursor fields, which in practice prefixes the
|
|
// FieldNode::execute by a test of rpb_number.isValid().
|
|
dsqlScratch->appendUChar(blr_derived_expr);
|
|
dsqlScratch->appendUChar(1);
|
|
GEN_stuff_context(dsqlScratch, val->as<FieldNode>()->dsqlContext);
|
|
}
|
|
|
|
GEN_expr(dsqlScratch, value);
|
|
}
|
|
|
|
void DerivedFieldNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, value);
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<NegateNode> regNegateNode(blr_negate);
|
|
|
|
NegateNode::NegateNode(MemoryPool& pool, ValueExprNode* aArg)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_NEGATE>(pool),
|
|
arg(aArg)
|
|
{
|
|
addChildNode(arg, arg);
|
|
}
|
|
|
|
DmlNode* NegateNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
NegateNode* node = FB_NEW(pool) NegateNode(pool);
|
|
node->arg = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void NegateNode::print(string& text) const
|
|
{
|
|
text = "NegateNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void NegateNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
// CVC: For this to be a thorough check, we need to recurse over all nodes.
|
|
// This means we should separate the code that sets aliases from
|
|
// the rest of the functionality here in MAKE_parameter_names().
|
|
// Otherwise, we need to test here for most of the other node types.
|
|
// However, we need to be recursive only if we agree things like -gen_id()
|
|
// should be given the GEN_ID alias, too.
|
|
int level = 0;
|
|
const ValueExprNode* innerNode = arg;
|
|
const NegateNode* innerNegateNode;
|
|
|
|
while ((innerNegateNode = ExprNode::as<NegateNode>(innerNode)))
|
|
{
|
|
innerNode = innerNegateNode->arg;
|
|
++level;
|
|
}
|
|
|
|
if (ExprNode::is<NullNode>(innerNode) || ExprNode::is<LiteralNode>(innerNode))
|
|
parameter->par_name = parameter->par_alias = "CONSTANT";
|
|
else if (!level)
|
|
{
|
|
const ArithmeticNode* arithmeticNode = ExprNode::as<ArithmeticNode>(innerNode);
|
|
|
|
if (arithmeticNode && (
|
|
/*arithmeticNode->blrOp == blr_add ||
|
|
arithmeticNode->blrOp == blr_subtract ||*/
|
|
arithmeticNode->blrOp == blr_multiply ||
|
|
arithmeticNode->blrOp == blr_divide))
|
|
{
|
|
parameter->par_name = parameter->par_alias = arithmeticNode->label.c_str();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NegateNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, arg, desc, forceVarChar);
|
|
}
|
|
|
|
void NegateNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
LiteralNode* literal = arg->as<LiteralNode>();
|
|
|
|
if (literal && DTYPE_IS_NUMERIC(literal->litDesc.dsc_dtype))
|
|
LiteralNode::genConstant(dsqlScratch, &literal->litDesc, true);
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_negate);
|
|
GEN_expr(dsqlScratch, arg);
|
|
}
|
|
}
|
|
|
|
void NegateNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, arg);
|
|
|
|
if (arg->is<NullNode>())
|
|
{
|
|
// -NULL = NULL of INT
|
|
desc->makeLong(0);
|
|
desc->setNullable(true);
|
|
}
|
|
else
|
|
{
|
|
// In Dialect 2 or 3, a string can never partipate in negation
|
|
// (use a specific cast instead)
|
|
if (DTYPE_IS_TEXT(desc->dsc_dtype))
|
|
{
|
|
if (dsqlScratch->clientDialect >= SQL_DIALECT_V6_TRANSITION)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_nostring_neg_dial3));
|
|
}
|
|
|
|
desc->dsc_dtype = dtype_double;
|
|
desc->dsc_length = sizeof(double);
|
|
}
|
|
else if (DTYPE_IS_BLOB(desc->dsc_dtype)) // Forbid blobs and arrays
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_no_blob_array));
|
|
}
|
|
else if (!DTYPE_IS_NUMERIC(desc->dsc_dtype)) // Forbid other not numeric datatypes
|
|
{
|
|
ERRD_post(Arg::Gds(isc_expression_eval_err) <<
|
|
Arg::Gds(isc_dsql_invalid_type_neg));
|
|
}
|
|
}
|
|
}
|
|
|
|
void NegateNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
arg->getDesc(tdbb, csb, desc);
|
|
nodFlags = arg->nodFlags & FLAG_DOUBLE;
|
|
|
|
if (desc->dsc_dtype == dtype_quad)
|
|
IBERROR(224); // msg 224 quad word arithmetic not supported
|
|
}
|
|
|
|
ValueExprNode* NegateNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
NegateNode* node = FB_NEW(*tdbb->getDefaultPool()) NegateNode(*tdbb->getDefaultPool());
|
|
node->arg = copier.copy(tdbb, arg);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* NegateNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* NegateNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
request->req_flags &= ~req_null;
|
|
|
|
const dsc* desc = EVL_expr(tdbb, request, arg);
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
EVL_make_value(tdbb, desc, impure);
|
|
|
|
switch (impure->vlu_desc.dsc_dtype)
|
|
{
|
|
case dtype_short:
|
|
if (impure->vlu_misc.vlu_short == MIN_SSHORT)
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
impure->vlu_misc.vlu_short = -impure->vlu_misc.vlu_short;
|
|
break;
|
|
|
|
case dtype_long:
|
|
if (impure->vlu_misc.vlu_long == MIN_SLONG)
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
impure->vlu_misc.vlu_long = -impure->vlu_misc.vlu_long;
|
|
break;
|
|
|
|
case dtype_real:
|
|
impure->vlu_misc.vlu_float = -impure->vlu_misc.vlu_float;
|
|
break;
|
|
|
|
case DEFAULT_DOUBLE:
|
|
impure->vlu_misc.vlu_double = -impure->vlu_misc.vlu_double;
|
|
break;
|
|
|
|
case dtype_int64:
|
|
if (impure->vlu_misc.vlu_int64 == MIN_SINT64)
|
|
ERR_post(Arg::Gds(isc_exception_integer_overflow));
|
|
impure->vlu_misc.vlu_int64 = -impure->vlu_misc.vlu_int64;
|
|
break;
|
|
|
|
default:
|
|
impure->vlu_misc.vlu_double = -MOV_get_double(&impure->vlu_desc);
|
|
impure->vlu_desc.dsc_dtype = DEFAULT_DOUBLE;
|
|
impure->vlu_desc.dsc_length = sizeof(double);
|
|
impure->vlu_desc.dsc_scale = 0;
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_double;
|
|
}
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
ValueExprNode* NegateNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
return FB_NEW(getPool()) NegateNode(getPool(), doDsqlPass(dsqlScratch, arg));
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<NullNode> regNullNode(blr_null);
|
|
|
|
DmlNode* NullNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* /*csb*/,
|
|
const UCHAR /*blrOp*/)
|
|
{
|
|
return FB_NEW(pool) NullNode(pool);
|
|
}
|
|
|
|
void NullNode::print(string& text) const
|
|
{
|
|
text.printf("NullNode");
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void NullNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "CONSTANT";
|
|
}
|
|
|
|
void NullNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_null);
|
|
}
|
|
|
|
void NullNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
// This occurs when SQL statement specifies a literal NULL, eg:
|
|
// SELECT NULL FROM TABLE1;
|
|
// As we don't have a <dtype_null, SQL_NULL> datatype pairing,
|
|
// we don't know how to map this NULL to a host-language
|
|
// datatype. Therefore we now describe it as a
|
|
// CHAR(1) CHARACTER SET NONE type.
|
|
// No value will ever be sent back, as the value of the select
|
|
// will be NULL - this is only for purposes of DESCRIBING
|
|
// the statement. Note that this mapping could be done in dsql.cpp
|
|
// as part of the DESCRIBE statement - but I suspect other areas
|
|
// of the code would break if this is declared dtype_unknown.
|
|
//
|
|
// ASF: We have SQL_NULL now, but don't use it here.
|
|
|
|
desc->makeNullString();
|
|
}
|
|
|
|
void NullNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
desc->makeLong(0);
|
|
desc->setNull();
|
|
}
|
|
|
|
ValueExprNode* NullNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
|
|
{
|
|
return FB_NEW(*tdbb->getDefaultPool()) NullNode(*tdbb->getDefaultPool());
|
|
}
|
|
|
|
ValueExprNode* NullNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* NullNode::execute(thread_db* /*tdbb*/, jrd_req* /*request*/) const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
OrderNode::OrderNode(MemoryPool& pool, ValueExprNode* aValue)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_ORDER>(pool),
|
|
value(aValue),
|
|
descending(false),
|
|
nullsPlacement(NULLS_DEFAULT)
|
|
{
|
|
addDsqlChildNode(value);
|
|
}
|
|
|
|
void OrderNode::print(string& text) const
|
|
{
|
|
text = "OrderNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
OrderNode* OrderNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
OrderNode* node = FB_NEW(getPool()) OrderNode(getPool(), doDsqlPass(dsqlScratch, value));
|
|
node->descending = descending;
|
|
node->nullsPlacement = nullsPlacement;
|
|
return node;
|
|
}
|
|
|
|
bool OrderNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const OrderNode* o = other->as<OrderNode>();
|
|
|
|
return o && descending == o->descending && nullsPlacement == o->nullsPlacement;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
OverNode::OverNode(MemoryPool& pool, AggNode* aAggExpr, ValueListNode* aPartition,
|
|
ValueListNode* aOrder)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_OVER>(pool),
|
|
aggExpr(aAggExpr),
|
|
partition(aPartition),
|
|
order(aOrder)
|
|
{
|
|
addDsqlChildNode(aggExpr);
|
|
addDsqlChildNode(partition);
|
|
addDsqlChildNode(order);
|
|
}
|
|
|
|
void OverNode::print(string& text) const
|
|
{
|
|
text = "OverNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
bool OverNode::dsqlAggregateFinder(AggregateFinder& visitor)
|
|
{
|
|
bool aggregate = false;
|
|
const bool wereWindow = visitor.window;
|
|
AutoSetRestore<bool> autoWindow(&visitor.window, false);
|
|
|
|
if (!wereWindow)
|
|
{
|
|
Array<NodeRef*>& exprChildren = aggExpr->dsqlChildNodes;
|
|
for (NodeRef** i = exprChildren.begin(); i != exprChildren.end(); ++i)
|
|
aggregate |= visitor.visit((*i)->getExpr());
|
|
}
|
|
else
|
|
aggregate |= visitor.visit(aggExpr);
|
|
|
|
aggregate |= visitor.visit(partition);
|
|
aggregate |= visitor.visit(order);
|
|
|
|
return aggregate;
|
|
}
|
|
|
|
bool OverNode::dsqlAggregate2Finder(Aggregate2Finder& visitor)
|
|
{
|
|
bool found = false;
|
|
|
|
{ // scope
|
|
AutoSetRestore<bool> autoWindowOnly(&visitor.windowOnly, false);
|
|
found |= visitor.visit(aggExpr);
|
|
}
|
|
|
|
found |= visitor.visit(partition);
|
|
found |= visitor.visit(order);
|
|
|
|
return found;
|
|
}
|
|
|
|
bool OverNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor)
|
|
{
|
|
bool invalid = false;
|
|
|
|
// It's allowed to use an aggregate function of our context inside window functions.
|
|
AutoSetRestore<bool> autoInsideHigherMap(&visitor.insideHigherMap, true);
|
|
|
|
invalid |= visitor.visit(aggExpr);
|
|
invalid |= visitor.visit(partition);
|
|
invalid |= visitor.visit(order);
|
|
|
|
return invalid;
|
|
}
|
|
|
|
bool OverNode::dsqlSubSelectFinder(SubSelectFinder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ValueExprNode* OverNode::dsqlFieldRemapper(FieldRemapper& visitor)
|
|
{
|
|
// Save the values to restore them in the end.
|
|
AutoSetRestore<ValueListNode*> autoPartitionNode(&visitor.partitionNode, visitor.partitionNode);
|
|
AutoSetRestore<ValueListNode*> autoOrderNode(&visitor.orderNode, visitor.orderNode);
|
|
|
|
if (partition)
|
|
{
|
|
if (Aggregate2Finder::find(visitor.context->ctx_scope_level, FIELD_MATCH_TYPE_EQUAL,
|
|
true, partition))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_dsql_agg_nested_err));
|
|
}
|
|
|
|
visitor.partitionNode = partition;
|
|
}
|
|
|
|
if (order)
|
|
{
|
|
if (Aggregate2Finder::find(visitor.context->ctx_scope_level, FIELD_MATCH_TYPE_EQUAL,
|
|
true, order))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_dsql_agg_nested_err));
|
|
}
|
|
|
|
visitor.orderNode = order;
|
|
}
|
|
|
|
// Before remap, aggExpr must always be an AggNode;
|
|
AggNode* aggNode = static_cast<AggNode*>(aggExpr.getObject());
|
|
|
|
Array<NodeRef*>& exprChildren = aggNode->dsqlChildNodes;
|
|
|
|
for (NodeRef** i = exprChildren.begin(); i != exprChildren.end(); ++i)
|
|
{
|
|
if (Aggregate2Finder::find(visitor.context->ctx_scope_level, FIELD_MATCH_TYPE_EQUAL,
|
|
true, (*i)->getExpr()))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_dsql_agg_nested_err));
|
|
}
|
|
}
|
|
|
|
AggregateFinder aggFinder(visitor.dsqlScratch, false);
|
|
aggFinder.deepestLevel = visitor.dsqlScratch->scopeLevel;
|
|
aggFinder.currentLevel = visitor.currentLevel;
|
|
|
|
if (aggFinder.visit(aggNode))
|
|
{
|
|
if (!visitor.window)
|
|
{
|
|
{ // scope
|
|
AutoSetRestore<ValueListNode*> autoPartitionNode2(&visitor.partitionNode, NULL);
|
|
AutoSetRestore<ValueListNode*> autoOrderNode2(&visitor.orderNode, NULL);
|
|
|
|
Array<NodeRef*>& exprChildren = aggNode->dsqlChildNodes;
|
|
for (NodeRef** i = exprChildren.begin(); i != exprChildren.end(); ++i)
|
|
(*i)->remap(visitor);
|
|
}
|
|
|
|
if (partition)
|
|
{
|
|
for (unsigned i = 0; i < partition->items.getCount(); ++i)
|
|
{
|
|
AutoSetRestore<ValueListNode*> autoPartitionNode2(&visitor.partitionNode, NULL);
|
|
AutoSetRestore<ValueListNode*> autoOrderNode2(&visitor.orderNode, NULL);
|
|
|
|
doDsqlFieldRemapper(visitor, partition->items[i]);
|
|
}
|
|
}
|
|
|
|
if (order)
|
|
{
|
|
for (unsigned i = 0; i < order->items.getCount(); ++i)
|
|
{
|
|
AutoSetRestore<ValueListNode*> autoPartitionNode(&visitor.partitionNode, NULL);
|
|
AutoSetRestore<ValueListNode*> autoOrderNode(&visitor.orderNode, NULL);
|
|
|
|
doDsqlFieldRemapper(visitor, order->items[i]);
|
|
}
|
|
}
|
|
}
|
|
else if (visitor.dsqlScratch->scopeLevel == aggFinder.deepestLevel)
|
|
{
|
|
return PASS1_post_map(visitor.dsqlScratch, aggNode, visitor.context,
|
|
visitor.partitionNode, visitor.orderNode);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
void OverNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
MAKE_parameter_names(parameter, aggExpr);
|
|
}
|
|
|
|
void OverNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
GEN_expr(dsqlScratch, aggExpr);
|
|
}
|
|
|
|
void OverNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, aggExpr);
|
|
desc->setNullable(true);
|
|
}
|
|
|
|
void OverNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* /*desc*/)
|
|
{
|
|
fb_assert(false);
|
|
}
|
|
|
|
ValueExprNode* OverNode::copy(thread_db* /*tdbb*/, NodeCopier& /*copier*/) const
|
|
{
|
|
fb_assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
dsc* OverNode::execute(thread_db* /*tdbb*/, jrd_req* /*request*/) const
|
|
{
|
|
fb_assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
ValueExprNode* OverNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
return FB_NEW(getPool()) OverNode(getPool(),
|
|
static_cast<AggNode*>(doDsqlPass(dsqlScratch, aggExpr)),
|
|
doDsqlPass(dsqlScratch, partition),
|
|
doDsqlPass(dsqlScratch, order));
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<ParameterNode> regParameterNode(blr_parameter);
|
|
static RegisterNode<ParameterNode> regParameterNode2(blr_parameter2);
|
|
static RegisterNode<ParameterNode> regParameterNode3(blr_parameter3);
|
|
|
|
ParameterNode::ParameterNode(MemoryPool& pool)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_PARAMETER>(pool),
|
|
dsqlParameterIndex(0),
|
|
dsqlParameter(NULL),
|
|
message(NULL),
|
|
argNumber(0),
|
|
argFlag(NULL),
|
|
argIndicator(NULL),
|
|
argInfo(NULL)
|
|
{
|
|
addChildNode(argFlag);
|
|
addChildNode(argIndicator);
|
|
}
|
|
|
|
DmlNode* ParameterNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
|
{
|
|
MessageNode* message = NULL;
|
|
USHORT n = csb->csb_blr_reader.getByte();
|
|
|
|
if (n >= csb->csb_rpt.getCount() || !(message = csb->csb_rpt[n].csb_message))
|
|
PAR_error(csb, Arg::Gds(isc_badmsgnum));
|
|
|
|
ParameterNode* node = FB_NEW(pool) ParameterNode(pool);
|
|
|
|
node->message = message;
|
|
node->argNumber = csb->csb_blr_reader.getWord();
|
|
|
|
const Format* format = message->format;
|
|
|
|
if (node->argNumber >= format->fmt_count)
|
|
PAR_error(csb, Arg::Gds(isc_badparnum));
|
|
|
|
if (blrOp != blr_parameter)
|
|
{
|
|
ParameterNode* flagNode = FB_NEW(pool) ParameterNode(pool);
|
|
flagNode->message = message;
|
|
flagNode->argNumber = csb->csb_blr_reader.getWord();
|
|
|
|
if (flagNode->argNumber >= format->fmt_count)
|
|
PAR_error(csb, Arg::Gds(isc_badparnum));
|
|
|
|
node->argFlag = flagNode;
|
|
}
|
|
|
|
if (blrOp == blr_parameter3)
|
|
{
|
|
ParameterNode* indicatorNode = FB_NEW(pool) ParameterNode(pool);
|
|
indicatorNode->message = message;
|
|
indicatorNode->argNumber = csb->csb_blr_reader.getWord();
|
|
|
|
if (indicatorNode->argNumber >= format->fmt_count)
|
|
PAR_error(csb, Arg::Gds(isc_badparnum));
|
|
|
|
node->argIndicator = indicatorNode;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void ParameterNode::print(string& text) const
|
|
{
|
|
text = "ParameterNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* ParameterNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (dsqlScratch->isPsql())
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
|
Arg::Gds(isc_dsql_command_err));
|
|
}
|
|
|
|
dsql_msg* tempMsg = dsqlParameter ?
|
|
dsqlParameter->par_message : dsqlScratch->getStatement()->getSendMsg();
|
|
|
|
ParameterNode* node = FB_NEW(getPool()) ParameterNode(getPool());
|
|
node->dsqlParameter = MAKE_parameter(tempMsg, true, true, dsqlParameterIndex, NULL);
|
|
node->dsqlParameterIndex = dsqlParameterIndex;
|
|
|
|
return node;
|
|
}
|
|
|
|
bool ParameterNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
const dsc oldDesc = dsqlParameter->par_desc;
|
|
|
|
if (!desc)
|
|
dsqlParameter->par_desc.makeNullString();
|
|
else
|
|
{
|
|
dsqlParameter->par_desc = *desc;
|
|
|
|
if (tdbb->getCharSet() != CS_NONE && tdbb->getCharSet() != CS_BINARY)
|
|
{
|
|
const USHORT fromCharSet = dsqlParameter->par_desc.getCharSet();
|
|
const USHORT toCharSet = (fromCharSet == CS_NONE || fromCharSet == CS_BINARY) ?
|
|
fromCharSet : tdbb->getCharSet();
|
|
|
|
if (dsqlParameter->par_desc.dsc_dtype <= dtype_any_text)
|
|
{
|
|
int diff = 0;
|
|
|
|
switch (dsqlParameter->par_desc.dsc_dtype)
|
|
{
|
|
case dtype_varying:
|
|
diff = sizeof(USHORT);
|
|
break;
|
|
case dtype_cstring:
|
|
diff = 1;
|
|
break;
|
|
}
|
|
|
|
dsqlParameter->par_desc.dsc_length -= diff;
|
|
|
|
if (toCharSet != fromCharSet)
|
|
{
|
|
const USHORT fromCharSetBPC = METD_get_charset_bpc(
|
|
dsqlScratch->getTransaction(), fromCharSet);
|
|
const USHORT toCharSetBPC = METD_get_charset_bpc(
|
|
dsqlScratch->getTransaction(), toCharSet);
|
|
|
|
dsqlParameter->par_desc.setTextType(INTL_CS_COLL_TO_TTYPE(toCharSet,
|
|
(fromCharSet == toCharSet ? INTL_GET_COLLATE(&dsqlParameter->par_desc) : 0)));
|
|
|
|
dsqlParameter->par_desc.dsc_length = UTLD_char_length_to_byte_length(
|
|
dsqlParameter->par_desc.dsc_length / fromCharSetBPC, toCharSetBPC, diff);
|
|
}
|
|
|
|
dsqlParameter->par_desc.dsc_length += diff;
|
|
}
|
|
else if (dsqlParameter->par_desc.dsc_dtype == dtype_blob &&
|
|
dsqlParameter->par_desc.dsc_sub_type == isc_blob_text &&
|
|
fromCharSet != CS_NONE && fromCharSet != CS_BINARY)
|
|
{
|
|
dsqlParameter->par_desc.setTextType(toCharSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!dsqlParameter)
|
|
{
|
|
dsqlParameter = MAKE_parameter(dsqlScratch->getStatement()->getSendMsg(), true, true,
|
|
dsqlParameterIndex, NULL);
|
|
dsqlParameterIndex = dsqlParameter->par_index;
|
|
}
|
|
|
|
// In case of RETURNING in MERGE and UPDATE OR INSERT, a single parameter is used in
|
|
// more than one place. So we save it to use below.
|
|
const bool hasOldDesc = dsqlParameter->par_node != NULL;
|
|
|
|
dsqlParameter->par_node = this;
|
|
|
|
// Parameters should receive precisely the data that the user
|
|
// passes in. Therefore for text strings lets use varying
|
|
// strings to insure that we don't add trailing blanks.
|
|
|
|
// However, there are situations this leads to problems - so
|
|
// we use the forceVarChar parameter to prevent this
|
|
// datatype assumption from occuring.
|
|
|
|
if (forceVarChar)
|
|
{
|
|
if (dsqlParameter->par_desc.dsc_dtype == dtype_text)
|
|
{
|
|
dsqlParameter->par_desc.dsc_dtype = dtype_varying;
|
|
// The error msgs is inaccurate, but causing dsc_length
|
|
// to be outsise range can be worse.
|
|
if (dsqlParameter->par_desc.dsc_length > MAX_COLUMN_SIZE - sizeof(USHORT))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
//Arg::Gds(isc_dsql_datatype_err)
|
|
Arg::Gds(isc_imp_exc));
|
|
//Arg::Gds(isc_field_name) << Arg::Str(parameter->par_name)
|
|
}
|
|
|
|
dsqlParameter->par_desc.dsc_length += sizeof(USHORT);
|
|
}
|
|
else if (!dsqlParameter->par_desc.isText() && !dsqlParameter->par_desc.isBlob())
|
|
{
|
|
const USHORT toCharSetBPC = METD_get_charset_bpc(
|
|
dsqlScratch->getTransaction(), tdbb->getCharSet());
|
|
|
|
// The LIKE & similar parameters must be varchar type
|
|
// strings - so force this parameter to be varchar
|
|
// and take a guess at a good length for it.
|
|
dsqlParameter->par_desc.dsc_dtype = dtype_varying;
|
|
dsqlParameter->par_desc.dsc_length = LIKE_PARAM_LEN * toCharSetBPC + sizeof(USHORT);
|
|
dsqlParameter->par_desc.dsc_sub_type = 0;
|
|
dsqlParameter->par_desc.dsc_scale = 0;
|
|
dsqlParameter->par_desc.setTextType(tdbb->getCharSet());
|
|
}
|
|
}
|
|
|
|
if (hasOldDesc)
|
|
{
|
|
dsc thisDesc = dsqlParameter->par_desc;
|
|
const dsc* args[] = {&oldDesc, &thisDesc};
|
|
DSqlDataTypeUtil(dsqlScratch).makeFromList(&dsqlParameter->par_desc,
|
|
dsqlParameter->par_name.c_str(), 2, args);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ParameterNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
GEN_parameter(dsqlScratch, dsqlParameter);
|
|
}
|
|
|
|
void ParameterNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
// We don't actually know the datatype of a parameter -
|
|
// we have to guess it based on the context that the
|
|
// parameter appears in. (This is done is pass1.c::set_parameter_type())
|
|
// However, a parameter can appear as part of an expression.
|
|
// As MAKE_desc is used for both determination of parameter
|
|
// types and for expression type checking, we just continue.
|
|
|
|
if (dsqlParameter->par_desc.dsc_dtype)
|
|
*desc = dsqlParameter->par_desc;
|
|
}
|
|
|
|
bool ParameterNode::dsqlMatch(const ExprNode* other, bool /*ignoreMapCast*/) const
|
|
{
|
|
const ParameterNode* o = other->as<ParameterNode>();
|
|
|
|
return o && dsqlParameter->par_index == o->dsqlParameter->par_index;
|
|
}
|
|
|
|
void ParameterNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
*desc = message->format->fmt_desc[argNumber];
|
|
// Must reset dsc_address because it's used in others places to read literals, but here it was
|
|
// an offset in the message.
|
|
desc->dsc_address = NULL;
|
|
}
|
|
|
|
ValueExprNode* ParameterNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
ParameterNode* node = FB_NEW(*tdbb->getDefaultPool()) ParameterNode(*tdbb->getDefaultPool());
|
|
node->argNumber = argNumber;
|
|
|
|
// dimitr: IMPORTANT!!!
|
|
// nod_message copying must be done in the only place
|
|
// (the nod_procedure code). Hence we don't call
|
|
// copy() here to keep argument->nod_arg[e_arg_message]
|
|
// and procedure->nod_arg[e_prc_in_msg] in sync. The
|
|
// message is passed to copy() as a parameter. If the
|
|
// passed message is NULL, it means nod_argument is
|
|
// cloned outside nod_procedure (e.g. in the optimizer)
|
|
// and we must keep the input message.
|
|
// ASF: We should only use "message" if its number matches the number
|
|
// in nod_argument. If it doesn't, it may be an input parameter cloned
|
|
// in RseBoolNode::convertNeqAllToNotAny - see CORE-3094.
|
|
|
|
if (copier.message && copier.message->messageNumber == message->messageNumber)
|
|
node->message = copier.message;
|
|
else
|
|
node->message = message;
|
|
|
|
node->argFlag = copier.copy(tdbb, argFlag);
|
|
node->argIndicator = copier.copy(tdbb, argIndicator);
|
|
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* ParameterNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
argInfo = CMP_pass2_validation(tdbb, csb,
|
|
Item(Item::TYPE_PARAMETER, message->messageNumber, argNumber));
|
|
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, (nodFlags & FLAG_VALUE) ? sizeof(impure_value_ex) : sizeof(dsc));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* ParameterNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
const dsc* desc;
|
|
|
|
if (argFlag)
|
|
{
|
|
desc = EVL_expr(tdbb, request, argFlag);
|
|
if (MOV_get_long(desc, 0))
|
|
request->req_flags |= req_null;
|
|
}
|
|
|
|
desc = &message->format->fmt_desc[argNumber];
|
|
|
|
impure->vlu_desc.dsc_address = request->getImpure<UCHAR>(
|
|
message->impureOffset + (IPTR) desc->dsc_address);
|
|
impure->vlu_desc.dsc_dtype = desc->dsc_dtype;
|
|
impure->vlu_desc.dsc_length = desc->dsc_length;
|
|
impure->vlu_desc.dsc_scale = desc->dsc_scale;
|
|
impure->vlu_desc.dsc_sub_type = desc->dsc_sub_type;
|
|
|
|
if (impure->vlu_desc.dsc_dtype == dtype_text)
|
|
INTL_adjust_text_descriptor(tdbb, &impure->vlu_desc);
|
|
|
|
USHORT* impure_flags = request->getImpure<USHORT>(
|
|
message->impureFlags + (sizeof(USHORT) * argNumber));
|
|
|
|
if (!(*impure_flags & VLU_checked))
|
|
{
|
|
if (argInfo)
|
|
{
|
|
EVL_validate(tdbb, Item(Item::TYPE_PARAMETER, message->messageNumber, argNumber),
|
|
argInfo, &impure->vlu_desc, request->req_flags & req_null);
|
|
}
|
|
|
|
*impure_flags |= VLU_checked;
|
|
}
|
|
|
|
return (request->req_flags & req_null) ? NULL : &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<RecordKeyNode> regRecordKeyNodeDbKey(blr_dbkey);
|
|
static RegisterNode<RecordKeyNode> regRecordKeyNodeRecordVersion(blr_record_version);
|
|
static RegisterNode<RecordKeyNode> regRecordKeyNodeRecordVersion2(blr_record_version2);
|
|
|
|
RecordKeyNode::RecordKeyNode(MemoryPool& pool, UCHAR aBlrOp, const MetaName& aDsqlQualifier)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_RECORD_KEY>(pool),
|
|
blrOp(aBlrOp),
|
|
dsqlQualifier(pool, aDsqlQualifier),
|
|
dsqlRelation(NULL),
|
|
recStream(0),
|
|
aggregate(false)
|
|
{
|
|
fb_assert(blrOp == blr_dbkey || blrOp == blr_record_version || blrOp == blr_record_version2);
|
|
addDsqlChildNode(dsqlRelation);
|
|
}
|
|
|
|
DmlNode* RecordKeyNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
|
{
|
|
RecordKeyNode* node = FB_NEW(pool) RecordKeyNode(pool, blrOp);
|
|
|
|
node->recStream = csb->csb_blr_reader.getByte();
|
|
|
|
if (node->recStream >= csb->csb_rpt.getCount() || !(csb->csb_rpt[node->recStream].csb_flags & csb_used))
|
|
PAR_error(csb, Arg::Gds(isc_ctxnotdef));
|
|
|
|
node->recStream = csb->csb_rpt[node->recStream].csb_stream;
|
|
|
|
return node;
|
|
}
|
|
|
|
void RecordKeyNode::print(string& text) const
|
|
{
|
|
text.printf("RecordKeyNode (%s)",
|
|
(blrOp == blr_dbkey ? "dbkey" :
|
|
blrOp == blr_record_version2 ? "record_version2" : "record_version"));
|
|
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
// Resolve a dbkey to an available context.
|
|
ValueExprNode* RecordKeyNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
if (dsqlQualifier.isEmpty())
|
|
{
|
|
DsqlContextStack contexts;
|
|
|
|
for (DsqlContextStack::iterator stack(*dsqlScratch->context); stack.hasData(); ++stack)
|
|
{
|
|
dsql_ctx* context = stack.object();
|
|
if (context->ctx_scope_level != dsqlScratch->scopeLevel)
|
|
continue;
|
|
|
|
if (context->ctx_relation)
|
|
contexts.push(context);
|
|
}
|
|
|
|
if (contexts.hasData())
|
|
{
|
|
dsql_ctx* context = contexts.object();
|
|
|
|
if (!context->ctx_relation)
|
|
raiseError(context);
|
|
|
|
if (context->ctx_flags & CTX_null)
|
|
return FB_NEW(*tdbb->getDefaultPool()) NullNode(*tdbb->getDefaultPool());
|
|
|
|
PASS1_ambiguity_check(dsqlScratch, getAlias(true), contexts);
|
|
|
|
RelationSourceNode* relNode = FB_NEW(getPool()) RelationSourceNode(getPool());
|
|
relNode->dsqlContext = context;
|
|
|
|
RecordKeyNode* node = FB_NEW(getPool()) RecordKeyNode(getPool(), blrOp);
|
|
node->dsqlRelation = relNode;
|
|
|
|
return node;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const bool cfgRlxAlias = Config::getRelaxedAliasChecking();
|
|
bool rlxAlias = false;
|
|
|
|
for (;;)
|
|
{
|
|
for (DsqlContextStack::iterator stack(*dsqlScratch->context); stack.hasData(); ++stack)
|
|
{
|
|
dsql_ctx* context = stack.object();
|
|
|
|
if ((!context->ctx_relation ||
|
|
context->ctx_relation->rel_name != dsqlQualifier ||
|
|
!rlxAlias && context->ctx_internal_alias.hasData()) &&
|
|
(context->ctx_internal_alias.isEmpty() ||
|
|
strcmp(dsqlQualifier.c_str(), context->ctx_internal_alias.c_str()) != 0))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!context->ctx_relation)
|
|
raiseError(context);
|
|
|
|
if (context->ctx_flags & CTX_null)
|
|
return FB_NEW(*tdbb->getDefaultPool()) NullNode(*tdbb->getDefaultPool());
|
|
|
|
RelationSourceNode* relNode = FB_NEW(getPool()) RelationSourceNode(getPool());
|
|
relNode->dsqlContext = context;
|
|
|
|
RecordKeyNode* node = FB_NEW(getPool()) RecordKeyNode(getPool(), blrOp);
|
|
node->dsqlRelation = relNode;
|
|
|
|
return node;
|
|
}
|
|
|
|
if (rlxAlias == cfgRlxAlias)
|
|
break;
|
|
|
|
rlxAlias = cfgRlxAlias;
|
|
}
|
|
}
|
|
|
|
// Field unresolved.
|
|
PASS1_field_unknown(dsqlQualifier.nullStr(), getAlias(false), this);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool RecordKeyNode::dsqlAggregate2Finder(Aggregate2Finder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool RecordKeyNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& visitor)
|
|
{
|
|
if (dsqlRelation)
|
|
{
|
|
if (dsqlRelation->dsqlContext &&
|
|
dsqlRelation->dsqlContext->ctx_scope_level == visitor.context->ctx_scope_level)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool RecordKeyNode::dsqlSubSelectFinder(SubSelectFinder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool RecordKeyNode::dsqlFieldFinder(FieldFinder& /*visitor*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ValueExprNode* RecordKeyNode::dsqlFieldRemapper(FieldRemapper& visitor)
|
|
{
|
|
return PASS1_post_map(visitor.dsqlScratch, this,
|
|
visitor.context, visitor.partitionNode, visitor.orderNode);
|
|
}
|
|
|
|
void RecordKeyNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = getAlias(false);
|
|
setParameterInfo(parameter, dsqlRelation->dsqlContext);
|
|
}
|
|
|
|
void RecordKeyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsql_ctx* context = dsqlRelation->dsqlContext;
|
|
dsqlScratch->appendUChar(blrOp);
|
|
GEN_stuff_context(dsqlScratch, context);
|
|
}
|
|
|
|
void RecordKeyNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
fb_assert(blrOp == blr_dbkey || blrOp == blr_record_version2);
|
|
fb_assert(dsqlRelation);
|
|
|
|
// Fix for bug 10072 check that the target is a relation
|
|
dsql_rel* relation = dsqlRelation->dsqlContext->ctx_relation;
|
|
|
|
if (relation)
|
|
{
|
|
USHORT dbKeyLength = (relation->rel_flags & REL_creating ? 8 : relation->rel_dbkey_length);
|
|
|
|
if (blrOp == blr_dbkey)
|
|
{
|
|
desc->dsc_dtype = dtype_text;
|
|
desc->dsc_length = dbKeyLength;
|
|
desc->dsc_flags = DSC_nullable;
|
|
desc->dsc_ttype() = ttype_binary;
|
|
}
|
|
else // blr_record_version2
|
|
{
|
|
if (dbKeyLength == 8)
|
|
{
|
|
desc->makeLong(0);
|
|
desc->setNullable(true);
|
|
}
|
|
else
|
|
raiseError(dsqlRelation->dsqlContext);
|
|
}
|
|
}
|
|
else
|
|
raiseError(dsqlRelation->dsqlContext);
|
|
}
|
|
|
|
bool RecordKeyNode::computable(CompilerScratch* csb, StreamType stream,
|
|
bool allowOnlyCurrentStream, ValueExprNode* /*value*/)
|
|
{
|
|
if (allowOnlyCurrentStream)
|
|
{
|
|
if (recStream != stream && !(csb->csb_rpt[recStream].csb_flags & csb_sub_stream))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (recStream == stream)
|
|
return false;
|
|
}
|
|
|
|
return csb->csb_rpt[recStream].csb_flags & csb_active;
|
|
}
|
|
|
|
void RecordKeyNode::findDependentFromStreams(const OptimizerRetrieval* optRet, SortedStreamList* streamList)
|
|
{
|
|
if (recStream != optRet->stream && (optRet->csb->csb_rpt[recStream].csb_flags & csb_active))
|
|
{
|
|
if (!streamList->exist(recStream))
|
|
streamList->add(recStream);
|
|
}
|
|
}
|
|
|
|
void RecordKeyNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
switch (blrOp)
|
|
{
|
|
case blr_dbkey:
|
|
desc->dsc_dtype = dtype_dbkey;
|
|
desc->dsc_length = type_lengths[dtype_dbkey];
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
break;
|
|
|
|
case blr_record_version:
|
|
desc->dsc_dtype = dtype_text;
|
|
desc->dsc_ttype() = ttype_binary;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
break;
|
|
|
|
case blr_record_version2:
|
|
desc->makeLong(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ValueExprNode* RecordKeyNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
RecordKeyNode* node = FB_NEW(*tdbb->getDefaultPool()) RecordKeyNode(*tdbb->getDefaultPool(), blrOp);
|
|
node->recStream = recStream;
|
|
node->aggregate = aggregate;
|
|
|
|
if (copier.remap)
|
|
{
|
|
#ifdef CMP_DEBUG
|
|
csb->dump("remap RecordKeyNode: %d -> %d\n", node->recStream, copier.remap[node->recStream]);
|
|
#endif
|
|
node->recStream = copier.remap[node->recStream];
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
bool RecordKeyNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const RecordKeyNode* o = other->as<RecordKeyNode>();
|
|
fb_assert(o);
|
|
|
|
return blrOp == o->blrOp;
|
|
}
|
|
|
|
bool RecordKeyNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const RecordKeyNode* const otherNode = other->as<RecordKeyNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return blrOp == otherNode->blrOp && (ignoreStreams || recStream == otherNode->recStream);
|
|
}
|
|
|
|
ValueExprNode* RecordKeyNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass1(tdbb, csb);
|
|
|
|
CMP_mark_variant(csb, recStream);
|
|
|
|
if (!csb->csb_rpt[recStream].csb_map)
|
|
return this;
|
|
|
|
ValueExprNodeStack stack;
|
|
CMP_expand_view_nodes(tdbb, csb, recStream, stack, blrOp, false);
|
|
|
|
#ifdef CMP_DEBUG
|
|
csb->dump("expand RecordKeyNode: %d\n", recStream);
|
|
#endif
|
|
|
|
if (stack.hasData())
|
|
{
|
|
const size_t stackCount = stack.getCount();
|
|
|
|
// If that is a DB_KEY of a view, it's possible (in case of
|
|
// outer joins) that some sub-stream have a NULL DB_KEY.
|
|
// In this case, we build a COALESCE(DB_KEY, _OCTETS x"0000000000000000"),
|
|
// for the concatenation of sub DB_KEYs not result in NULL.
|
|
if (blrOp == blr_dbkey && stackCount > 1)
|
|
{
|
|
ValueExprNodeStack stack2;
|
|
|
|
for (ValueExprNodeStack::iterator i(stack); i.hasData(); ++i)
|
|
{
|
|
#ifdef CMP_DEBUG
|
|
csb->dump(" %d", ExprNode::as<RecordKeyNode>(i.object())->recStream);
|
|
#endif
|
|
|
|
ValueIfNode* valueIfNode = FB_NEW(csb->csb_pool) ValueIfNode(csb->csb_pool);
|
|
|
|
MissingBoolNode* missingNode = FB_NEW(csb->csb_pool) MissingBoolNode(csb->csb_pool);
|
|
missingNode->arg = i.object();
|
|
|
|
NotBoolNode* notNode = FB_NEW(csb->csb_pool) NotBoolNode(csb->csb_pool);
|
|
notNode->arg = missingNode;
|
|
|
|
// build an IF (RDB$DB_KEY IS NOT NULL)
|
|
valueIfNode->condition = notNode;
|
|
|
|
valueIfNode->trueValue = i.object(); // THEN
|
|
|
|
LiteralNode* literal = FB_NEW(csb->csb_pool) LiteralNode(csb->csb_pool);
|
|
literal->litDesc.dsc_dtype = dtype_text;
|
|
literal->litDesc.dsc_ttype() = CS_BINARY;
|
|
literal->litDesc.dsc_scale = 0;
|
|
literal->litDesc.dsc_length = 8;
|
|
literal->litDesc.dsc_address = reinterpret_cast<UCHAR*>(
|
|
const_cast<char*>("\0\0\0\0\0\0\0\0")); // safe const_cast
|
|
|
|
valueIfNode->falseValue = literal;
|
|
|
|
stack2.push(valueIfNode);
|
|
}
|
|
|
|
stack.clear();
|
|
|
|
// stack2 is in reverse order, pushing everything in stack
|
|
// will correct the order.
|
|
for (ValueExprNodeStack::iterator i2(stack2); i2.hasData(); ++i2)
|
|
stack.push(i2.object());
|
|
}
|
|
|
|
ValueExprNode* node = catenateNodes(tdbb, stack);
|
|
|
|
if (blrOp == blr_dbkey && stackCount > 1)
|
|
{
|
|
// ASF: If the view is in null state (with outer joins) we need to transform
|
|
// the view RDB$KEY to NULL. (CORE-1245)
|
|
|
|
ValueIfNode* valueIfNode = FB_NEW(csb->csb_pool) ValueIfNode(csb->csb_pool);
|
|
|
|
ComparativeBoolNode* eqlNode = FB_NEW(csb->csb_pool) ComparativeBoolNode(
|
|
csb->csb_pool, blr_eql);
|
|
|
|
// build an IF (RDB$DB_KEY = '')
|
|
valueIfNode->condition = eqlNode;
|
|
|
|
eqlNode->arg1 = NodeCopier::copy(tdbb, csb, node, NULL);
|
|
|
|
LiteralNode* literal = FB_NEW(csb->csb_pool) LiteralNode(csb->csb_pool);
|
|
literal->litDesc.dsc_dtype = dtype_text;
|
|
literal->litDesc.dsc_ttype() = CS_BINARY;
|
|
literal->litDesc.dsc_scale = 0;
|
|
literal->litDesc.dsc_length = 0;
|
|
literal->litDesc.dsc_address = reinterpret_cast<UCHAR*>(
|
|
const_cast<char*>("")); // safe const_cast
|
|
|
|
eqlNode->arg2 = literal;
|
|
|
|
// THEN: NULL
|
|
valueIfNode->trueValue = FB_NEW(csb->csb_pool) NullNode(csb->csb_pool);
|
|
|
|
// ELSE: RDB$DB_KEY
|
|
valueIfNode->falseValue = node;
|
|
|
|
node = valueIfNode;
|
|
}
|
|
|
|
#ifdef CMP_DEBUG
|
|
csb->dump("\n");
|
|
#endif
|
|
|
|
return node;
|
|
}
|
|
|
|
#ifdef CMP_DEBUG
|
|
csb->dump("\n");
|
|
#endif
|
|
|
|
// The user is asking for the dbkey/record version of an aggregate.
|
|
// Humor him with a key filled with zeros.
|
|
|
|
RecordKeyNode* node = FB_NEW(*tdbb->getDefaultPool()) RecordKeyNode(*tdbb->getDefaultPool(), blrOp);
|
|
node->recStream = recStream;
|
|
node->aggregate = true;
|
|
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* RecordKeyNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* RecordKeyNode::execute(thread_db* /*tdbb*/, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
const record_param* rpb = &request->req_rpb[recStream];
|
|
|
|
if (blrOp == blr_dbkey)
|
|
{
|
|
// Make up a dbkey for a record stream. A dbkey is expressed as an 8 byte character string.
|
|
|
|
const jrd_rel* relation = rpb->rpb_relation;
|
|
|
|
// If it doesn't point to a valid record, return NULL
|
|
if (!rpb->rpb_number.isValid() || rpb->rpb_number.isBof() || !relation)
|
|
{
|
|
request->req_flags |= req_null;
|
|
return NULL;
|
|
}
|
|
|
|
// Format dbkey as vector of relation id, record number
|
|
|
|
// Initialize first 32 bits of DB_KEY
|
|
impure->vlu_misc.vlu_dbkey[0] = 0;
|
|
|
|
// Now, put relation ID into first 16 bits of DB_KEY
|
|
// We do not assign it as SLONG because of big-endian machines.
|
|
*(USHORT*) impure->vlu_misc.vlu_dbkey = relation->rel_id;
|
|
|
|
// Encode 40-bit record number. Before that, increment the value
|
|
// because users expect the numbering to start with one.
|
|
RecordNumber temp(rpb->rpb_number.getValue() + 1);
|
|
temp.bid_encode(reinterpret_cast<RecordNumber::Packed*>(impure->vlu_misc.vlu_dbkey));
|
|
|
|
// Initialize descriptor
|
|
|
|
impure->vlu_desc.dsc_address = (UCHAR*) impure->vlu_misc.vlu_dbkey;
|
|
impure->vlu_desc.dsc_dtype = dtype_dbkey;
|
|
impure->vlu_desc.dsc_length = type_lengths[dtype_dbkey];
|
|
impure->vlu_desc.dsc_ttype() = ttype_binary;
|
|
}
|
|
else if (blrOp == blr_record_version)
|
|
{
|
|
// Make up a record version for a record stream. The tid of the record will be used.
|
|
// This will be returned as a 4 byte character string.
|
|
|
|
// If the current transaction has updated the record, the record version
|
|
// coming in from DSQL will have the original transaction # (or current
|
|
// transaction if the current transaction updated the record in a different
|
|
// request). In these cases, mark the request so that the boolean
|
|
// to check equality of record version will be forced to evaluate to true.
|
|
|
|
if (request->req_transaction->tra_number == rpb->rpb_transaction_nr)
|
|
request->req_flags |= req_same_tx_upd;
|
|
else
|
|
{
|
|
// If the transaction is a commit retain, check if the record was
|
|
// last updated in one of its own prior transactions
|
|
|
|
if (request->req_transaction->tra_commit_sub_trans)
|
|
{
|
|
if (request->req_transaction->tra_commit_sub_trans->test(rpb->rpb_transaction_nr))
|
|
request->req_flags |= req_same_tx_upd;
|
|
}
|
|
}
|
|
|
|
// Initialize descriptor
|
|
|
|
impure->vlu_misc.vlu_long = rpb->rpb_transaction_nr;
|
|
impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_long;
|
|
impure->vlu_desc.dsc_dtype = dtype_text;
|
|
impure->vlu_desc.dsc_length = 4;
|
|
impure->vlu_desc.dsc_ttype() = ttype_binary;
|
|
}
|
|
else if (blrOp == blr_record_version2)
|
|
{
|
|
const jrd_rel* relation = rpb->rpb_relation;
|
|
|
|
// If it doesn't point to a valid record, return NULL.
|
|
if (!rpb->rpb_number.isValid() || !relation || relation->isVirtual() || relation->rel_file)
|
|
{
|
|
request->req_flags |= req_null;
|
|
return NULL;
|
|
}
|
|
|
|
impure->vlu_misc.vlu_long = rpb->rpb_transaction_nr;
|
|
impure->vlu_desc.makeLong(0, &impure->vlu_misc.vlu_long);
|
|
}
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
// Take a stack of nodes and turn them into a tree of concatenations.
|
|
ValueExprNode* RecordKeyNode::catenateNodes(thread_db* tdbb, ValueExprNodeStack& stack)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
ValueExprNode* node1 = stack.pop();
|
|
|
|
if (stack.isEmpty())
|
|
return node1;
|
|
|
|
ConcatenateNode* concatNode = FB_NEW(*tdbb->getDefaultPool()) ConcatenateNode(
|
|
*tdbb->getDefaultPool());
|
|
concatNode->arg1 = node1;
|
|
concatNode->arg2 = catenateNodes(tdbb, stack);
|
|
|
|
return concatNode;
|
|
}
|
|
|
|
void RecordKeyNode::raiseError(dsql_ctx* context) const
|
|
{
|
|
if (blrOp != blr_record_version2)
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_dbkey_from_non_table));
|
|
}
|
|
|
|
string name = context->getObjectName();
|
|
const string& alias = context->ctx_internal_alias;
|
|
|
|
if (alias.hasData() && name != alias)
|
|
{
|
|
if (name.hasData())
|
|
name += " (alias " + alias + ")";
|
|
else
|
|
name = alias;
|
|
}
|
|
|
|
status_exception::raise(
|
|
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
|
|
Arg::Gds(isc_dsql_record_version_table) << name);
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<ScalarNode> regScalarNode1(blr_index);
|
|
|
|
DmlNode* ScalarNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
ScalarNode* node = FB_NEW(pool) ScalarNode(pool);
|
|
node->field = PAR_parse_value(tdbb, csb);
|
|
node->subscripts = PAR_args(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void ScalarNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
FieldNode* fieldNode = field->as<FieldNode>();
|
|
fb_assert(fieldNode);
|
|
|
|
jrd_rel* relation = csb->csb_rpt[fieldNode->fieldStream].csb_relation;
|
|
const jrd_fld* field = MET_get_field(relation, fieldNode->fieldId);
|
|
const ArrayField* array;
|
|
|
|
if (!field || !(array = field->fld_array))
|
|
{
|
|
IBERROR(223); // msg 223 argument of scalar operation must be an array
|
|
return;
|
|
}
|
|
|
|
*desc = array->arr_desc.iad_rpt[0].iad_desc;
|
|
|
|
if (array->arr_desc.iad_dimensions > MAX_ARRAY_DIMENSIONS)
|
|
IBERROR(306); // Found array data type with more than 16 dimensions
|
|
}
|
|
|
|
ValueExprNode* ScalarNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
ScalarNode* node = FB_NEW(*tdbb->getDefaultPool()) ScalarNode(*tdbb->getDefaultPool());
|
|
node->field = copier.copy(tdbb, field);
|
|
node->subscripts = copier.copy(tdbb, subscripts);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* ScalarNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// Evaluate a scalar item from an array.
|
|
dsc* ScalarNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
const dsc* desc = EVL_expr(tdbb, request, field);
|
|
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
if (desc->dsc_dtype != dtype_array)
|
|
IBERROR(261); // msg 261 scalar operator used on field which is not an array
|
|
|
|
if (subscripts->items.getCount() > MAX_ARRAY_DIMENSIONS)
|
|
ERR_post(Arg::Gds(isc_array_max_dimensions) << Arg::Num(MAX_ARRAY_DIMENSIONS));
|
|
|
|
SLONG numSubscripts[MAX_ARRAY_DIMENSIONS];
|
|
int iter = 0;
|
|
const NestConst<ValueExprNode>* ptr = subscripts->items.begin();
|
|
|
|
for (const NestConst<ValueExprNode>* const end = subscripts->items.end(); ptr != end;)
|
|
{
|
|
const dsc* temp = EVL_expr(tdbb, request, *ptr++);
|
|
|
|
if (temp && !(request->req_flags & req_null))
|
|
numSubscripts[iter++] = MOV_get_long(temp, 0);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
blb::scalar(tdbb, request->req_transaction, reinterpret_cast<bid*>(desc->dsc_address),
|
|
subscripts->items.getCount(), numSubscripts, impure);
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<StmtExprNode> regStmtExprNode(blr_stmt_expr);
|
|
|
|
DmlNode* StmtExprNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
StmtExprNode* node = FB_NEW(pool) StmtExprNode(pool);
|
|
|
|
node->stmt = PAR_parse_stmt(tdbb, csb);
|
|
node->expr = PAR_parse_value(tdbb, csb);
|
|
|
|
// Avoid blr_stmt_expr in a BLR expression header
|
|
CompoundStmtNode* const stmt = node->stmt->as<CompoundStmtNode>();
|
|
|
|
if (stmt)
|
|
{
|
|
if (stmt->statements.getCount() != 2 ||
|
|
!stmt->statements[0]->is<DeclareVariableNode>() ||
|
|
!stmt->statements[1]->is<AssignmentNode>())
|
|
{
|
|
return node->expr;
|
|
}
|
|
}
|
|
else if (!node->stmt->is<AssignmentNode>())
|
|
return node->expr;
|
|
|
|
return node;
|
|
}
|
|
|
|
void StmtExprNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
fb_assert(false);
|
|
}
|
|
|
|
ValueExprNode* StmtExprNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
fb_assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
ValueExprNode* StmtExprNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
fb_assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
ValueExprNode* StmtExprNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
fb_assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
dsc* StmtExprNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
fb_assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<StrCaseNode> regStrCaseNodeLower(blr_lowcase);
|
|
static RegisterNode<StrCaseNode> regStrCaseNodeUpper(blr_upcase);
|
|
|
|
StrCaseNode::StrCaseNode(MemoryPool& pool, UCHAR aBlrOp, ValueExprNode* aArg)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_STR_CASE>(pool),
|
|
blrOp(aBlrOp),
|
|
arg(aArg)
|
|
{
|
|
addChildNode(arg, arg);
|
|
}
|
|
|
|
DmlNode* StrCaseNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
|
|
{
|
|
StrCaseNode* node = FB_NEW(pool) StrCaseNode(pool, blrOp);
|
|
node->arg = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void StrCaseNode::print(string& text) const
|
|
{
|
|
text.printf("StrCaseNode (%s)", (blrOp == blr_lowcase ? "lower" : "upper"));
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* StrCaseNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
return FB_NEW(getPool()) StrCaseNode(getPool(), blrOp, doDsqlPass(dsqlScratch, arg));
|
|
}
|
|
|
|
void StrCaseNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = (blrOp == blr_lowcase ? "LOWER" : "UPPER");
|
|
}
|
|
|
|
bool StrCaseNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, arg, desc, forceVarChar);
|
|
}
|
|
|
|
void StrCaseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blrOp);
|
|
GEN_expr(dsqlScratch, arg);
|
|
}
|
|
|
|
void StrCaseNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, arg);
|
|
|
|
if (desc->dsc_dtype > dtype_any_text && desc->dsc_dtype != dtype_blob)
|
|
{
|
|
desc->dsc_length = static_cast<int>(sizeof(USHORT)) + DSC_string_length(desc);
|
|
desc->dsc_dtype = dtype_varying;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_ttype() = ttype_ascii;
|
|
desc->dsc_flags = desc->dsc_flags & DSC_nullable;
|
|
}
|
|
}
|
|
|
|
void StrCaseNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
arg->getDesc(tdbb, csb, desc);
|
|
|
|
if (desc->dsc_dtype > dtype_any_text && desc->dsc_dtype != dtype_blob)
|
|
{
|
|
desc->dsc_length = DSC_convert_to_text_length(desc->dsc_dtype);
|
|
desc->dsc_dtype = dtype_text;
|
|
desc->dsc_ttype() = ttype_ascii;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_flags = 0;
|
|
}
|
|
}
|
|
|
|
ValueExprNode* StrCaseNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
StrCaseNode* node = FB_NEW(*tdbb->getDefaultPool()) StrCaseNode(*tdbb->getDefaultPool(), blrOp);
|
|
node->arg = copier.copy(tdbb, arg);
|
|
return node;
|
|
}
|
|
|
|
bool StrCaseNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const StrCaseNode* o = other->as<StrCaseNode>();
|
|
fb_assert(o);
|
|
|
|
return blrOp == o->blrOp;
|
|
}
|
|
|
|
bool StrCaseNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const StrCaseNode* const otherNode = other->as<StrCaseNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return blrOp == otherNode->blrOp;
|
|
}
|
|
|
|
ValueExprNode* StrCaseNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// Low/up case a string.
|
|
dsc* StrCaseNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
const dsc* value = EVL_expr(tdbb, request, arg);
|
|
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
TextType* textType = INTL_texttype_lookup(tdbb, value->getTextType());
|
|
ULONG (TextType::*intlFunction)(ULONG, const UCHAR*, ULONG, UCHAR*) =
|
|
(blrOp == blr_lowcase ? &TextType::str_to_lower : &TextType::str_to_upper);
|
|
|
|
if (value->isBlob())
|
|
{
|
|
EVL_make_value(tdbb, value, impure);
|
|
|
|
if (value->dsc_sub_type != isc_blob_text)
|
|
return &impure->vlu_desc;
|
|
|
|
CharSet* charSet = textType->getCharSet();
|
|
|
|
blb* blob = blb::open(tdbb, tdbb->getRequest()->req_transaction,
|
|
reinterpret_cast<bid*>(value->dsc_address));
|
|
|
|
HalfStaticArray<UCHAR, BUFFER_SMALL> buffer;
|
|
|
|
if (charSet->isMultiByte())
|
|
buffer.getBuffer(blob->blb_length); // alloc space to put entire blob in memory
|
|
|
|
blb* newBlob = blb::create(tdbb, tdbb->getRequest()->req_transaction,
|
|
&impure->vlu_misc.vlu_bid);
|
|
|
|
while (!(blob->blb_flags & BLB_eof))
|
|
{
|
|
SLONG len = blob->BLB_get_data(tdbb, buffer.begin(), buffer.getCapacity(), false);
|
|
|
|
if (len)
|
|
{
|
|
len = (textType->*intlFunction)(len, buffer.begin(), len, buffer.begin());
|
|
newBlob->BLB_put_data(tdbb, buffer.begin(), len);
|
|
}
|
|
}
|
|
|
|
newBlob->BLB_close(tdbb);
|
|
blob->BLB_close(tdbb);
|
|
}
|
|
else
|
|
{
|
|
UCHAR* ptr;
|
|
VaryStr<32> temp;
|
|
USHORT ttype;
|
|
|
|
dsc desc;
|
|
desc.dsc_length = MOV_get_string_ptr(value, &ttype, &ptr, &temp, sizeof(temp));
|
|
desc.dsc_dtype = dtype_text;
|
|
desc.dsc_address = NULL;
|
|
desc.setTextType(ttype);
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
|
|
ULONG len = (textType->*intlFunction)(desc.dsc_length,
|
|
ptr, desc.dsc_length, impure->vlu_desc.dsc_address);
|
|
|
|
if (len == INTL_BAD_STR_LENGTH)
|
|
status_exception::raise(Arg::Gds(isc_arith_except));
|
|
|
|
impure->vlu_desc.dsc_length = (USHORT) len;
|
|
}
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<StrLenNode> regStrLenNode(blr_strlen);
|
|
|
|
StrLenNode::StrLenNode(MemoryPool& pool, UCHAR aBlrSubOp, ValueExprNode* aArg)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_STR_LEN>(pool),
|
|
blrSubOp(aBlrSubOp),
|
|
arg(aArg)
|
|
{
|
|
addChildNode(arg, arg);
|
|
}
|
|
|
|
DmlNode* StrLenNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
UCHAR blrSubOp = csb->csb_blr_reader.getByte();
|
|
|
|
StrLenNode* node = FB_NEW(pool) StrLenNode(pool, blrSubOp);
|
|
node->arg = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void StrLenNode::print(string& text) const
|
|
{
|
|
text.printf("StrLenNode (%d)", blrSubOp);
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* StrLenNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
return FB_NEW(getPool()) StrLenNode(getPool(), blrSubOp, doDsqlPass(dsqlScratch, arg));
|
|
}
|
|
|
|
void StrLenNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
const char* alias;
|
|
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_strlen_bit:
|
|
alias = "BIT_LENGTH";
|
|
break;
|
|
|
|
case blr_strlen_char:
|
|
alias = "CHAR_LENGTH";
|
|
break;
|
|
|
|
case blr_strlen_octet:
|
|
alias = "OCTET_LENGTH";
|
|
break;
|
|
|
|
default:
|
|
alias = "";
|
|
fb_assert(false);
|
|
break;
|
|
}
|
|
|
|
parameter->par_name = parameter->par_alias = alias;
|
|
}
|
|
|
|
bool StrLenNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, arg, desc, forceVarChar);
|
|
}
|
|
|
|
void StrLenNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_strlen);
|
|
dsqlScratch->appendUChar(blrSubOp);
|
|
GEN_expr(dsqlScratch, arg);
|
|
}
|
|
|
|
void StrLenNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
dsc desc1;
|
|
MAKE_desc(dsqlScratch, &desc1, arg);
|
|
|
|
if (desc1.isBlob())
|
|
desc->makeInt64(0);
|
|
else
|
|
desc->makeLong(0);
|
|
|
|
desc->setNullable(desc1.isNullable());
|
|
}
|
|
|
|
void StrLenNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
dsc desc1;
|
|
arg->getDesc(tdbb, csb, &desc1);
|
|
|
|
if (desc1.isBlob())
|
|
desc->makeInt64(0);
|
|
else
|
|
desc->makeLong(0);
|
|
}
|
|
|
|
ValueExprNode* StrLenNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
StrLenNode* node = FB_NEW(*tdbb->getDefaultPool()) StrLenNode(*tdbb->getDefaultPool(), blrSubOp);
|
|
node->arg = copier.copy(tdbb, arg);
|
|
return node;
|
|
}
|
|
|
|
bool StrLenNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const StrLenNode* o = other->as<StrLenNode>();
|
|
fb_assert(o);
|
|
|
|
return blrSubOp == o->blrSubOp;
|
|
}
|
|
|
|
bool StrLenNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const StrLenNode* const otherNode = other->as<StrLenNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return blrSubOp == otherNode->blrSubOp;
|
|
}
|
|
|
|
ValueExprNode* StrLenNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// Handles BIT_LENGTH(s), OCTET_LENGTH(s) and CHAR[ACTER]_LENGTH(s)
|
|
dsc* StrLenNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
const dsc* value = EVL_expr(tdbb, request, arg);
|
|
|
|
impure->vlu_desc.makeInt64(0, &impure->vlu_misc.vlu_int64);
|
|
|
|
if (!value || (request->req_flags & req_null))
|
|
return NULL;
|
|
|
|
FB_UINT64 length;
|
|
|
|
if (value->isBlob())
|
|
{
|
|
blb* blob = blb::open(tdbb, tdbb->getRequest()->req_transaction,
|
|
reinterpret_cast<bid*>(value->dsc_address));
|
|
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_strlen_bit:
|
|
length = (FB_UINT64) blob->blb_length * 8;
|
|
break;
|
|
|
|
case blr_strlen_octet:
|
|
length = blob->blb_length;
|
|
break;
|
|
|
|
case blr_strlen_char:
|
|
{
|
|
CharSet* charSet = INTL_charset_lookup(tdbb, value->dsc_blob_ttype());
|
|
|
|
if (charSet->isMultiByte())
|
|
{
|
|
HalfStaticArray<UCHAR, BUFFER_LARGE> buffer;
|
|
|
|
length = blob->BLB_get_data(tdbb, buffer.getBuffer(blob->blb_length),
|
|
blob->blb_length, false);
|
|
length = charSet->length(length, buffer.begin(), true);
|
|
}
|
|
else
|
|
length = blob->blb_length / charSet->maxBytesPerChar();
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
fb_assert(false);
|
|
length = 0;
|
|
}
|
|
|
|
if (length > MAX_SINT64)
|
|
{
|
|
ERR_post(Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_numeric_out_of_range));
|
|
}
|
|
|
|
*(FB_UINT64*) impure->vlu_desc.dsc_address = length;
|
|
|
|
blob->BLB_close(tdbb);
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
VaryStr<32> temp;
|
|
USHORT ttype;
|
|
UCHAR* p;
|
|
|
|
length = MOV_get_string_ptr(value, &ttype, &p, &temp, sizeof(temp));
|
|
|
|
switch (blrSubOp)
|
|
{
|
|
case blr_strlen_bit:
|
|
length *= 8;
|
|
break;
|
|
|
|
case blr_strlen_octet:
|
|
break;
|
|
|
|
case blr_strlen_char:
|
|
{
|
|
CharSet* charSet = INTL_charset_lookup(tdbb, ttype);
|
|
length = charSet->length(length, p, true);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
fb_assert(false);
|
|
length = 0;
|
|
}
|
|
|
|
*(FB_UINT64*) impure->vlu_desc.dsc_address = length;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
// 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, RecordSourceNode* aDsqlRse,
|
|
ValueExprNode* aValue1, ValueExprNode* aValue2)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_SUBQUERY>(pool),
|
|
blrOp(aBlrOp),
|
|
ownSavepoint(true),
|
|
dsqlRse(aDsqlRse),
|
|
value1(aValue1),
|
|
value2(aValue2),
|
|
rsb(NULL)
|
|
{
|
|
addChildNode(dsqlRse, rse);
|
|
addChildNode(value1, value1);
|
|
addChildNode(value2, value2);
|
|
}
|
|
|
|
DmlNode* SubQueryNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const 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_rse(tdbb, csb);
|
|
|
|
if (blrOp != blr_count)
|
|
node->value1 = PAR_parse_value(tdbb, csb);
|
|
|
|
if (blrOp == blr_via)
|
|
{
|
|
node->value2 = PAR_parse_value(tdbb, csb);
|
|
|
|
if (csb->csb_currentForNode && csb->csb_currentForNode->parBlrBeginCnt <= 1)
|
|
node->ownSavepoint = false;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void SubQueryNode::print(string& text) const
|
|
{
|
|
text = "SubQueryNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* SubQueryNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
const DsqlContextStack::iterator base(*dsqlScratch->context);
|
|
|
|
RseNode* rse = PASS1_rse(dsqlScratch, dsqlRse->as<SelectExprNode>(), false);
|
|
|
|
SubQueryNode* node = FB_NEW(getPool()) SubQueryNode(getPool(), blrOp, rse,
|
|
rse->dsqlSelectList->items[0], FB_NEW(getPool()) NullNode(getPool()));
|
|
|
|
// Finish off by cleaning up contexts.
|
|
dsqlScratch->context->clear(base);
|
|
|
|
return node;
|
|
}
|
|
|
|
void SubQueryNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
MAKE_parameter_names(parameter, value1);
|
|
}
|
|
|
|
void SubQueryNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blrOp);
|
|
GEN_expr(dsqlScratch, dsqlRse);
|
|
GEN_expr(dsqlScratch, value1);
|
|
GEN_expr(dsqlScratch, value2);
|
|
}
|
|
|
|
void SubQueryNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, value1);
|
|
|
|
// 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.
|
|
}
|
|
|
|
ValueExprNode* SubQueryNode::dsqlFieldRemapper(FieldRemapper& visitor)
|
|
{
|
|
doDsqlFieldRemapper(visitor, dsqlRse);
|
|
value1 = dsqlRse->as<RseNode>()->dsqlSelectList->items[0];
|
|
return this;
|
|
}
|
|
|
|
void SubQueryNode::collectStreams(SortedStreamList& streamList) const
|
|
{
|
|
if (rse)
|
|
rse->collectStreams(streamList);
|
|
|
|
if (value1)
|
|
value1->collectStreams(streamList);
|
|
}
|
|
|
|
bool SubQueryNode::computable(CompilerScratch* csb, StreamType stream,
|
|
bool allowOnlyCurrentStream, ValueExprNode* /*value*/)
|
|
{
|
|
if (value2 && !value2->computable(csb, stream, allowOnlyCurrentStream))
|
|
return false;
|
|
|
|
return rse->computable(csb, stream, allowOnlyCurrentStream, value1);
|
|
}
|
|
|
|
void SubQueryNode::findDependentFromStreams(const OptimizerRetrieval* optRet,
|
|
SortedStreamList* streamList)
|
|
{
|
|
if (value2)
|
|
value2->findDependentFromStreams(optRet, streamList);
|
|
|
|
rse->findDependentFromStreams(optRet, streamList);
|
|
|
|
// Check value expression, if any.
|
|
if (value1)
|
|
value1->findDependentFromStreams(optRet, streamList);
|
|
}
|
|
|
|
void SubQueryNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
if (blrOp == blr_count)
|
|
desc->makeLong(0);
|
|
else if (value1)
|
|
value1->getDesc(tdbb, csb, 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)
|
|
{
|
|
const USHORT dtype = desc->dsc_dtype;
|
|
|
|
switch (dtype)
|
|
{
|
|
case dtype_short:
|
|
desc->dsc_dtype = dtype_long;
|
|
desc->dsc_length = sizeof(SLONG);
|
|
nodScale = 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;
|
|
nodScale = 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;
|
|
nodFlags |= FLAG_DOUBLE;
|
|
return;
|
|
|
|
case dtype_sql_time:
|
|
case dtype_sql_date:
|
|
case dtype_timestamp:
|
|
case dtype_quad:
|
|
case dtype_blob:
|
|
case dtype_array:
|
|
case dtype_dbkey:
|
|
// break to error reporting code
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
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) const
|
|
{
|
|
SubQueryNode* node = FB_NEW(*tdbb->getDefaultPool()) SubQueryNode(*tdbb->getDefaultPool(), blrOp);
|
|
node->nodScale = nodScale;
|
|
node->ownSavepoint = this->ownSavepoint;
|
|
node->rse = copier.copy(tdbb, rse);
|
|
node->value1 = copier.copy(tdbb, value1);
|
|
node->value2 = copier.copy(tdbb, value2);
|
|
|
|
return node;
|
|
}
|
|
|
|
bool SubQueryNode::sameAs(const ExprNode* /*other*/, bool /*ignoreStreams*/) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ValueExprNode* SubQueryNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
rse->ignoreDbKey(tdbb, csb);
|
|
doPass1(tdbb, csb, rse.getAddress());
|
|
|
|
csb->csb_current_nodes.push(rse.getObject());
|
|
|
|
doPass1(tdbb, csb, value1.getAddress());
|
|
doPass1(tdbb, csb, value2.getAddress());
|
|
|
|
csb->csb_current_nodes.pop();
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* SubQueryNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
if (!rse)
|
|
ERR_post(Arg::Gds(isc_wish_list));
|
|
|
|
if (!(rse->flags & RseNode::FLAG_VARIANT))
|
|
{
|
|
nodFlags |= FLAG_INVARIANT;
|
|
csb->csb_invariants.push(&impureOffset);
|
|
}
|
|
|
|
rse->pass2Rse(tdbb, csb);
|
|
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value_ex));
|
|
|
|
if (blrOp == blr_average)
|
|
nodFlags |= FLAG_DOUBLE;
|
|
else if (blrOp == blr_total)
|
|
{
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
}
|
|
|
|
// Bind values of invariant nodes to top-level RSE (if present).
|
|
if ((nodFlags & FLAG_INVARIANT) && csb->csb_current_nodes.hasData())
|
|
{
|
|
RseOrExprNode& 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(impureOffset);
|
|
}
|
|
|
|
// Finish up processing of record selection expressions.
|
|
|
|
rsb = CMP_post_rse(tdbb, csb, rse);
|
|
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>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
dsc* desc = &impure->vlu_desc;
|
|
USHORT* invariant_flags = NULL;
|
|
|
|
if (nodFlags & FLAG_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 (request->req_flags & req_null) ? NULL : 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
|
|
{
|
|
StableCursorSavePoint savePoint(tdbb, request->req_transaction,
|
|
blrOp == blr_via && ownSavepoint);
|
|
|
|
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, request, 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, request, 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, this, 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, request, value1);
|
|
else
|
|
{
|
|
if (value2)
|
|
desc = EVL_expr(tdbb, request, 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 (nodFlags & FLAG_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, ValueExprNode* aExpr, ValueExprNode* aStart,
|
|
ValueExprNode* aLength)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_SUBSTRING>(pool),
|
|
expr(aExpr),
|
|
start(aStart),
|
|
length(aLength)
|
|
{
|
|
addChildNode(expr, expr);
|
|
addChildNode(start, start);
|
|
addChildNode(length, length);
|
|
}
|
|
|
|
DmlNode* SubstringNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb,
|
|
const UCHAR /*blrOp*/)
|
|
{
|
|
SubstringNode* node = FB_NEW(pool) SubstringNode(pool);
|
|
node->expr = PAR_parse_value(tdbb, csb);
|
|
node->start = PAR_parse_value(tdbb, csb);
|
|
node->length = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void SubstringNode::print(string& text) const
|
|
{
|
|
text = "SubstringNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* SubstringNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
SubstringNode* node = FB_NEW(getPool()) SubstringNode(getPool(),
|
|
doDsqlPass(dsqlScratch, expr),
|
|
doDsqlPass(dsqlScratch, start),
|
|
doDsqlPass(dsqlScratch, length));
|
|
|
|
return node;
|
|
}
|
|
|
|
void SubstringNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "SUBSTRING";
|
|
}
|
|
|
|
bool SubstringNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, expr, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, start, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, length, desc, forceVarChar);
|
|
}
|
|
|
|
void SubstringNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_substring);
|
|
|
|
GEN_expr(dsqlScratch, expr);
|
|
GEN_expr(dsqlScratch, start);
|
|
|
|
if (length)
|
|
GEN_expr(dsqlScratch, length);
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_literal);
|
|
dsqlScratch->appendUChar(blr_long);
|
|
dsqlScratch->appendUChar(0);
|
|
dsqlScratch->appendUShort(LONG_POS_MAX & 0xFFFF);
|
|
dsqlScratch->appendUShort(LONG_POS_MAX >> 16);
|
|
}
|
|
}
|
|
|
|
void SubstringNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
dsc desc1, desc2, desc3;
|
|
|
|
MAKE_desc(dsqlScratch, &desc1, expr);
|
|
MAKE_desc(dsqlScratch, &desc2, start);
|
|
|
|
if (length)
|
|
{
|
|
MAKE_desc(dsqlScratch, &desc3, length);
|
|
|
|
if (!length->is<LiteralNode>())
|
|
desc3.dsc_address = NULL;
|
|
}
|
|
|
|
DSqlDataTypeUtil(dsqlScratch).makeSubstr(desc, &desc1, &desc2, &desc3);
|
|
}
|
|
|
|
void SubstringNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
DSC desc0, desc1, desc2, desc3;
|
|
|
|
expr->getDesc(tdbb, csb, &desc0);
|
|
|
|
ValueExprNode* offsetNode = start;
|
|
ValueExprNode* decrementNode = NULL;
|
|
ArithmeticNode* arithmeticNode = offsetNode->as<ArithmeticNode>();
|
|
|
|
// ASF: This code is very strange. The DSQL node is created as dialect 1, but only the dialect
|
|
// 3 is verified here. Also, this task seems unnecessary here, as it must be done during
|
|
// execution anyway.
|
|
|
|
if (arithmeticNode && arithmeticNode->blrOp == blr_subtract && !arithmeticNode->dialect1)
|
|
{
|
|
// This node is created by the DSQL layer, but the system BLR code bypasses it and uses
|
|
// zero-based string offsets instead.
|
|
decrementNode = arithmeticNode->arg2;
|
|
decrementNode->getDesc(tdbb, csb, &desc3);
|
|
offsetNode = arithmeticNode->arg1;
|
|
}
|
|
|
|
offsetNode->getDesc(tdbb, csb, &desc1);
|
|
length->getDesc(tdbb, csb, &desc2);
|
|
|
|
DataTypeUtil(tdbb).makeSubstr(desc, &desc0, &desc1, &desc2);
|
|
|
|
if (desc1.dsc_flags & DSC_null || desc2.dsc_flags & DSC_null)
|
|
desc->dsc_flags |= DSC_null;
|
|
else
|
|
{
|
|
if (offsetNode->is<LiteralNode>() && desc1.dsc_dtype == dtype_long)
|
|
{
|
|
SLONG offset = MOV_get_long(&desc1, 0);
|
|
|
|
if (decrementNode && decrementNode->is<LiteralNode>() && desc3.dsc_dtype == dtype_long)
|
|
offset -= MOV_get_long(&desc3, 0);
|
|
|
|
if (offset < 0)
|
|
ERR_post(Arg::Gds(isc_bad_substring_offset) << Arg::Num(offset + 1));
|
|
}
|
|
|
|
if (length->is<LiteralNode>() && desc2.dsc_dtype == dtype_long)
|
|
{
|
|
const SLONG len = MOV_get_long(&desc2, 0);
|
|
|
|
if (len < 0)
|
|
ERR_post(Arg::Gds(isc_bad_substring_length) << Arg::Num(len));
|
|
}
|
|
}
|
|
}
|
|
|
|
ValueExprNode* SubstringNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
SubstringNode* node = FB_NEW(*tdbb->getDefaultPool()) SubstringNode(
|
|
*tdbb->getDefaultPool());
|
|
node->expr = copier.copy(tdbb, expr);
|
|
node->start = copier.copy(tdbb, start);
|
|
node->length = copier.copy(tdbb, length);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* SubstringNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* SubstringNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* impure = request->getImpure<impure_value>(impureOffset);
|
|
|
|
// Run all expression arguments.
|
|
|
|
const dsc* exprDesc = EVL_expr(tdbb, request, expr);
|
|
exprDesc = (request->req_flags & req_null) ? NULL : exprDesc;
|
|
|
|
const dsc* startDesc = EVL_expr(tdbb, request, start);
|
|
startDesc = (request->req_flags & req_null) ? NULL : startDesc;
|
|
|
|
const dsc* lengthDesc = EVL_expr(tdbb, request, length);
|
|
lengthDesc = (request->req_flags & req_null) ? NULL : lengthDesc;
|
|
|
|
if (exprDesc && startDesc && lengthDesc)
|
|
return perform(tdbb, impure, exprDesc, startDesc, lengthDesc);
|
|
|
|
// If any of them is NULL, return NULL.
|
|
return NULL;
|
|
}
|
|
|
|
dsc* SubstringNode::perform(thread_db* tdbb, impure_value* impure, const dsc* valueDsc,
|
|
const dsc* startDsc, const dsc* lengthDsc)
|
|
{
|
|
const SLONG sStart = MOV_get_long(startDsc, 0);
|
|
const SLONG sLength = MOV_get_long(lengthDsc, 0);
|
|
|
|
if (sStart < 0)
|
|
status_exception::raise(Arg::Gds(isc_bad_substring_offset) << Arg::Num(sStart + 1));
|
|
else if (sLength < 0)
|
|
status_exception::raise(Arg::Gds(isc_bad_substring_length) << Arg::Num(sLength));
|
|
|
|
dsc desc;
|
|
DataTypeUtil(tdbb).makeSubstr(&desc, valueDsc, startDsc, lengthDsc);
|
|
|
|
ULONG start = (ULONG) sStart;
|
|
ULONG length = (ULONG) sLength;
|
|
|
|
if (desc.isText() && length > MAX_COLUMN_SIZE)
|
|
length = MAX_COLUMN_SIZE;
|
|
|
|
ULONG dataLen;
|
|
|
|
if (valueDsc->isBlob())
|
|
{
|
|
// Source string is a blob, things get interesting.
|
|
|
|
fb_assert(desc.dsc_dtype == dtype_blob);
|
|
|
|
desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_bid;
|
|
|
|
blb* newBlob = blb::create(tdbb, tdbb->getRequest()->req_transaction, &impure->vlu_misc.vlu_bid);
|
|
|
|
blb* blob = blb::open(tdbb, tdbb->getRequest()->req_transaction,
|
|
reinterpret_cast<bid*>(valueDsc->dsc_address));
|
|
|
|
HalfStaticArray<UCHAR, BUFFER_LARGE> buffer;
|
|
CharSet* charSet = INTL_charset_lookup(tdbb, valueDsc->getCharSet());
|
|
|
|
const FB_UINT64 byte_offset = FB_UINT64(start) * charSet->maxBytesPerChar();
|
|
const FB_UINT64 byte_length = FB_UINT64(length) * charSet->maxBytesPerChar();
|
|
|
|
if (charSet->isMultiByte())
|
|
{
|
|
buffer.getBuffer(MIN(blob->blb_length, byte_offset + byte_length));
|
|
dataLen = blob->BLB_get_data(tdbb, buffer.begin(), buffer.getCount(), false);
|
|
|
|
HalfStaticArray<UCHAR, BUFFER_LARGE> buffer2;
|
|
buffer2.getBuffer(dataLen);
|
|
|
|
dataLen = charSet->substring(dataLen, buffer.begin(),
|
|
buffer2.getCapacity(), buffer2.begin(), start, length);
|
|
newBlob->BLB_put_data(tdbb, buffer2.begin(), dataLen);
|
|
}
|
|
else if (byte_offset < blob->blb_length)
|
|
{
|
|
start = byte_offset;
|
|
length = MIN(blob->blb_length, byte_length);
|
|
|
|
while (!(blob->blb_flags & BLB_eof) && start)
|
|
{
|
|
// Both cases are the same for now. Let's see if we can optimize in the future.
|
|
ULONG l1 = blob->BLB_get_data(tdbb, buffer.begin(),
|
|
MIN(buffer.getCapacity(), start), false);
|
|
start -= l1;
|
|
}
|
|
|
|
while (!(blob->blb_flags & BLB_eof) && length)
|
|
{
|
|
dataLen = blob->BLB_get_data(tdbb, buffer.begin(),
|
|
MIN(length, buffer.getCapacity()), false);
|
|
length -= dataLen;
|
|
|
|
newBlob->BLB_put_data(tdbb, buffer.begin(), dataLen);
|
|
}
|
|
}
|
|
|
|
blob->BLB_close(tdbb);
|
|
newBlob->BLB_close(tdbb);
|
|
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
}
|
|
else
|
|
{
|
|
fb_assert(desc.isText());
|
|
|
|
desc.dsc_dtype = dtype_text;
|
|
|
|
// CVC: I didn't bother to define a larger buffer because:
|
|
// - Native types when converted to string don't reach 31 bytes plus terminator.
|
|
// - String types do not need and do not use the buffer ("temp") to be pulled.
|
|
// - The types that can cause an error() issued inside the low level MOV/CVT
|
|
// routines because the "temp" is not enough are blob and array but at this time
|
|
// they aren't accepted, so they will cause error() to be called anyway.
|
|
VaryStr<32> temp;
|
|
USHORT ttype;
|
|
desc.dsc_length = MOV_get_string_ptr(valueDsc, &ttype, &desc.dsc_address,
|
|
&temp, sizeof(temp));
|
|
desc.setTextType(ttype);
|
|
|
|
// CVC: Why bother? If the start is greater or equal than the length in bytes,
|
|
// it's impossible that the start be less than the length in an international charset.
|
|
if (start >= desc.dsc_length || !length)
|
|
{
|
|
desc.dsc_length = 0;
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
}
|
|
// CVC: God save the king if the engine doesn't protect itself against buffer overruns,
|
|
// because intl.h defines UNICODE as the type of most system relations' string fields.
|
|
// Also, the field charset can come as 127 (dynamic) when it comes from system triggers,
|
|
// but it's resolved by INTL_obj_lookup() to UNICODE_FSS in the cases I observed. Here I cannot
|
|
// distinguish between user calls and system calls. Unlike the original ASCII substring(),
|
|
// this one will get correctly the amount of UNICODE characters requested.
|
|
else if (ttype == ttype_ascii || ttype == ttype_none || ttype == ttype_binary)
|
|
{
|
|
/* Redundant.
|
|
if (start >= desc.dsc_length)
|
|
desc.dsc_length = 0;
|
|
else */
|
|
desc.dsc_address += start;
|
|
desc.dsc_length -= start;
|
|
if (length < desc.dsc_length)
|
|
desc.dsc_length = length;
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
}
|
|
else
|
|
{
|
|
// CVC: ATTENTION:
|
|
// I couldn't find an appropriate message for this failure among current registered
|
|
// messages, so I will return empty.
|
|
// Finally I decided to use arithmetic exception or numeric overflow.
|
|
const UCHAR* p = desc.dsc_address;
|
|
const USHORT pcount = desc.dsc_length;
|
|
|
|
CharSet* charSet = INTL_charset_lookup(tdbb, desc.getCharSet());
|
|
|
|
desc.dsc_address = NULL;
|
|
const ULONG totLen = MIN(MAX_COLUMN_SIZE, length * charSet->maxBytesPerChar());
|
|
desc.dsc_length = totLen;
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
|
|
dataLen = charSet->substring(pcount, p, totLen,
|
|
impure->vlu_desc.dsc_address, start, length);
|
|
impure->vlu_desc.dsc_length = static_cast<USHORT>(dataLen);
|
|
}
|
|
}
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<SubstringSimilarNode> regSubstringSimilarNode(blr_substring_similar);
|
|
|
|
SubstringSimilarNode::SubstringSimilarNode(MemoryPool& pool, ValueExprNode* aExpr,
|
|
ValueExprNode* aPattern, ValueExprNode* aEscape)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_SUBSTRING_SIMILAR>(pool),
|
|
expr(aExpr),
|
|
pattern(aPattern),
|
|
escape(aEscape)
|
|
{
|
|
addChildNode(expr, expr);
|
|
addChildNode(pattern, pattern);
|
|
addChildNode(escape, escape);
|
|
}
|
|
|
|
DmlNode* SubstringSimilarNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb,
|
|
const UCHAR /*blrOp*/)
|
|
{
|
|
SubstringSimilarNode* node = FB_NEW(pool) SubstringSimilarNode(pool);
|
|
node->expr = PAR_parse_value(tdbb, csb);
|
|
node->pattern = PAR_parse_value(tdbb, csb);
|
|
node->escape = PAR_parse_value(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
void SubstringSimilarNode::print(string& text) const
|
|
{
|
|
text = "SubstringSimilarNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void SubstringSimilarNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "SUBSTRING";
|
|
}
|
|
|
|
bool SubstringSimilarNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, expr, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, pattern, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, escape, desc, forceVarChar);
|
|
}
|
|
|
|
void SubstringSimilarNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_substring_similar);
|
|
GEN_expr(dsqlScratch, expr);
|
|
GEN_expr(dsqlScratch, pattern);
|
|
GEN_expr(dsqlScratch, escape);
|
|
}
|
|
|
|
void SubstringSimilarNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
MAKE_desc(dsqlScratch, desc, expr);
|
|
desc->setNullable(true);
|
|
}
|
|
|
|
void SubstringSimilarNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
expr->getDesc(tdbb, csb, desc);
|
|
|
|
dsc tempDesc;
|
|
pattern->getDesc(tdbb, csb, &tempDesc);
|
|
escape->getDesc(tdbb, csb, &tempDesc);
|
|
}
|
|
|
|
ValueExprNode* SubstringSimilarNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
SubstringSimilarNode* node = FB_NEW(*tdbb->getDefaultPool()) SubstringSimilarNode(
|
|
*tdbb->getDefaultPool());
|
|
node->expr = copier.copy(tdbb, expr);
|
|
node->pattern = copier.copy(tdbb, pattern);
|
|
node->escape = copier.copy(tdbb, escape);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* SubstringSimilarNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
doPass1(tdbb, csb, expr.getAddress());
|
|
|
|
// We need to take care of invariantness expressions to be able to pre-compile the pattern.
|
|
nodFlags |= FLAG_INVARIANT;
|
|
csb->csb_current_nodes.push(this);
|
|
|
|
doPass1(tdbb, csb, pattern.getAddress());
|
|
doPass1(tdbb, csb, escape.getAddress());
|
|
|
|
csb->csb_current_nodes.pop();
|
|
|
|
// If there is no top-level RSE present and patterns are not constant, unmark node as invariant
|
|
// because it may be dependent on data or variables.
|
|
if ((nodFlags & FLAG_INVARIANT) && (!pattern->is<LiteralNode>() || !escape->is<LiteralNode>()))
|
|
{
|
|
const RseOrExprNode* ctx_node, *end;
|
|
|
|
for (ctx_node = csb->csb_current_nodes.begin(), end = csb->csb_current_nodes.end();
|
|
ctx_node != end; ++ctx_node)
|
|
{
|
|
if (ctx_node->rseNode)
|
|
break;
|
|
}
|
|
|
|
if (ctx_node >= end)
|
|
nodFlags &= ~FLAG_INVARIANT;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* SubstringSimilarNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
if (nodFlags & FLAG_INVARIANT)
|
|
csb->csb_invariants.push(&impureOffset);
|
|
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* SubstringSimilarNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
// Run all expression arguments.
|
|
|
|
const dsc* exprDesc = EVL_expr(tdbb, request, expr);
|
|
exprDesc = (request->req_flags & req_null) ? NULL : exprDesc;
|
|
|
|
const dsc* patternDesc = EVL_expr(tdbb, request, pattern);
|
|
patternDesc = (request->req_flags & req_null) ? NULL : patternDesc;
|
|
|
|
const dsc* escapeDesc = EVL_expr(tdbb, request, escape);
|
|
escapeDesc = (request->req_flags & req_null) ? NULL : escapeDesc;
|
|
|
|
// If any of them is NULL, return NULL.
|
|
if (!exprDesc || !patternDesc || !escapeDesc)
|
|
return NULL;
|
|
|
|
USHORT textType = exprDesc->getTextType();
|
|
Collation* collation = INTL_texttype_lookup(tdbb, textType);
|
|
CharSet* charSet = collation->getCharSet();
|
|
|
|
MoveBuffer exprBuffer;
|
|
UCHAR* exprStr;
|
|
int exprLen = MOV_make_string2(tdbb, exprDesc, textType, &exprStr, exprBuffer);
|
|
|
|
MoveBuffer patternBuffer;
|
|
UCHAR* patternStr;
|
|
int patternLen = MOV_make_string2(tdbb, patternDesc, textType, &patternStr, patternBuffer);
|
|
|
|
MoveBuffer escapeBuffer;
|
|
UCHAR* escapeStr;
|
|
int escapeLen = MOV_make_string2(tdbb, escapeDesc, textType, &escapeStr, escapeBuffer);
|
|
|
|
// Verify the correctness of the escape character.
|
|
if (escapeLen == 0 || charSet->length(escapeLen, escapeStr, true) != 1)
|
|
ERR_post(Arg::Gds(isc_escape_invalid));
|
|
|
|
impure_value* impure = request->getImpure<impure_value>(impureOffset);
|
|
|
|
AutoPtr<BaseSubstringSimilarMatcher> autoEvaluator; // deallocate non-invariant evaluator
|
|
BaseSubstringSimilarMatcher* evaluator;
|
|
|
|
if (nodFlags & FLAG_INVARIANT)
|
|
{
|
|
if (!(impure->vlu_flags & VLU_computed))
|
|
{
|
|
delete impure->vlu_misc.vlu_invariant;
|
|
|
|
impure->vlu_misc.vlu_invariant = evaluator = collation->createSubstringSimilarMatcher(
|
|
*tdbb->getDefaultPool(), patternStr, patternLen, escapeStr, escapeLen);
|
|
|
|
impure->vlu_flags |= VLU_computed;
|
|
}
|
|
else
|
|
{
|
|
evaluator = static_cast<BaseSubstringSimilarMatcher*>(impure->vlu_misc.vlu_invariant);
|
|
evaluator->reset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
autoEvaluator = evaluator = collation->createSubstringSimilarMatcher(*tdbb->getDefaultPool(),
|
|
patternStr, patternLen, escapeStr, escapeLen);
|
|
}
|
|
|
|
evaluator->process(exprStr, exprLen);
|
|
|
|
if (evaluator->result())
|
|
{
|
|
// Get the byte bounds of the matched substring.
|
|
unsigned start = 0;
|
|
unsigned length = 0;
|
|
evaluator->getResultInfo(&start, &length);
|
|
|
|
dsc desc;
|
|
desc.makeText((USHORT) exprLen, textType);
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
|
|
// And return it.
|
|
memcpy(impure->vlu_desc.dsc_address, exprStr + start, length);
|
|
impure->vlu_desc.dsc_length = length;
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
else
|
|
return NULL; // No match. Return NULL.
|
|
}
|
|
|
|
ValueExprNode* SubstringSimilarNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
SubstringSimilarNode* node = FB_NEW(getPool()) SubstringSimilarNode(getPool(),
|
|
doDsqlPass(dsqlScratch, expr),
|
|
doDsqlPass(dsqlScratch, pattern),
|
|
doDsqlPass(dsqlScratch, escape));
|
|
|
|
// ? SIMILAR FIELD case.
|
|
PASS1_set_parameter_type(dsqlScratch, node->expr, node->pattern, true);
|
|
|
|
// FIELD SIMILAR ? case.
|
|
PASS1_set_parameter_type(dsqlScratch, node->pattern, node->expr, true);
|
|
|
|
// X SIMILAR Y ESCAPE ? case.
|
|
PASS1_set_parameter_type(dsqlScratch, node->escape, node->pattern, true);
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<SysFuncCallNode> regSysFuncCallNode(blr_sys_function);
|
|
|
|
SysFuncCallNode::SysFuncCallNode(MemoryPool& pool, const MetaName& aName, ValueListNode* aArgs)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_SYSFUNC_CALL>(pool),
|
|
name(pool, aName),
|
|
dsqlSpecialSyntax(false),
|
|
args(aArgs),
|
|
function(NULL)
|
|
{
|
|
addChildNode(args, args);
|
|
}
|
|
|
|
DmlNode* SysFuncCallNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb,
|
|
const UCHAR /*blrOp*/)
|
|
{
|
|
MetaName name;
|
|
const USHORT count = PAR_name(csb, name);
|
|
|
|
SysFuncCallNode* node = FB_NEW(pool) SysFuncCallNode(pool, name);
|
|
node->function = SysFunction::lookup(name);
|
|
|
|
if (!node->function)
|
|
{
|
|
csb->csb_blr_reader.seekBackward(count);
|
|
PAR_error(csb, Arg::Gds(isc_funnotdef) << Arg::Str(name));
|
|
}
|
|
|
|
node->args = PAR_args(tdbb, csb);
|
|
|
|
return node;
|
|
}
|
|
|
|
void SysFuncCallNode::print(string& text) const
|
|
{
|
|
text = "SysFuncCallNode\n\tname: " + string(name.c_str());
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void SysFuncCallNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = name;
|
|
}
|
|
|
|
void SysFuncCallNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (args->items.getCount() > MAX_UCHAR)
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_max_args_exceeded) << Arg::Num(MAX_UCHAR) << function->name);
|
|
}
|
|
|
|
dsqlScratch->appendUChar(blr_sys_function);
|
|
dsqlScratch->appendMetaString(function->name.c_str());
|
|
dsqlScratch->appendUChar(args->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* ptr = args->items.begin(); ptr != args->items.end(); ++ptr)
|
|
GEN_expr(dsqlScratch, *ptr);
|
|
}
|
|
|
|
void SysFuncCallNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
Array<const dsc*> argsArray;
|
|
|
|
for (NestConst<ValueExprNode>* p = args->items.begin(); p != args->items.end(); ++p)
|
|
{
|
|
MAKE_desc(dsqlScratch, &(*p)->nodDesc, *p);
|
|
argsArray.add(&(*p)->nodDesc);
|
|
}
|
|
|
|
DSqlDataTypeUtil dataTypeUtil(dsqlScratch);
|
|
function->checkArgsMismatch(argsArray.getCount());
|
|
function->makeFunc(&dataTypeUtil, function, desc, argsArray.getCount(), argsArray.begin());
|
|
}
|
|
|
|
void SysFuncCallNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
Array<const dsc*> argsArray;
|
|
|
|
for (NestConst<ValueExprNode>* p = args->items.begin(); p != args->items.end(); ++p)
|
|
{
|
|
dsc* targetDesc = FB_NEW(*tdbb->getDefaultPool()) dsc();
|
|
argsArray.push(targetDesc);
|
|
(*p)->getDesc(tdbb, csb, targetDesc);
|
|
|
|
// dsc_address is verified in makeFunc to get literals. If the node is not a
|
|
// literal, set it to NULL, to prevent wrong interpretation of offsets as
|
|
// pointers - CORE-2612.
|
|
if (!(*p)->is<LiteralNode>())
|
|
targetDesc->dsc_address = NULL;
|
|
}
|
|
|
|
DataTypeUtil dataTypeUtil(tdbb);
|
|
function->makeFunc(&dataTypeUtil, function, desc, argsArray.getCount(), argsArray.begin());
|
|
|
|
for (const dsc** pArgs = argsArray.begin(); pArgs != argsArray.end(); ++pArgs)
|
|
delete *pArgs;
|
|
}
|
|
|
|
ValueExprNode* SysFuncCallNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
SysFuncCallNode* node = FB_NEW(*tdbb->getDefaultPool()) SysFuncCallNode(
|
|
*tdbb->getDefaultPool(), name);
|
|
node->args = copier.copy(tdbb, args);
|
|
node->function = function;
|
|
return node;
|
|
}
|
|
|
|
bool SysFuncCallNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const SysFuncCallNode* otherNode = other->as<SysFuncCallNode>();
|
|
|
|
return name == otherNode->name;
|
|
}
|
|
|
|
bool SysFuncCallNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const SysFuncCallNode* const otherNode = other->as<SysFuncCallNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return function && function == otherNode->function;
|
|
}
|
|
|
|
ValueExprNode* SysFuncCallNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
function->checkArgsMismatch(args->items.getCount());
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* SysFuncCallNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* impure = request->getImpure<impure_value>(impureOffset);
|
|
return function->evlFunc(tdbb, function, args->items, impure);
|
|
}
|
|
|
|
ValueExprNode* SysFuncCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
QualifiedName qualifName(name);
|
|
|
|
if (!dsqlSpecialSyntax && METD_get_function(dsqlScratch->getTransaction(), dsqlScratch, qualifName))
|
|
{
|
|
UdfCallNode* node = FB_NEW(getPool()) UdfCallNode(getPool(), qualifName, args);
|
|
return node->dsqlPass(dsqlScratch);
|
|
}
|
|
|
|
SysFuncCallNode* node = FB_NEW(getPool()) SysFuncCallNode(getPool(), name,
|
|
doDsqlPass(dsqlScratch, args));
|
|
node->dsqlSpecialSyntax = dsqlSpecialSyntax;
|
|
|
|
node->function = SysFunction::lookup(name);
|
|
|
|
if (node->function && node->function->setParamsFunc)
|
|
{
|
|
ValueListNode* inList = node->args;
|
|
Array<dsc*> argsArray;
|
|
|
|
for (unsigned int i = 0; i < inList->items.getCount(); ++i)
|
|
{
|
|
ValueExprNode* p = inList->items[i];
|
|
MAKE_desc(dsqlScratch, &p->nodDesc, p);
|
|
argsArray.add(&p->nodDesc);
|
|
}
|
|
|
|
DSqlDataTypeUtil dataTypeUtil(dsqlScratch);
|
|
node->function->setParamsFunc(&dataTypeUtil, node->function,
|
|
argsArray.getCount(), argsArray.begin());
|
|
|
|
for (unsigned int i = 0; i < inList->items.getCount(); ++i)
|
|
{
|
|
ValueExprNode* p = inList->items[i];
|
|
PASS1_set_parameter_type(dsqlScratch, p, &p->nodDesc, false);
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<TrimNode> regTrimNode(blr_trim);
|
|
|
|
TrimNode::TrimNode(MemoryPool& pool, UCHAR aWhere, ValueExprNode* aValue, ValueExprNode* aTrimChars)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_TRIM>(pool),
|
|
where(aWhere),
|
|
value(aValue),
|
|
trimChars(aTrimChars)
|
|
{
|
|
addChildNode(value, value);
|
|
addChildNode(trimChars, trimChars);
|
|
}
|
|
|
|
DmlNode* TrimNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
UCHAR where = csb->csb_blr_reader.getByte();
|
|
UCHAR what = csb->csb_blr_reader.getByte();
|
|
|
|
TrimNode* node = FB_NEW(pool) TrimNode(pool, where);
|
|
|
|
if (what == blr_trim_characters)
|
|
node->trimChars = PAR_parse_value(tdbb, csb);
|
|
|
|
node->value = PAR_parse_value(tdbb, csb);
|
|
|
|
return node;
|
|
}
|
|
|
|
void TrimNode::print(string& text) const
|
|
{
|
|
text = "TrimNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* TrimNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
TrimNode* node = FB_NEW(getPool()) TrimNode(getPool(), where,
|
|
doDsqlPass(dsqlScratch, value), doDsqlPass(dsqlScratch, trimChars));
|
|
|
|
// Try to force trimChars to be same type as value: TRIM(? FROM FIELD)
|
|
PASS1_set_parameter_type(dsqlScratch, node->trimChars, node->value, false);
|
|
|
|
return node;
|
|
}
|
|
|
|
void TrimNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "TRIM";
|
|
}
|
|
|
|
bool TrimNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, value, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, trimChars, desc, forceVarChar);
|
|
}
|
|
|
|
void TrimNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->appendUChar(blr_trim);
|
|
dsqlScratch->appendUChar(where);
|
|
|
|
if (trimChars)
|
|
{
|
|
dsqlScratch->appendUChar(blr_trim_characters);
|
|
GEN_expr(dsqlScratch, trimChars);
|
|
}
|
|
else
|
|
dsqlScratch->appendUChar(blr_trim_spaces);
|
|
|
|
GEN_expr(dsqlScratch, value);
|
|
}
|
|
|
|
void TrimNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
dsc desc1, desc2;
|
|
|
|
MAKE_desc(dsqlScratch, &desc1, value);
|
|
|
|
if (trimChars)
|
|
MAKE_desc(dsqlScratch, &desc2, trimChars);
|
|
else
|
|
desc2.dsc_flags = 0;
|
|
|
|
if (desc1.dsc_dtype == dtype_blob)
|
|
{
|
|
*desc = desc1;
|
|
desc->dsc_flags |= (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
}
|
|
else if (desc1.dsc_dtype <= dtype_any_text)
|
|
{
|
|
*desc = desc1;
|
|
desc->dsc_dtype = dtype_varying;
|
|
desc->dsc_length = static_cast<int>(sizeof(USHORT)) + DSC_string_length(&desc1);
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
}
|
|
else
|
|
{
|
|
desc->dsc_dtype = dtype_varying;
|
|
desc->dsc_scale = 0;
|
|
desc->dsc_ttype() = ttype_ascii;
|
|
desc->dsc_length = static_cast<int>(sizeof(USHORT)) + DSC_string_length(&desc1);
|
|
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
|
|
}
|
|
}
|
|
|
|
void TrimNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
value->getDesc(tdbb, csb, desc);
|
|
|
|
if (trimChars)
|
|
{
|
|
dsc desc1;
|
|
trimChars->getDesc(tdbb, csb, &desc1);
|
|
desc->dsc_flags |= desc1.dsc_flags & DSC_null;
|
|
}
|
|
|
|
if (desc->dsc_dtype != dtype_blob)
|
|
{
|
|
USHORT length = DSC_string_length(desc);
|
|
|
|
if (!DTYPE_IS_TEXT(desc->dsc_dtype))
|
|
{
|
|
desc->dsc_ttype() = ttype_ascii;
|
|
desc->dsc_scale = 0;
|
|
}
|
|
|
|
desc->dsc_dtype = dtype_varying;
|
|
desc->dsc_length = length + sizeof(USHORT);
|
|
}
|
|
}
|
|
|
|
ValueExprNode* TrimNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
TrimNode* node = FB_NEW(*tdbb->getDefaultPool()) TrimNode(*tdbb->getDefaultPool(), where);
|
|
node->value = copier.copy(tdbb, value);
|
|
if (trimChars)
|
|
node->trimChars = copier.copy(tdbb, trimChars);
|
|
return node;
|
|
}
|
|
|
|
bool TrimNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const TrimNode* o = other->as<TrimNode>();
|
|
fb_assert(o);
|
|
|
|
return where == o->where;
|
|
}
|
|
|
|
bool TrimNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const TrimNode* const otherNode = other->as<TrimNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return where == otherNode->where;
|
|
}
|
|
|
|
ValueExprNode* TrimNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
// Perform trim function = TRIM([where what FROM] string).
|
|
dsc* TrimNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* impure = request->getImpure<impure_value>(impureOffset);
|
|
request->req_flags &= ~req_null;
|
|
|
|
dsc* trimCharsDesc = (trimChars ? EVL_expr(tdbb, request, trimChars) : NULL);
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
dsc* valueDesc = EVL_expr(tdbb, request, value);
|
|
if (request->req_flags & req_null)
|
|
return NULL;
|
|
|
|
USHORT ttype = INTL_TEXT_TYPE(*valueDesc);
|
|
TextType* tt = INTL_texttype_lookup(tdbb, ttype);
|
|
CharSet* cs = tt->getCharSet();
|
|
|
|
const UCHAR* charactersAddress;
|
|
MoveBuffer charactersBuffer;
|
|
USHORT charactersLength;
|
|
|
|
if (trimCharsDesc)
|
|
{
|
|
UCHAR* tempAddress = NULL;
|
|
|
|
if (trimCharsDesc->isBlob())
|
|
{
|
|
UCharBuffer bpb;
|
|
CharSet* charsCharSet;
|
|
|
|
if (trimCharsDesc->getBlobSubType() == isc_blob_text)
|
|
{
|
|
BLB_gen_bpb_from_descs(trimCharsDesc, valueDesc, bpb);
|
|
charsCharSet = INTL_charset_lookup(tdbb, trimCharsDesc->getCharSet());
|
|
}
|
|
else
|
|
charsCharSet = cs;
|
|
|
|
blb* blob = blb::open2(tdbb, request->req_transaction,
|
|
reinterpret_cast<bid*>(trimCharsDesc->dsc_address), bpb.getCount(), bpb.begin());
|
|
|
|
// Go simple way and always read entire blob in memory.
|
|
|
|
unsigned maxLen = blob->blb_length / charsCharSet->minBytesPerChar() *
|
|
cs->maxBytesPerChar();
|
|
|
|
tempAddress = charactersBuffer.getBuffer(maxLen);
|
|
charactersLength = blob->BLB_get_data(tdbb, tempAddress, maxLen, true);
|
|
}
|
|
else
|
|
{
|
|
charactersLength = MOV_make_string2(tdbb, trimCharsDesc, ttype,
|
|
&tempAddress, charactersBuffer);
|
|
}
|
|
|
|
charactersAddress = tempAddress;
|
|
}
|
|
else
|
|
{
|
|
charactersLength = tt->getCharSet()->getSpaceLength();
|
|
charactersAddress = tt->getCharSet()->getSpace();
|
|
}
|
|
|
|
HalfStaticArray<UCHAR, BUFFER_SMALL> charactersCanonical;
|
|
charactersCanonical.getBuffer(charactersLength / tt->getCharSet()->minBytesPerChar() *
|
|
tt->getCanonicalWidth());
|
|
const SLONG charactersCanonicalLen = tt->canonical(charactersLength, charactersAddress,
|
|
charactersCanonical.getCount(), charactersCanonical.begin()) * tt->getCanonicalWidth();
|
|
|
|
MoveBuffer valueBuffer;
|
|
UCHAR* valueAddress;
|
|
ULONG valueLength;
|
|
|
|
if (valueDesc->isBlob())
|
|
{
|
|
// Source string is a blob, things get interesting.
|
|
blb* blob = blb::open(tdbb, request->req_transaction,
|
|
reinterpret_cast<bid*>(valueDesc->dsc_address));
|
|
|
|
// It's very difficult (and probably not very efficient) to trim a blob in chunks.
|
|
// So go simple way and always read entire blob in memory.
|
|
valueAddress = valueBuffer.getBuffer(blob->blb_length);
|
|
valueLength = blob->BLB_get_data(tdbb, valueAddress, blob->blb_length, true);
|
|
}
|
|
else
|
|
valueLength = MOV_make_string2(tdbb, valueDesc, ttype, &valueAddress, valueBuffer);
|
|
|
|
HalfStaticArray<UCHAR, BUFFER_SMALL> valueCanonical;
|
|
valueCanonical.getBuffer(valueLength / cs->minBytesPerChar() * tt->getCanonicalWidth());
|
|
const SLONG valueCanonicalLen = tt->canonical(valueLength, valueAddress,
|
|
valueCanonical.getCount(), valueCanonical.begin()) * tt->getCanonicalWidth();
|
|
|
|
SLONG offsetLead = 0;
|
|
SLONG offsetTrail = valueCanonicalLen;
|
|
|
|
// CVC: Avoid endless loop with zero length trim chars.
|
|
if (charactersCanonicalLen)
|
|
{
|
|
if (where == blr_trim_both || where == blr_trim_leading)
|
|
{
|
|
// CVC: Prevent surprises with offsetLead < valueCanonicalLen; it may fail.
|
|
for (; offsetLead + charactersCanonicalLen <= valueCanonicalLen;
|
|
offsetLead += charactersCanonicalLen)
|
|
{
|
|
if (memcmp(charactersCanonical.begin(), &valueCanonical[offsetLead],
|
|
charactersCanonicalLen) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (where == blr_trim_both || where == blr_trim_trailing)
|
|
{
|
|
for (; offsetTrail - charactersCanonicalLen >= offsetLead;
|
|
offsetTrail -= charactersCanonicalLen)
|
|
{
|
|
if (memcmp(charactersCanonical.begin(),
|
|
&valueCanonical[offsetTrail - charactersCanonicalLen],
|
|
charactersCanonicalLen) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valueDesc->isBlob())
|
|
{
|
|
// We have valueCanonical already allocated.
|
|
// Use it to get the substring that will be written to the new blob.
|
|
const ULONG len = cs->substring(valueLength, valueAddress,
|
|
valueCanonical.getCapacity(), valueCanonical.begin(),
|
|
offsetLead / tt->getCanonicalWidth(),
|
|
(offsetTrail - offsetLead) / tt->getCanonicalWidth());
|
|
|
|
EVL_make_value(tdbb, valueDesc, impure);
|
|
|
|
blb* newBlob = blb::create(tdbb, tdbb->getRequest()->req_transaction, &impure->vlu_misc.vlu_bid);
|
|
newBlob->BLB_put_data(tdbb, valueCanonical.begin(), len);
|
|
newBlob->BLB_close(tdbb);
|
|
}
|
|
else
|
|
{
|
|
dsc desc;
|
|
desc.makeText(valueLength, ttype);
|
|
EVL_make_value(tdbb, &desc, impure);
|
|
|
|
impure->vlu_desc.dsc_length = cs->substring(valueLength, valueAddress,
|
|
impure->vlu_desc.dsc_length, impure->vlu_desc.dsc_address,
|
|
offsetLead / tt->getCanonicalWidth(),
|
|
(offsetTrail - offsetLead) / tt->getCanonicalWidth());
|
|
}
|
|
|
|
return &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<UdfCallNode> regUdfCallNode1(blr_function);
|
|
static RegisterNode<UdfCallNode> regUdfCallNode2(blr_function2);
|
|
static RegisterNode<UdfCallNode> regUdfCallNode3(blr_subfunc);
|
|
|
|
UdfCallNode::UdfCallNode(MemoryPool& pool, const QualifiedName& aName, ValueListNode* aArgs)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_UDF_CALL>(pool),
|
|
name(pool, aName),
|
|
args(aArgs),
|
|
function(NULL),
|
|
dsqlFunction(NULL)
|
|
{
|
|
addChildNode(args, args);
|
|
}
|
|
|
|
DmlNode* UdfCallNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb,
|
|
const UCHAR blrOp)
|
|
{
|
|
const UCHAR* savePos = csb->csb_blr_reader.getPos();
|
|
|
|
QualifiedName name;
|
|
USHORT count = 0;
|
|
|
|
if (blrOp == blr_function2)
|
|
count = PAR_name(csb, name.package);
|
|
|
|
count += PAR_name(csb, name.identifier);
|
|
|
|
UdfCallNode* node = FB_NEW(pool) UdfCallNode(pool, name);
|
|
|
|
if (blrOp == blr_function &&
|
|
(name.identifier == "RDB$GET_CONTEXT" || name.identifier == "RDB$SET_CONTEXT"))
|
|
{
|
|
csb->csb_blr_reader.setPos(savePos);
|
|
return SysFuncCallNode::parse(tdbb, pool, csb, blr_sys_function);
|
|
}
|
|
|
|
if (blrOp == blr_subfunc)
|
|
{
|
|
DeclareSubFuncNode* declareNode;
|
|
if (csb->subFunctions.get(name.identifier, declareNode))
|
|
node->function = declareNode->routine;
|
|
}
|
|
|
|
Function* function = node->function;
|
|
|
|
if (!function)
|
|
function = node->function = Function::lookup(tdbb, name, false);
|
|
|
|
if (function)
|
|
{
|
|
if (function->isImplemented() && !function->isDefined())
|
|
{
|
|
if (tdbb->getAttachment()->isGbak())
|
|
{
|
|
PAR_warning(Arg::Warning(isc_funnotdef) << Arg::Str(name.toString()) <<
|
|
Arg::Warning(isc_modnotfound));
|
|
}
|
|
else
|
|
{
|
|
csb->csb_blr_reader.seekBackward(count);
|
|
PAR_error(csb, Arg::Gds(isc_funnotdef) << Arg::Str(name.toString()) <<
|
|
Arg::Gds(isc_modnotfound));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
csb->csb_blr_reader.seekBackward(count);
|
|
PAR_error(csb, Arg::Gds(isc_funnotdef) << Arg::Str(name.toString()));
|
|
}
|
|
|
|
const UCHAR argCount = csb->csb_blr_reader.getByte();
|
|
|
|
// Check to see if the argument count matches.
|
|
if (argCount < function->fun_inputs - function->getDefaultCount() || argCount > function->fun_inputs)
|
|
PAR_error(csb, Arg::Gds(isc_funmismat) << name.toString());
|
|
|
|
node->args = PAR_args(tdbb, csb, argCount, function->fun_inputs);
|
|
|
|
for (USHORT i = argCount; i < function->fun_inputs; ++i)
|
|
{
|
|
Parameter* const parameter = function->getInputFields()[i];
|
|
node->args->items[i] = CMP_clone_node(tdbb, csb, parameter->prm_default_value);
|
|
}
|
|
|
|
// CVC: I will track ufds only if a function is not being dropped.
|
|
if (!function->isSubRoutine() && (csb->csb_g_flags & csb_get_dependencies))
|
|
{
|
|
CompilerScratch::Dependency dependency(obj_udf);
|
|
dependency.function = function;
|
|
csb->csb_dependencies.push(dependency);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void UdfCallNode::print(string& text) const
|
|
{
|
|
text = "UdfCallNode\n\tname: " + name.toString();
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
void UdfCallNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = dsqlFunction->udf_name.identifier;
|
|
}
|
|
|
|
void UdfCallNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
if (dsqlFunction->udf_name.package.isEmpty())
|
|
dsqlScratch->appendUChar((dsqlFunction->udf_flags & UDF_subfunc) ? blr_subfunc : blr_function);
|
|
else
|
|
{
|
|
dsqlScratch->appendUChar(blr_function2);
|
|
dsqlScratch->appendMetaString(dsqlFunction->udf_name.package.c_str());
|
|
}
|
|
|
|
dsqlScratch->appendMetaString(dsqlFunction->udf_name.identifier.c_str());
|
|
dsqlScratch->appendUChar(args->items.getCount());
|
|
|
|
for (NestConst<ValueExprNode>* ptr = args->items.begin(); ptr != args->items.end(); ++ptr)
|
|
GEN_expr(dsqlScratch, *ptr);
|
|
}
|
|
|
|
void UdfCallNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
desc->dsc_dtype = static_cast<UCHAR>(dsqlFunction->udf_dtype);
|
|
desc->dsc_length = dsqlFunction->udf_length;
|
|
desc->dsc_scale = static_cast<SCHAR>(dsqlFunction->udf_scale);
|
|
// CVC: Setting flags to zero obviously impeded DSQL to acknowledge
|
|
// the fact that any UDF can return NULL simply returning a NULL
|
|
// pointer.
|
|
desc->setNullable(true);
|
|
|
|
if (desc->dsc_dtype <= dtype_any_text)
|
|
{
|
|
desc->dsc_ttype() = dsqlFunction->udf_character_set_id;
|
|
|
|
// UNICODE_FSS_HACK
|
|
// Fix UNICODE_FSS wrong length used in system tables.
|
|
if ((dsqlFunction->udf_flags & UDF_sys_based) && (desc->dsc_ttype() == CS_UNICODE_FSS))
|
|
desc->dsc_length *= 3;
|
|
}
|
|
else
|
|
desc->dsc_ttype() = dsqlFunction->udf_sub_type;
|
|
}
|
|
|
|
void UdfCallNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
// Null value for the function indicates that the function was not
|
|
// looked up during parsing the BLR. This is true if the function
|
|
// referenced in the procedure BLR was dropped before dropping the
|
|
// procedure itself. Ignore the case because we are currently trying
|
|
// to drop the procedure.
|
|
// For normal requests, function would never be null. We would have
|
|
// created a valid block while parsing.
|
|
if (function)
|
|
*desc = function->getOutputFields()[0]->prm_desc;
|
|
else
|
|
desc->clear();
|
|
}
|
|
|
|
ValueExprNode* UdfCallNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
UdfCallNode* node = FB_NEW(*tdbb->getDefaultPool()) UdfCallNode(*tdbb->getDefaultPool(), name);
|
|
node->args = copier.copy(tdbb, args);
|
|
node->function = function;
|
|
return node;
|
|
}
|
|
|
|
bool UdfCallNode::dsqlMatch(const ExprNode* other, bool ignoreMapCast) const
|
|
{
|
|
if (!ExprNode::dsqlMatch(other, ignoreMapCast))
|
|
return false;
|
|
|
|
const UdfCallNode* otherNode = other->as<UdfCallNode>();
|
|
|
|
return name == otherNode->name;
|
|
}
|
|
|
|
bool UdfCallNode::sameAs(const ExprNode* other, bool ignoreStreams) const
|
|
{
|
|
if (!ExprNode::sameAs(other, ignoreStreams))
|
|
return false;
|
|
|
|
const UdfCallNode* const otherNode = other->as<UdfCallNode>();
|
|
fb_assert(otherNode);
|
|
|
|
return function && function == otherNode->function;
|
|
}
|
|
|
|
ValueExprNode* UdfCallNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass1(tdbb, csb);
|
|
|
|
if (!function->isSubRoutine())
|
|
{
|
|
if (!(csb->csb_g_flags & (csb_internal | csb_ignore_perm)))
|
|
{
|
|
if (function->getName().package.isEmpty())
|
|
{
|
|
CMP_post_access(tdbb, csb, function->getSecurityName(),
|
|
(csb->csb_view ? csb->csb_view->rel_id : 0),
|
|
SCL_execute, SCL_object_function, function->getName().identifier);
|
|
}
|
|
else
|
|
{
|
|
CMP_post_access(tdbb, csb, function->getSecurityName(),
|
|
(csb->csb_view ? csb->csb_view->rel_id : 0),
|
|
SCL_execute, SCL_object_package, function->getName().package);
|
|
}
|
|
|
|
ExternalAccess temp(ExternalAccess::exa_function, function->getId());
|
|
FB_SIZE_T idx;
|
|
if (!csb->csb_external.find(temp, idx))
|
|
csb->csb_external.insert(idx, temp);
|
|
}
|
|
|
|
CMP_post_resource(&csb->csb_resources, function, Resource::rsc_function, function->getId());
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* UdfCallNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
if (function->fun_deterministic && !function->fun_inputs)
|
|
{
|
|
// Deterministic function without input arguments is expected to be
|
|
// always returning the same result, so it can be marked as invariant
|
|
nodFlags |= FLAG_INVARIANT;
|
|
csb->csb_invariants.push(&impureOffset);
|
|
}
|
|
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
if (function->isDefined() && !function->fun_entrypoint)
|
|
{
|
|
if (function->getInputFormat() && function->getInputFormat()->fmt_count)
|
|
{
|
|
fb_assert(function->getInputFormat()->fmt_length);
|
|
CMP_impure(csb, function->getInputFormat()->fmt_length);
|
|
}
|
|
|
|
fb_assert(function->getOutputFormat()->fmt_count == 3);
|
|
|
|
fb_assert(function->getOutputFormat()->fmt_length);
|
|
CMP_impure(csb, function->getOutputFormat()->fmt_length);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* UdfCallNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
UCHAR* impure = request->getImpure<UCHAR>(impureOffset);
|
|
impure_value* value = request->getImpure<impure_value>(impureOffset);
|
|
|
|
USHORT& invariantFlags = value->vlu_flags;
|
|
|
|
// If the function is known as being both deterministic and invariant,
|
|
// check whether it has already been evaluated
|
|
|
|
if (nodFlags & FLAG_INVARIANT)
|
|
{
|
|
if (invariantFlags & VLU_computed)
|
|
{
|
|
if (invariantFlags & VLU_null)
|
|
request->req_flags |= req_null;
|
|
else
|
|
request->req_flags &= ~req_null;
|
|
|
|
return (request->req_flags & req_null) ? NULL : &value->vlu_desc;
|
|
}
|
|
}
|
|
|
|
if (!function->isImplemented())
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_func_pack_not_implemented) <<
|
|
Arg::Str(function->getName().identifier) << Arg::Str(function->getName().package));
|
|
}
|
|
else if (!function->isDefined())
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_funnotdef) << Arg::Str(function->getName().toString()) <<
|
|
Arg::Gds(isc_modnotfound));
|
|
}
|
|
|
|
// Evaluate the function.
|
|
|
|
if (function->fun_entrypoint)
|
|
{
|
|
const Parameter* const returnParam = function->getOutputFields()[0];
|
|
value->vlu_desc = returnParam->prm_desc;
|
|
|
|
// If the return data type is any of the string types, allocate space to hold value.
|
|
|
|
if (value->vlu_desc.dsc_dtype <= dtype_varying)
|
|
{
|
|
const USHORT retLength = value->vlu_desc.dsc_length;
|
|
VaryingString* string = value->vlu_string;
|
|
|
|
if (string && string->str_length < retLength)
|
|
{
|
|
delete string;
|
|
string = NULL;
|
|
}
|
|
|
|
if (!string)
|
|
{
|
|
string = FB_NEW_RPT(*tdbb->getDefaultPool(), retLength) VaryingString;
|
|
string->str_length = retLength;
|
|
value->vlu_string = string;
|
|
}
|
|
|
|
value->vlu_desc.dsc_address = string->str_data;
|
|
}
|
|
else
|
|
value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc;
|
|
|
|
FUN_evaluate(tdbb, function, args->items, value);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
UCHAR* const outMsg = FB_ALIGN(inMsg + inMsgLength, FB_ALIGNMENT);
|
|
|
|
if (function->fun_inputs != 0)
|
|
{
|
|
const NestConst<ValueExprNode>* const sourceEnd = args->items.end();
|
|
const NestConst<ValueExprNode>* sourcePtr = args->items.begin();
|
|
const dsc* fmtDesc = function->getInputFormat()->fmt_desc.begin();
|
|
|
|
for (; sourcePtr != sourceEnd; ++sourcePtr, fmtDesc += 2)
|
|
{
|
|
const ULONG argOffset = (IPTR) fmtDesc[0].dsc_address;
|
|
const ULONG nullOffset = (IPTR) fmtDesc[1].dsc_address;
|
|
|
|
dsc argDesc = fmtDesc[0];
|
|
argDesc.dsc_address = inMsg + argOffset;
|
|
|
|
SSHORT* const nullPtr = reinterpret_cast<SSHORT*>(inMsg + nullOffset);
|
|
|
|
dsc* const srcDesc = EVL_expr(tdbb, request, *sourcePtr);
|
|
if (srcDesc && !(request->req_flags & req_null))
|
|
{
|
|
*nullPtr = 0;
|
|
MOV_move(tdbb, srcDesc, &argDesc);
|
|
}
|
|
else
|
|
*nullPtr = -1;
|
|
}
|
|
}
|
|
|
|
jrd_tra* transaction = request->req_transaction;
|
|
const SLONG savePointNumber = transaction->tra_save_point ?
|
|
transaction->tra_save_point->sav_number : 0;
|
|
|
|
jrd_req* funcRequest = function->getStatement()->findRequest(tdbb);
|
|
|
|
// trace function execution start
|
|
TraceFuncExecute trace(tdbb, funcRequest, request, inMsg, inMsgLength);
|
|
|
|
// Catch errors so we can unwind cleanly.
|
|
|
|
try
|
|
{
|
|
Jrd::ContextPoolHolder context(tdbb, funcRequest->req_pool); // Save the old pool.
|
|
|
|
funcRequest->req_timestamp = request->req_timestamp;
|
|
|
|
EXE_start(tdbb, funcRequest, transaction);
|
|
|
|
if (inMsgLength != 0)
|
|
EXE_send(tdbb, funcRequest, 0, inMsgLength, inMsg);
|
|
|
|
EXE_receive(tdbb, funcRequest, 1, outMsgLength, outMsg);
|
|
|
|
// Clean up all savepoints started during execution of the procedure.
|
|
|
|
if (transaction != attachment->getSysTransaction())
|
|
{
|
|
for (const Savepoint* savePoint = transaction->tra_save_point;
|
|
savePoint && savePointNumber < savePoint->sav_number;
|
|
savePoint = transaction->tra_save_point)
|
|
{
|
|
VIO_verb_cleanup(tdbb, transaction);
|
|
}
|
|
}
|
|
}
|
|
catch (const Exception& ex)
|
|
{
|
|
const bool noPriv = (ex.stuff_exception(tdbb->tdbb_status_vector) == isc_no_priv);
|
|
trace.finish(noPriv ? ITracePlugin::TRACE_RESULT_UNAUTHORIZED : ITracePlugin::TRACE_RESULT_FAILED);
|
|
|
|
tdbb->setRequest(request);
|
|
EXE_unwind(tdbb, funcRequest);
|
|
funcRequest->req_attachment = NULL;
|
|
funcRequest->req_flags &= ~(req_in_use | req_proc_fetch);
|
|
funcRequest->req_timestamp.invalidate();
|
|
throw;
|
|
}
|
|
|
|
const dsc* fmtDesc = function->getOutputFormat()->fmt_desc.begin();
|
|
const ULONG nullOffset = (IPTR) fmtDesc[1].dsc_address;
|
|
SSHORT* const nullPtr = reinterpret_cast<SSHORT*>(outMsg + nullOffset);
|
|
|
|
if (*nullPtr)
|
|
{
|
|
request->req_flags |= req_null;
|
|
trace.finish(ITracePlugin::TRACE_RESULT_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
request->req_flags &= ~req_null;
|
|
|
|
const ULONG argOffset = (IPTR) fmtDesc[0].dsc_address;
|
|
value->vlu_desc = *fmtDesc;
|
|
value->vlu_desc.dsc_address = outMsg + argOffset;
|
|
|
|
trace.finish(ITracePlugin::TRACE_RESULT_SUCCESS, &value->vlu_desc);
|
|
}
|
|
|
|
EXE_unwind(tdbb, funcRequest);
|
|
tdbb->setRequest(request);
|
|
|
|
funcRequest->req_attachment = NULL;
|
|
funcRequest->req_flags &= ~(req_in_use | req_proc_fetch);
|
|
funcRequest->req_timestamp.invalidate();
|
|
}
|
|
|
|
if (!(request->req_flags & req_null))
|
|
INTL_adjust_text_descriptor(tdbb, &value->vlu_desc);
|
|
|
|
// If the function is declared as invariant, mark it as computed.
|
|
if (nodFlags & FLAG_INVARIANT)
|
|
{
|
|
invariantFlags |= VLU_computed;
|
|
|
|
if (request->req_flags & req_null)
|
|
invariantFlags |= VLU_null;
|
|
}
|
|
|
|
return (request->req_flags & req_null) ? NULL : &value->vlu_desc;
|
|
}
|
|
|
|
ValueExprNode* UdfCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
UdfCallNode* node = FB_NEW(getPool()) UdfCallNode(getPool(), name,
|
|
doDsqlPass(dsqlScratch, args));
|
|
|
|
if (name.package.isEmpty())
|
|
node->dsqlFunction = dsqlScratch->getSubFunction(name.identifier);
|
|
|
|
if (!node->dsqlFunction)
|
|
node->dsqlFunction = METD_get_function(dsqlScratch->getTransaction(), dsqlScratch, name);
|
|
|
|
if (!node->dsqlFunction)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
|
|
Arg::Gds(isc_dsql_function_err) <<
|
|
Arg::Gds(isc_random) << Arg::Str(name.toString()));
|
|
}
|
|
|
|
for (NestConst<ValueExprNode>* ptr = node->args->items.begin();
|
|
ptr != node->args->items.end();
|
|
++ptr)
|
|
{
|
|
unsigned pos = ptr - node->args->items.begin();
|
|
|
|
if (pos < node->dsqlFunction->udf_arguments.getCount())
|
|
PASS1_set_parameter_type(dsqlScratch, *ptr, &node->dsqlFunction->udf_arguments[pos], false);
|
|
else
|
|
{
|
|
// We should complain here in the future! The parameter is
|
|
// out of bounds or the function doesn't declare input params.
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<ValueIfNode> regValueIfNode(blr_value_if);
|
|
|
|
ValueIfNode::ValueIfNode(MemoryPool& pool, BoolExprNode* aCondition, ValueExprNode* aTrueValue,
|
|
ValueExprNode* aFalseValue)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_VALUE_IF>(pool),
|
|
condition(aCondition),
|
|
trueValue(aTrueValue),
|
|
falseValue(aFalseValue)
|
|
{
|
|
addChildNode(condition, condition);
|
|
addChildNode(trueValue, trueValue);
|
|
addChildNode(falseValue, falseValue);
|
|
}
|
|
|
|
DmlNode* ValueIfNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
ValueIfNode* node = FB_NEW(pool) ValueIfNode(pool);
|
|
node->condition = PAR_parse_boolean(tdbb, csb);
|
|
node->trueValue = PAR_parse_value(tdbb, csb);
|
|
node->falseValue = PAR_parse_value(tdbb, csb);
|
|
|
|
// Get rid of blr_stmt_expr expressions.
|
|
|
|
// Coalesce.
|
|
MissingBoolNode* missing = node->condition->as<MissingBoolNode>();
|
|
if (missing)
|
|
{
|
|
StmtExprNode* missingCond = missing->arg->as<StmtExprNode>();
|
|
if (!missingCond)
|
|
return node;
|
|
|
|
CompoundStmtNode* stmt = missingCond->stmt->as<CompoundStmtNode>();
|
|
DeclareVariableNode* declStmt = NULL;
|
|
AssignmentNode* assignStmt;
|
|
|
|
if (stmt)
|
|
{
|
|
if (stmt->statements.getCount() != 2 ||
|
|
!(declStmt = stmt->statements[0]->as<DeclareVariableNode>()) ||
|
|
!(assignStmt = stmt->statements[1]->as<AssignmentNode>()))
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
else if (!(assignStmt = missingCond->stmt->as<AssignmentNode>()))
|
|
return node;
|
|
|
|
VariableNode* var = node->falseValue->as<VariableNode>();
|
|
VariableNode* var2 = assignStmt->asgnTo->as<VariableNode>();
|
|
|
|
if (!var || !var2 || var->varId != var2->varId || (declStmt && declStmt->varId != var->varId))
|
|
return node;
|
|
|
|
CoalesceNode* coalesceNode = FB_NEW(pool) CoalesceNode(pool);
|
|
coalesceNode->args = FB_NEW(pool) ValueListNode(pool, 2);
|
|
coalesceNode->args->items[0] = assignStmt->asgnFrom;
|
|
coalesceNode->args->items[1] = node->trueValue;
|
|
|
|
return coalesceNode;
|
|
}
|
|
|
|
// Decode.
|
|
ComparativeBoolNode* cmp = node->condition->as<ComparativeBoolNode>();
|
|
if (cmp && cmp->blrOp == blr_eql)
|
|
{
|
|
StmtExprNode* cmpCond = cmp->arg1->as<StmtExprNode>();
|
|
if (!cmpCond)
|
|
return node;
|
|
|
|
CompoundStmtNode* stmt = cmpCond->stmt->as<CompoundStmtNode>();
|
|
DeclareVariableNode* declStmt = NULL;
|
|
AssignmentNode* assignStmt;
|
|
|
|
if (stmt)
|
|
{
|
|
if (stmt->statements.getCount() != 2 ||
|
|
!(declStmt = stmt->statements[0]->as<DeclareVariableNode>()) ||
|
|
!(assignStmt = stmt->statements[1]->as<AssignmentNode>()))
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
else if (!(assignStmt = cmpCond->stmt->as<AssignmentNode>()))
|
|
return node;
|
|
|
|
VariableNode* var = assignStmt->asgnTo->as<VariableNode>();
|
|
|
|
if (!var || (declStmt && declStmt->varId != var->varId))
|
|
return node;
|
|
|
|
DecodeNode* decodeNode = FB_NEW(pool) DecodeNode(pool);
|
|
decodeNode->test = assignStmt->asgnFrom;
|
|
decodeNode->conditions = FB_NEW(pool) ValueListNode(pool, 0u);
|
|
decodeNode->values = FB_NEW(pool) ValueListNode(pool, 0u);
|
|
|
|
decodeNode->conditions->add(cmp->arg2);
|
|
decodeNode->values->add(node->trueValue);
|
|
|
|
ValueExprNode* last = node->falseValue;
|
|
while ((node = last->as<ValueIfNode>()))
|
|
{
|
|
ComparativeBoolNode* cmp = node->condition->as<ComparativeBoolNode>();
|
|
if (!cmp || cmp->blrOp != blr_eql)
|
|
break;
|
|
|
|
VariableNode* var2 = cmp->arg1->as<VariableNode>();
|
|
|
|
if (!var2 || var2->varId != var->varId)
|
|
break;
|
|
|
|
decodeNode->conditions->add(cmp->arg2);
|
|
decodeNode->values->add(node->trueValue);
|
|
|
|
last = node->falseValue;
|
|
}
|
|
|
|
decodeNode->values->add(last);
|
|
|
|
return decodeNode;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void ValueIfNode::print(string& text) const
|
|
{
|
|
text = "ValueIfNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* ValueIfNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
ValueIfNode* node = FB_NEW(getPool()) ValueIfNode(getPool(),
|
|
doDsqlPass(dsqlScratch, condition),
|
|
doDsqlPass(dsqlScratch, trueValue),
|
|
doDsqlPass(dsqlScratch, falseValue));
|
|
|
|
PASS1_set_parameter_type(dsqlScratch, node->trueValue, node->falseValue, false);
|
|
PASS1_set_parameter_type(dsqlScratch, node->falseValue, node->trueValue, false);
|
|
|
|
return node;
|
|
}
|
|
|
|
void ValueIfNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = "CASE";
|
|
}
|
|
|
|
bool ValueIfNode::setParameterType(DsqlCompilerScratch* dsqlScratch,
|
|
const dsc* desc, bool forceVarChar)
|
|
{
|
|
return PASS1_set_parameter_type(dsqlScratch, trueValue, desc, forceVarChar) |
|
|
PASS1_set_parameter_type(dsqlScratch, falseValue, desc, forceVarChar);
|
|
}
|
|
|
|
void ValueIfNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsc desc;
|
|
make(dsqlScratch, &desc);
|
|
dsqlScratch->appendUChar(blr_cast);
|
|
GEN_descriptor(dsqlScratch, &desc, true);
|
|
|
|
dsqlScratch->appendUChar(blr_value_if);
|
|
GEN_expr(dsqlScratch, condition);
|
|
GEN_expr(dsqlScratch, trueValue);
|
|
GEN_expr(dsqlScratch, falseValue);
|
|
}
|
|
|
|
void ValueIfNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)
|
|
{
|
|
Array<const dsc*> args;
|
|
|
|
MAKE_desc(dsqlScratch, &trueValue->nodDesc, trueValue);
|
|
args.add(&trueValue->nodDesc);
|
|
|
|
MAKE_desc(dsqlScratch, &falseValue->nodDesc, falseValue);
|
|
args.add(&falseValue->nodDesc);
|
|
|
|
DSqlDataTypeUtil(dsqlScratch).makeFromList(desc, "CASE", args.getCount(), args.begin());
|
|
}
|
|
|
|
void ValueIfNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|
{
|
|
ValueExprNode* val = trueValue->is<NullNode>() ? falseValue : trueValue;
|
|
val->getDesc(tdbb, csb, desc);
|
|
}
|
|
|
|
ValueExprNode* ValueIfNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
ValueIfNode* node = FB_NEW(*tdbb->getDefaultPool()) ValueIfNode(*tdbb->getDefaultPool());
|
|
node->condition = copier.copy(tdbb, condition);
|
|
node->trueValue = copier.copy(tdbb, trueValue);
|
|
node->falseValue = copier.copy(tdbb, falseValue);
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* ValueIfNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
dsc desc;
|
|
getDesc(tdbb, csb, &desc);
|
|
impureOffset = CMP_impure(csb, sizeof(impure_value));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* ValueIfNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
return EVL_expr(tdbb, request, (condition->execute(tdbb, request) ? trueValue : falseValue));
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
static RegisterNode<VariableNode> regVariableNode(blr_variable);
|
|
|
|
VariableNode::VariableNode(MemoryPool& pool)
|
|
: TypedNode<ValueExprNode, ExprNode::TYPE_VARIABLE>(pool),
|
|
dsqlName(pool),
|
|
dsqlVar(NULL),
|
|
varId(0),
|
|
varDecl(NULL),
|
|
varInfo(NULL)
|
|
{
|
|
}
|
|
|
|
DmlNode* VariableNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
|
|
{
|
|
const USHORT n = csb->csb_blr_reader.getWord();
|
|
vec<DeclareVariableNode*>* vector = csb->csb_variables;
|
|
|
|
if (!vector || n >= vector->count())
|
|
PAR_error(csb, Arg::Gds(isc_badvarnum));
|
|
|
|
VariableNode* node = FB_NEW(pool) VariableNode(pool);
|
|
node->varId = n;
|
|
|
|
return node;
|
|
}
|
|
|
|
void VariableNode::print(string& text) const
|
|
{
|
|
text = "VariableNode";
|
|
ExprNode::print(text);
|
|
}
|
|
|
|
ValueExprNode* VariableNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
VariableNode* node = FB_NEW(getPool()) VariableNode(getPool());
|
|
node->dsqlName = dsqlName;
|
|
node->dsqlVar = dsqlVar ? dsqlVar.getObject() : dsqlScratch->resolveVariable(dsqlName);
|
|
|
|
if (!node->dsqlVar)
|
|
PASS1_field_unknown(NULL, dsqlName.c_str(), this);
|
|
|
|
return node;
|
|
}
|
|
|
|
void VariableNode::setParameterName(dsql_par* parameter) const
|
|
{
|
|
parameter->par_name = parameter->par_alias = dsqlVar->field->fld_name.c_str();
|
|
}
|
|
|
|
void VariableNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
bool execBlock = (dsqlScratch->flags & DsqlCompilerScratch::FLAG_BLOCK) &&
|
|
!(dsqlScratch->flags &
|
|
(DsqlCompilerScratch::FLAG_PROCEDURE |
|
|
DsqlCompilerScratch::FLAG_TRIGGER |
|
|
DsqlCompilerScratch::FLAG_FUNCTION));
|
|
|
|
if (dsqlVar->type == dsql_var::TYPE_INPUT && !execBlock)
|
|
{
|
|
dsqlScratch->appendUChar(blr_parameter2);
|
|
dsqlScratch->appendUChar(dsqlVar->msgNumber);
|
|
dsqlScratch->appendUShort(dsqlVar->msgItem);
|
|
dsqlScratch->appendUShort(dsqlVar->msgItem + 1);
|
|
}
|
|
else
|
|
{
|
|
// If this is an EXECUTE BLOCK input parameter, use the internal variable.
|
|
dsqlScratch->appendUChar(blr_variable);
|
|
dsqlScratch->appendUShort(dsqlVar->number);
|
|
}
|
|
}
|
|
|
|
void VariableNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
|
|
{
|
|
*desc = dsqlVar->desc;
|
|
}
|
|
|
|
bool VariableNode::dsqlMatch(const ExprNode* other, bool /*ignoreMapCast*/) const
|
|
{
|
|
const VariableNode* o = other->as<VariableNode>();
|
|
if (!o)
|
|
return false;
|
|
|
|
if (dsqlVar->field != o->dsqlVar->field ||
|
|
dsqlVar->field->fld_name != o->dsqlVar->field->fld_name ||
|
|
dsqlVar->number != o->dsqlVar->number ||
|
|
dsqlVar->msgItem != o->dsqlVar->msgItem ||
|
|
dsqlVar->msgNumber != o->dsqlVar->msgNumber)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VariableNode::getDesc(thread_db* /*tdbb*/, CompilerScratch* /*csb*/, dsc* desc)
|
|
{
|
|
*desc = varDecl->varDesc;
|
|
}
|
|
|
|
ValueExprNode* VariableNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
|
{
|
|
VariableNode* node = FB_NEW(*tdbb->getDefaultPool()) VariableNode(*tdbb->getDefaultPool());
|
|
node->varId = copier.csb->csb_remap_variable + varId;
|
|
node->varDecl = varDecl;
|
|
node->varInfo = varInfo;
|
|
|
|
return node;
|
|
}
|
|
|
|
ValueExprNode* VariableNode::pass1(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
ValueExprNode::pass1(tdbb, csb);
|
|
|
|
vec<DeclareVariableNode*>* vector = csb->csb_variables;
|
|
|
|
if (!vector || varId >= vector->count() || !(varDecl = (*vector)[varId]))
|
|
PAR_error(csb, Arg::Gds(isc_badvarnum));
|
|
|
|
return this;
|
|
}
|
|
|
|
ValueExprNode* VariableNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
|
{
|
|
varInfo = CMP_pass2_validation(tdbb, csb, Item(Item::TYPE_VARIABLE, varId));
|
|
|
|
ValueExprNode::pass2(tdbb, csb);
|
|
|
|
impureOffset = CMP_impure(csb, (nodFlags & FLAG_VALUE) ? sizeof(impure_value_ex) : sizeof(dsc));
|
|
|
|
return this;
|
|
}
|
|
|
|
dsc* VariableNode::execute(thread_db* tdbb, jrd_req* request) const
|
|
{
|
|
impure_value* const impure = request->getImpure<impure_value>(impureOffset);
|
|
impure_value* impure2 = request->getImpure<impure_value>(varDecl->impureOffset);
|
|
|
|
request->req_flags &= ~req_null;
|
|
|
|
if (impure2->vlu_desc.dsc_flags & DSC_null)
|
|
request->req_flags |= req_null;
|
|
|
|
impure->vlu_desc = impure2->vlu_desc;
|
|
|
|
if (impure->vlu_desc.dsc_dtype == dtype_text)
|
|
INTL_adjust_text_descriptor(tdbb, &impure->vlu_desc);
|
|
|
|
if (!(impure2->vlu_flags & VLU_checked))
|
|
{
|
|
if (varInfo)
|
|
{
|
|
EVL_validate(tdbb, Item(Item::TYPE_VARIABLE, varId), varInfo,
|
|
&impure->vlu_desc, (impure->vlu_desc.dsc_flags & DSC_null));
|
|
}
|
|
|
|
impure2->vlu_flags |= VLU_checked;
|
|
}
|
|
|
|
return (request->req_flags & req_null) ? NULL : &impure->vlu_desc;
|
|
}
|
|
|
|
|
|
//--------------------
|
|
|
|
|
|
// Firebird provides transparent conversion from string to date in
|
|
// contexts where it makes sense. This macro checks a descriptor to
|
|
// see if it is something that *could* represent a date value
|
|
|
|
static bool couldBeDate(const dsc desc)
|
|
{
|
|
return DTYPE_IS_DATE(desc.dsc_dtype) || desc.dsc_dtype <= dtype_any_text;
|
|
}
|
|
|
|
// Take the input number, assume it represents a fractional count of days.
|
|
// Convert it to a count of microseconds.
|
|
static SINT64 getDayFraction(const dsc* d)
|
|
{
|
|
dsc result;
|
|
double result_days;
|
|
|
|
result.dsc_dtype = dtype_double;
|
|
result.dsc_scale = 0;
|
|
result.dsc_flags = 0;
|
|
result.dsc_sub_type = 0;
|
|
result.dsc_length = sizeof(double);
|
|
result.dsc_address = reinterpret_cast<UCHAR*>(&result_days);
|
|
|
|
// Convert the input number to a double
|
|
CVT_move(d, &result);
|
|
|
|
// There's likely some loss of precision here due to rounding of number
|
|
|
|
// 08-Apr-2004, Nickolay Samofatov. Loss of precision manifested itself as bad
|
|
// result returned by the following query:
|
|
//
|
|
// select (cast('01.01.2004 10:01:00' as timestamp)
|
|
// -cast('01.01.2004 10:00:00' as timestamp))
|
|
// +cast('01.01.2004 10:00:00' as timestamp) from rdb$database
|
|
//
|
|
// Let's use llrint where it is supported and offset number for other platforms
|
|
// in hope that compiler rounding mode doesn't get in.
|
|
|
|
#ifdef HAVE_LLRINT
|
|
return llrint(result_days * ISC_TICKS_PER_DAY);
|
|
#else
|
|
const double eps = 0.49999999999999;
|
|
if (result_days >= 0)
|
|
return (SINT64)(result_days * ISC_TICKS_PER_DAY + eps);
|
|
|
|
return (SINT64) (result_days * ISC_TICKS_PER_DAY - eps);
|
|
#endif
|
|
}
|
|
|
|
// Take the input value, which is either a timestamp or a string representing a timestamp.
|
|
// Convert it to a timestamp, and then return that timestamp as a count of isc_ticks since the base
|
|
// date and time in MJD time arithmetic.
|
|
// ISC_TICKS or isc_ticks are actually deci - milli seconds or tenthousandth of seconds per day.
|
|
// This is derived from the ISC_TIME_SECONDS_PRECISION.
|
|
static SINT64 getTimeStampToIscTicks(const dsc* d)
|
|
{
|
|
dsc result;
|
|
GDS_TIMESTAMP result_timestamp;
|
|
|
|
result.dsc_dtype = dtype_timestamp;
|
|
result.dsc_scale = 0;
|
|
result.dsc_flags = 0;
|
|
result.dsc_sub_type = 0;
|
|
result.dsc_length = sizeof(GDS_TIMESTAMP);
|
|
result.dsc_address = reinterpret_cast<UCHAR*>(&result_timestamp);
|
|
|
|
CVT_move(d, &result);
|
|
|
|
return ((SINT64) result_timestamp.timestamp_date) * ISC_TICKS_PER_DAY +
|
|
(SINT64) result_timestamp.timestamp_time;
|
|
}
|
|
|
|
// One of d1, d2 is time, the other is date
|
|
static bool isDateAndTime(const dsc& d1, const dsc& d2)
|
|
{
|
|
return ((d1.dsc_dtype == dtype_sql_time && d2.dsc_dtype == dtype_sql_date) ||
|
|
(d2.dsc_dtype == dtype_sql_time && d1.dsc_dtype == dtype_sql_date));
|
|
}
|
|
|
|
// Set parameter info based on a context.
|
|
static void setParameterInfo(dsql_par* parameter, const dsql_ctx* context)
|
|
{
|
|
if (!context)
|
|
return;
|
|
|
|
if (context->ctx_relation)
|
|
{
|
|
parameter->par_rel_name = context->ctx_relation->rel_name.c_str();
|
|
parameter->par_owner_name = context->ctx_relation->rel_owner.c_str();
|
|
}
|
|
else if (context->ctx_procedure)
|
|
{
|
|
parameter->par_rel_name = context->ctx_procedure->prc_name.identifier.c_str();
|
|
parameter->par_owner_name = context->ctx_procedure->prc_owner.c_str();
|
|
}
|
|
|
|
parameter->par_rel_alias = context->ctx_alias.c_str();
|
|
}
|
|
|
|
|
|
} // namespace Jrd
|