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

1) Refactored LOWER, UPPER and TRIM.

2) Fixed CORE-3174 - Expression index with TRIM may lead to incorrect indexed lookup
This commit is contained in:
asfernandes 2010-10-09 18:39:45 +00:00
parent f76961d639
commit ac3c00d503
17 changed files with 610 additions and 458 deletions

View File

@ -22,6 +22,7 @@
#include <math.h>
#include "../jrd/common.h"
#include "../common/classes/FpeControl.h"
#include "../common/classes/VaryStr.h"
#include "../dsql/ExprNodes.h"
#include "../dsql/node.h"
#include "../jrd/align.h"
@ -4309,6 +4310,205 @@ dsc* ParameterNode::execute(thread_db* tdbb, jrd_req* request) const
//--------------------
static RegisterNode<StrCaseNode> regStrCaseNodeLower(blr_lowcase);
static RegisterNode<StrCaseNode> regStrCaseNodeUpper(blr_upcase);
StrCaseNode::StrCaseNode(MemoryPool& pool, UCHAR aBlrOp, dsql_nod* aArg)
: TypedNode<ValueExprNode, ExprNode::TYPE_STR_CASE>(pool),
blrOp(aBlrOp),
dsqlArg(aArg),
arg(NULL)
{
addChildNode(dsqlArg, arg);
}
DmlNode* StrCaseNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, UCHAR blrOp)
{
StrCaseNode* node = FB_NEW(pool) StrCaseNode(pool, blrOp);
node->arg = PAR_parse_node(tdbb, csb, VALUE);
return node;
}
void StrCaseNode::print(string& text, Array<dsql_nod*>& nodes) const
{
text.printf("StrCaseNode (%s)", (blrOp == blr_lowcase ? "lower" : "upper"));
ExprNode::print(text, nodes);
}
ValueExprNode* StrCaseNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
return FB_NEW(getPool()) StrCaseNode(getPool(), blrOp, PASS1_node(dsqlScratch, dsqlArg));
}
void StrCaseNode::setParameterName(dsql_par* parameter) const
{
parameter->par_name = parameter->par_alias = (blrOp == blr_lowcase ? "LOWER" : "UPPER");
}
bool StrCaseNode::setParameterType(DsqlCompilerScratch* dsqlScratch, dsql_nod* thisNode,
dsql_nod* node, bool forceVarChar)
{
return PASS1_set_parameter_type(dsqlScratch, dsqlArg, node, forceVarChar);
}
void StrCaseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->appendUChar(blrOp);
GEN_expr(dsqlScratch, dsqlArg);
}
void StrCaseNode::make(DsqlCompilerScratch* dsqlScratch, dsql_nod* /*thisNode*/, dsc* desc,
dsql_nod* nullReplacement)
{
dsc desc1;
MAKE_desc(dsqlScratch, &desc1, dsqlArg, nullReplacement);
if (desc1.dsc_dtype <= dtype_any_text || desc1.dsc_dtype == dtype_blob)
{
*desc = desc1;
return;
}
desc->dsc_dtype = dtype_varying;
desc->dsc_scale = 0;
desc->dsc_ttype() = ttype_ascii;
desc->dsc_length = sizeof(USHORT) + DSC_string_length(&desc1);
desc->dsc_flags = desc1.dsc_flags & DSC_nullable;
}
void StrCaseNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
{
CMP_get_desc(tdbb, csb, arg, desc);
if (desc->dsc_dtype > dtype_varying && 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)
{
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::expressionEqual(thread_db* tdbb, CompilerScratch* csb, /*const*/ ExprNode* other,
USHORT stream)
{
if (!ExprNode::expressionEqual(tdbb, csb, other, stream))
return false;
StrCaseNode* otherNode = other->as<StrCaseNode>();
fb_assert(otherNode);
return blrOp == otherNode->blrOp;
}
ExprNode* StrCaseNode::pass2(thread_db* tdbb, CompilerScratch* csb)
{
ExprNode::pass2(tdbb, csb);
dsc desc;
getDesc(tdbb, csb, &desc);
node->nod_impure = 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>(node->nod_impure);
request->req_flags &= ~req_null;
const dsc* value = EVL_expr(tdbb, 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 = BLB_get_data(tdbb, blob, buffer.begin(), buffer.getCapacity(), false);
if (len)
{
len = (textType->*intlFunction)(len, buffer.begin(), len, buffer.begin());
BLB_put_data(tdbb, newBlob, buffer.begin(), len);
}
}
BLB_close(tdbb, newBlob);
BLB_close(tdbb, blob);
}
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<SubstringSimilarNode> regSubstringSimilarNode(blr_substring_similar);
SubstringSimilarNode::SubstringSimilarNode(MemoryPool& pool, dsql_nod* aExpr, dsql_nod* aPattern,
@ -4739,6 +4939,315 @@ ValueExprNode* SysFuncCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
//--------------------
static RegisterNode<TrimNode> regTrimNode(blr_trim);
TrimNode::TrimNode(MemoryPool& pool, UCHAR aWhere, dsql_nod* aValue, dsql_nod* aTrimChars)
: TypedNode<ValueExprNode, ExprNode::TYPE_TRIM>(pool),
where(aWhere),
dsqlValue(aValue),
dsqlTrimChars(aTrimChars),
value(NULL),
trimChars(NULL)
{
addChildNode(dsqlValue, value);
addChildNode(dsqlTrimChars, trimChars);
}
DmlNode* TrimNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, 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_node(tdbb, csb, VALUE);
node->value = PAR_parse_node(tdbb, csb, VALUE);
return node;
}
void TrimNode::print(string& text, Array<dsql_nod*>& nodes) const
{
text = "TrimNode";
ExprNode::print(text, nodes);
}
ValueExprNode* TrimNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
TrimNode* node = FB_NEW(getPool()) TrimNode(getPool(), where,
PASS1_node(dsqlScratch, dsqlValue), PASS1_node(dsqlScratch, dsqlTrimChars));
// Try to force trimChars to be same type as value: TRIM(? FROM FIELD)
PASS1_set_parameter_type(dsqlScratch, node->dsqlTrimChars, node->dsqlValue, false);
return node;
}
void TrimNode::setParameterName(dsql_par* parameter) const
{
parameter->par_name = parameter->par_alias = "TRIM";
}
bool TrimNode::setParameterType(DsqlCompilerScratch* dsqlScratch, dsql_nod* thisNode,
dsql_nod* node, bool forceVarChar)
{
return PASS1_set_parameter_type(dsqlScratch, dsqlValue, node, forceVarChar) |
PASS1_set_parameter_type(dsqlScratch, dsqlTrimChars, node, forceVarChar);
}
void TrimNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->appendUChar(blr_trim);
dsqlScratch->appendUChar(where);
if (dsqlTrimChars)
{
dsqlScratch->appendUChar(blr_trim_characters);
GEN_expr(dsqlScratch, dsqlTrimChars);
}
else
dsqlScratch->appendUChar(blr_trim_spaces);
GEN_expr(dsqlScratch, dsqlValue);
}
void TrimNode::make(DsqlCompilerScratch* dsqlScratch, dsql_nod* /*thisNode*/, dsc* desc,
dsql_nod* nullReplacement)
{
dsc desc1, desc2;
MAKE_desc(dsqlScratch, &desc1, dsqlValue, nullReplacement);
if (dsqlTrimChars)
MAKE_desc(dsqlScratch, &desc2, dsqlTrimChars, nullReplacement);
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 = 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 = 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)
{
CMP_get_desc(tdbb, csb, value, desc);
if (trimChars)
{
dsc desc1;
CMP_get_desc(tdbb, csb, trimChars, &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)
{
TrimNode* node = FB_NEW(*tdbb->getDefaultPool()) TrimNode(*tdbb->getDefaultPool(), where);
node->value = copier.copy(tdbb, value);
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::expressionEqual(thread_db* tdbb, CompilerScratch* csb, /*const*/ ExprNode* other,
USHORT stream)
{
if (!ExprNode::expressionEqual(tdbb, csb, other, stream))
return false;
TrimNode* o = other->as<TrimNode>();
fb_assert(o);
return where == o->where;
}
ExprNode* TrimNode::pass2(thread_db* tdbb, CompilerScratch* csb)
{
ExprNode::pass2(tdbb, csb);
dsc desc;
getDesc(tdbb, csb, &desc);
node->nod_impure = 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>(node->nod_impure);
request->req_flags &= ~req_null;
dsc* trimCharsDesc = (trimChars ? EVL_expr(tdbb, trimChars) : NULL);
if (request->req_flags & req_null)
return NULL;
dsc* valueDesc = EVL_expr(tdbb, 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 = 0;
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();
HalfStaticArray<UCHAR, BUFFER_SMALL> blobBuffer;
MoveBuffer valueBuffer;
UCHAR* valueAddress;
ULONG valueLength;
if (valueDesc->isBlob())
{
// Source string is a blob, things get interesting.
blb* blob = BLB_open(tdbb, tdbb->getRequest()->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 = blobBuffer.getBuffer(blob->blb_length);
valueLength = BLB_get_data(tdbb, blob, 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);
BLB_put_data(tdbb, newBlob, valueCanonical.begin(), len);
BLB_close(tdbb, newBlob);
}
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);

View File

@ -449,6 +449,37 @@ public:
};
class StrCaseNode : public TypedNode<ValueExprNode, ExprNode::TYPE_STR_CASE>
{
public:
StrCaseNode(MemoryPool& pool, UCHAR aBlrOp, dsql_nod* aArg = NULL);
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, UCHAR blrOp);
virtual void print(Firebird::string& text, Firebird::Array<dsql_nod*>& nodes) const;
virtual ValueExprNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void setParameterName(dsql_par* parameter) const;
virtual bool setParameterType(DsqlCompilerScratch* dsqlScratch, dsql_nod* thisNode,
dsql_nod* node, bool forceVarChar);
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual void make(DsqlCompilerScratch* dsqlScratch, dsql_nod* thisNode, dsc* desc,
dsql_nod* nullReplacement);
virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc);
virtual ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier);
virtual bool dsqlMatch(const ExprNode* other, bool ignoreMapCast) const;
virtual bool expressionEqual(thread_db* tdbb, CompilerScratch* csb, /*const*/ ExprNode* other,
USHORT stream) /*const*/;
virtual ExprNode* pass2(thread_db* tdbb, CompilerScratch* csb);
virtual dsc* execute(thread_db* tdbb, jrd_req* request) const;
public:
UCHAR blrOp;
dsql_nod* dsqlArg;
NestConst<jrd_nod> arg;
};
class SubstringSimilarNode : public TypedNode<ValueExprNode, ExprNode::TYPE_SUBSTRING_SIMILAR>
{
public:
@ -514,6 +545,40 @@ public:
};
class TrimNode : public TypedNode<ValueExprNode, ExprNode::TYPE_TRIM>
{
public:
explicit TrimNode(MemoryPool& pool, UCHAR aWhere,
dsql_nod* aValue = NULL, dsql_nod* aTrimChars = NULL);
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, UCHAR blrOp);
virtual void print(Firebird::string& text, Firebird::Array<dsql_nod*>& nodes) const;
virtual ValueExprNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void setParameterName(dsql_par* parameter) const;
virtual bool setParameterType(DsqlCompilerScratch* dsqlScratch, dsql_nod* thisNode,
dsql_nod* node, bool forceVarChar);
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual void make(DsqlCompilerScratch* dsqlScratch, dsql_nod* thisNode, dsc* desc,
dsql_nod* nullReplacement);
virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc);
virtual ValueExprNode* copy(thread_db* tdbb, NodeCopier& copier);
virtual bool dsqlMatch(const ExprNode* other, bool ignoreMapCast) const;
virtual bool expressionEqual(thread_db* tdbb, CompilerScratch* csb, /*const*/ ExprNode* other,
USHORT stream) /*const*/;
virtual ExprNode* pass2(thread_db* tdbb, CompilerScratch* csb);
virtual dsc* execute(thread_db* tdbb, jrd_req* request) const;
public:
UCHAR where;
dsql_nod* dsqlValue;
dsql_nod* dsqlTrimChars; // may be NULL
NestConst<jrd_nod> value;
NestConst<jrd_nod> trimChars; // may be NULL
};
class UdfCallNode : public TypedNode<ValueExprNode, ExprNode::TYPE_UDF_CALL>
{
public:

View File

@ -272,8 +272,10 @@ public:
TYPE_OVER,
TYPE_PARAMETER,
TYPE_RSE_BOOL,
TYPE_STR_CASE,
TYPE_SUBSTRING_SIMILAR,
TYPE_SYSFUNC_CALL,
TYPE_TRIM,
TYPE_UDF_CALL,
TYPE_VALUE_IF
};

View File

@ -520,9 +520,6 @@ inline bool DsqlNodeVisitor<T, T2>::visitChildren(T node)
break;
case nod_substr:
case nod_trim:
case nod_upcase:
case nod_lowcase:
case nod_extract:
case nod_strlen:
case nod_simple_case:

View File

@ -311,12 +311,6 @@ void GEN_expr(DsqlCompilerScratch* dsqlScratch, dsql_nod* node)
blr_operator = blr_via;
break;
case nod_upcase:
blr_operator = blr_upcase;
break;
case nod_lowcase:
blr_operator = blr_lowcase;
break;
case nod_substr:
blr_operator = blr_substring;
break;
@ -333,21 +327,6 @@ void GEN_expr(DsqlCompilerScratch* dsqlScratch, dsql_nod* node)
gen_searched_case(dsqlScratch, node);
return;
case nod_trim:
dsqlScratch->appendUChar(blr_trim);
dsqlScratch->appendUChar(node->nod_arg[e_trim_specification]->getSlong());
if (node->nod_arg[e_trim_characters])
{
dsqlScratch->appendUChar(blr_trim_characters);
GEN_expr(dsqlScratch, node->nod_arg[e_trim_characters]);
}
else
dsqlScratch->appendUChar(blr_trim_spaces);
GEN_expr(dsqlScratch, node->nod_arg[e_trim_value]);
return;
case nod_assign:
dsqlScratch->appendUChar(blr_assignment);
GEN_expr(dsqlScratch, node->nod_arg[0]);

View File

@ -426,22 +426,6 @@ void MAKE_desc(DsqlCompilerScratch* dsqlScratch, dsc* desc, dsql_nod* node, dsql
MAKE_desc(dsqlScratch, desc, node->nod_arg[e_derived_field_value], null_replacement);
return;
case nod_upcase:
case nod_lowcase:
MAKE_desc(dsqlScratch, &desc1, node->nod_arg[0], null_replacement);
if (desc1.dsc_dtype <= dtype_any_text || desc1.dsc_dtype == dtype_blob)
{
*desc = desc1;
return;
}
desc->dsc_dtype = dtype_varying;
desc->dsc_scale = 0;
desc->dsc_ttype() = ttype_ascii;
desc->dsc_length = sizeof(USHORT) + DSC_string_length(&desc1);
desc->dsc_flags = desc1.dsc_flags & DSC_nullable;
return;
case nod_substr:
MAKE_desc(dsqlScratch, &desc1, node->nod_arg[0], null_replacement);
MAKE_desc(dsqlScratch, &desc2, node->nod_arg[1], null_replacement);
@ -449,36 +433,6 @@ void MAKE_desc(DsqlCompilerScratch* dsqlScratch, dsc* desc, dsql_nod* node, dsql
DSqlDataTypeUtil(dsqlScratch).makeSubstr(desc, &desc1, &desc2, &desc3);
return;
case nod_trim:
MAKE_desc(dsqlScratch, &desc1, node->nod_arg[e_trim_value], null_replacement);
if (node->nod_arg[e_trim_characters])
MAKE_desc(dsqlScratch, &desc2, node->nod_arg[e_trim_characters], null_replacement);
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 = 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 = sizeof(USHORT) + DSC_string_length(&desc1);
desc->dsc_flags = (desc1.dsc_flags | desc2.dsc_flags) & DSC_nullable;
}
return;
case nod_cast:
field = (dsql_fld*) node->nod_arg[e_cast_target];
MAKE_desc_from_field(desc, field);
@ -1226,18 +1180,9 @@ void MAKE_parameter_names(dsql_par* parameter, const dsql_nod* item)
case nod_substr:
name_alias = "SUBSTRING";
break;
case nod_trim:
name_alias = "TRIM";
break;
case nod_cast:
name_alias = "CAST";
break;
case nod_upcase:
name_alias = "UPPER";
break;
case nod_lowcase:
name_alias = "LOWER";
break;
case nod_extract:
name_alias = "EXTRACT";
break;

View File

@ -138,8 +138,6 @@ enum nod_t
nod_null,
nod_dbkey,
nod_cast,
nod_upcase,
nod_lowcase,
nod_collate,
nod_get_segment, // blobs
nod_put_segment,
@ -212,7 +210,6 @@ enum nod_t
nod_query_spec,
nod_mod_udf,
nod_strlen,
nod_trim,
nod_returning,
nod_tra_misc,
nod_lock_timeout,

View File

@ -799,7 +799,6 @@ inline void check_copy_incr(char*& to, const char ch, const char* const string)
%type <legacyNode> table_proc table_proc_inputs table_reference table_subquery tbl_reserve_options
%type <legacyNode> timestamp_part top tra_misc_options tra_timeout tran_opt tran_opt_list tran_opt_list_m
%type <legacyNode> trim_function
%type <legacyNode> trim_specification
%type <uintVal> time_precision_opt timestamp_precision_opt
%type <legacyNode> u_constant u_numeric_constant udf udf_data_type udf_decl_clause undo_savepoint
@ -869,6 +868,8 @@ inline void check_copy_incr(char*& to, const char ch, const char* const string)
%type <sysFuncCallNode> system_function_special_syntax
%type <blrOp> trim_specification
// Predicates
%type <boolExprNode> between_predicate comparison_predicate distinct_predicate
%type <boolExprNode> exists_predicate in_predicate binary_pattern_predicate ternary_pattern_predicate null_predicate predicate
@ -5683,12 +5684,13 @@ system_function_special_syntax
{ $$ = FB_NEW(getPool()) SysFuncCallNode(getPool(), toName($1), $3); }
;
string_value_function : substring_function
string_value_function
: substring_function
| trim_function
| KW_UPPER '(' value ')'
{ $$ = make_node (nod_upcase, 1, $3); }
{ $$ = makeClassNode(FB_NEW(getPool()) StrCaseNode(getPool(), blr_upcase, $3)); }
| KW_LOWER '(' value ')'
{ $$ = make_node (nod_lowcase, 1, $3); }
{ $$ = makeClassNode(FB_NEW(getPool()) StrCaseNode(getPool(), blr_lowcase, $3)); }
;
substring_function
@ -5706,30 +5708,26 @@ substring_function
{ $$ = makeClassNode(FB_NEW(getPool()) SubstringSimilarNode(getPool(), $3, $5, $7)); }
;
string_length_opt : FOR value
{ $$ = $2; }
|
{ $$ = MAKE_const_slong (SHRT_POS_MAX); }
string_length_opt
: { $$ = MAKE_const_slong(SHRT_POS_MAX); }
| FOR value { $$ = $2; }
;
trim_function : TRIM '(' trim_specification value FROM value ')'
{ $$ = make_node (nod_trim, (int) e_trim_count, $3, $4, $6); }
trim_function
: TRIM '(' trim_specification value FROM value ')'
{ $$ = makeClassNode(FB_NEW(getPool()) TrimNode(getPool(), $3, $6, $4)); }
| TRIM '(' value FROM value ')'
{ $$ = make_node (nod_trim, (int) e_trim_count,
MAKE_const_slong (blr_trim_both), $3, $5); }
{ $$ = makeClassNode(FB_NEW(getPool()) TrimNode(getPool(), blr_trim_both, $5, $3)); }
| TRIM '(' trim_specification FROM value ')'
{ $$ = make_node (nod_trim, (int) e_trim_count, $3, NULL, $5); }
{ $$ = makeClassNode(FB_NEW(getPool()) TrimNode(getPool(), $3, $5, NULL)); }
| TRIM '(' value ')'
{ $$ = make_node (nod_trim, (int) e_trim_count,
MAKE_const_slong (blr_trim_both), NULL, $3); }
{ $$ = makeClassNode(FB_NEW(getPool()) TrimNode(getPool(), blr_trim_both, $3, NULL)); }
;
trim_specification : BOTH
{ $$ = MAKE_const_slong (blr_trim_both); }
| TRAILING
{ $$ = MAKE_const_slong (blr_trim_trailing); }
| LEADING
{ $$ = MAKE_const_slong (blr_trim_leading); }
trim_specification
: BOTH { $$ = blr_trim_both; }
| TRAILING { $$ = blr_trim_trailing; }
| LEADING { $$ = blr_trim_leading; }
;
udf

View File

@ -1575,14 +1575,6 @@ dsql_nod* PASS1_node(DsqlCompilerScratch* dsqlScratch, dsql_nod* input)
break;
case nod_trim:
sub1 = node->nod_arg[e_trim_characters];
sub2 = node->nod_arg[e_trim_value];
// Try to force sub1 to be same type as sub2 eg: TRIM(? FROM FIELD) case
PASS1_set_parameter_type(dsqlScratch, sub1, sub2, false);
break;
default:
break;
}
@ -8532,9 +8524,6 @@ bool PASS1_set_parameter_type(DsqlCompilerScratch* dsqlScratch, dsql_nod* in_nod
}
case nod_substr:
case nod_trim:
case nod_upcase:
case nod_lowcase:
case nod_extract:
case nod_limit:
case nod_rows:
@ -8636,7 +8625,9 @@ static void set_parameter_name( dsql_nod* par_node, const dsql_nod* fld_node, co
case ExprNode::TYPE_ARITHMETIC:
case ExprNode::TYPE_CONCATENATE:
case ExprNode::TYPE_NEGATE:
case ExprNode::TYPE_STR_CASE:
case ExprNode::TYPE_SUBSTRING_SIMILAR:
case ExprNode::TYPE_TRIM:
for (dsql_nod*** i = exprNode->dsqlChildNodes.begin();
i != exprNode->dsqlChildNodes.end(); ++i)
{
@ -8659,9 +8650,6 @@ static void set_parameter_name( dsql_nod* par_node, const dsql_nod* fld_node, co
return;
case nod_substr:
case nod_trim:
case nod_upcase:
case nod_lowcase:
case nod_extract:
case nod_strlen:
case nod_limit:
@ -8993,9 +8981,6 @@ void DSQL_pretty(const dsql_nod* node, int column)
case nod_substr:
verb = "substr";
break;
case nod_trim:
verb = "trim";
break;
case nod_update:
verb = "update";
break;
@ -9005,12 +8990,6 @@ void DSQL_pretty(const dsql_nod* node, int column)
case nod_unique:
verb = "unique";
break;
case nod_upcase:
verb = "upcase";
break;
case nod_lowcase:
verb = "lowcase";
break;
case nod_via:
verb = "via";
break;

View File

@ -327,8 +327,6 @@ bool OPT_expression_equal2(thread_db* tdbb, CompilerScratch* csb,
return true;
case nod_substr:
case nod_trim:
{
if (node1->nod_count != node2->nod_count)
return false;
@ -339,11 +337,6 @@ bool OPT_expression_equal2(thread_db* tdbb, CompilerScratch* csb,
}
return true;
}
case nod_upcase:
case nod_lowcase:
return OPT_expression_equal2(tdbb, csb, node1->nod_arg[0], node2->nod_arg[0], stream);
case nod_cast:
{

View File

@ -571,19 +571,6 @@ void CMP_get_desc(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node, DSC* des
return;
}
case nod_upcase:
case nod_lowcase:
CMP_get_desc(tdbb, csb, node->nod_arg[0], desc);
if (desc->dsc_dtype > dtype_varying && 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;
}
return;
case nod_dbkey:
desc->dsc_dtype = dtype_dbkey;
desc->dsc_length = type_lengths[dtype_dbkey];
@ -741,32 +728,6 @@ void CMP_get_desc(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node, DSC* des
return;
}
case nod_trim:
CMP_get_desc(tdbb, csb, node->nod_arg[e_trim_value], desc);
if (node->nod_arg[e_trim_characters])
{
DSC desc1;
CMP_get_desc(tdbb, csb, node->nod_arg[e_trim_characters], &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);
}
return;
case nod_variable:
{
const jrd_nod* value = node->nod_arg[e_var_variable];
@ -1357,15 +1318,6 @@ jrd_nod* NodeCopier::copy(thread_db* tdbb, jrd_nod* input)
node->nod_arg[e_strlen_type] = input->nod_arg[e_strlen_type];
return (node);
case nod_trim:
node = PAR_make_node(tdbb, e_trim_length);
node->nod_count = input->nod_count;
node->nod_type = input->nod_type;
node->nod_arg[e_trim_characters] = copy(tdbb, input->nod_arg[e_trim_characters]);
node->nod_arg[e_trim_value] = copy(tdbb, input->nod_arg[e_trim_value]);
node->nod_arg[e_trim_specification] = input->nod_arg[e_trim_specification];
return (node);
case nod_count:
case nod_max:
case nod_min:
@ -3209,10 +3161,7 @@ jrd_nod* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, jrd_nod* const node, j
case nod_dbkey:
case nod_rec_version:
case nod_substr:
case nod_trim:
case nod_null:
case nod_upcase:
case nod_lowcase:
case nod_prot_mask:
case nod_lock_state:
case nod_scalar:

View File

@ -124,13 +124,10 @@ static dsc* eval_statistical(thread_db*, const jrd_nod*, impure_value*);
static dsc* extract(thread_db*, const jrd_nod*, impure_value*);
static dsc* get_mask(thread_db*, const jrd_nod*, impure_value*);
static dsc* lock_state(thread_db*, const jrd_nod*, impure_value*);
static dsc* low_up_case(thread_db*, const dsc*, impure_value*,
ULONG (TextType::*tt_str_to_case)(ULONG, const UCHAR*, ULONG, UCHAR*));
static dsc* record_version(thread_db*, const jrd_nod*, impure_value*);
static dsc* scalar(thread_db*, const jrd_nod*, impure_value*);
static dsc* string_length(thread_db*, const jrd_nod*, impure_value*);
static dsc* substring(thread_db*, impure_value*, dsc*, const dsc*, const dsc*);
static dsc* trim(thread_db*, const jrd_nod*, impure_value*);
dsc* EVL_assign_to(thread_db* tdbb, const jrd_nod* node)
@ -536,9 +533,6 @@ dsc* EVL_expr(thread_db* tdbb, const jrd_nod* node)
}
return request->req_domain_validation;
case nod_trim:
return trim(tdbb, node, impure);
default: // Shut up some compiler warnings
break;
@ -576,12 +570,6 @@ dsc* EVL_expr(thread_db* tdbb, const jrd_nod* node)
case nod_substr:
return substring(tdbb, impure, values[0], values[1], values[2]);
case nod_upcase:
return low_up_case(tdbb, values[0], impure, &TextType::str_to_upper);
case nod_lowcase:
return low_up_case(tdbb, values[0], impure, &TextType::str_to_lower);
case nod_cast:
return cast(tdbb, values[0], node, impure);
@ -1541,83 +1529,6 @@ static dsc* lock_state(thread_db* tdbb, const jrd_nod* node, impure_value* impur
}
static dsc* low_up_case(thread_db* tdbb, const dsc* value, impure_value* impure,
ULONG (TextType::*tt_str_to_case)(ULONG, const UCHAR*, ULONG, UCHAR*))
{
/**************************************
*
* l o w _ u p _ c a s e
*
**************************************
*
* Functional description
* Low/up case a string.
*
**************************************/
SET_TDBB(tdbb);
TextType* textType = INTL_texttype_lookup(tdbb, value->getTextType());
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));
Firebird::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 = BLB_get_data(tdbb, blob, buffer.begin(), buffer.getCapacity(), false);
if (len)
{
len = (textType->*tt_str_to_case)(len, buffer.begin(), len, buffer.begin());
BLB_put_data(tdbb, newBlob, buffer.begin(), len);
}
}
BLB_close(tdbb, newBlob);
BLB_close(tdbb, blob);
}
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->*tt_str_to_case)(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 dsc* record_version(thread_db* tdbb, const jrd_nod* node, impure_value* impure)
{
/**************************************
@ -1869,142 +1780,3 @@ static dsc* substring(thread_db* tdbb, impure_value* impure,
**************************************/
return SysFunction::substring(tdbb, impure, value, offset_value, length_value);
}
static dsc* trim(thread_db* tdbb, const jrd_nod* node, impure_value* impure)
{
/**************************************
*
* t r i m
*
**************************************
*
* Functional description
* Perform trim function = TRIM([where what FROM] string)
*
**************************************/
SET_TDBB(tdbb);
jrd_req* request = tdbb->getRequest();
const ULONG specification = (IPTR) node->nod_arg[e_trim_specification];
request->req_flags &= ~req_null;
dsc* characters = (node->nod_arg[e_trim_characters] ? EVL_expr(tdbb, node->nod_arg[e_trim_characters]) : NULL);
if (request->req_flags & req_null)
return characters;
request->req_flags &= ~req_null;
dsc* value = EVL_expr(tdbb, node->nod_arg[e_trim_value]);
if (request->req_flags & req_null)
return value;
USHORT ttype = INTL_TEXT_TYPE(*value);
TextType* tt = INTL_texttype_lookup(tdbb, ttype);
CharSet* cs = tt->getCharSet();
const UCHAR* charactersAddress;
MoveBuffer charactersBuffer;
USHORT charactersLength;
if (characters)
{
UCHAR* tempAddress = 0;
charactersLength = MOV_make_string2(tdbb, characters, ttype, &tempAddress, charactersBuffer);
charactersAddress = tempAddress;
}
else
{
charactersLength = tt->getCharSet()->getSpaceLength();
charactersAddress = tt->getCharSet()->getSpace();
}
Firebird::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();
Firebird::HalfStaticArray<UCHAR, BUFFER_SMALL> blobBuffer;
MoveBuffer valueBuffer;
UCHAR* valueAddress;
ULONG valueLength;
if (value->isBlob())
{
// Source string is a blob, things get interesting.
blb* blob = BLB_open(tdbb, tdbb->getRequest()->req_transaction,
reinterpret_cast<bid*>(value->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 = blobBuffer.getBuffer(blob->blb_length);
valueLength = BLB_get_data(tdbb, blob, valueAddress, blob->blb_length, true);
}
else
valueLength = MOV_make_string2(tdbb, value, ttype, &valueAddress, valueBuffer);
Firebird::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 (specification == blr_trim_both || specification == 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 (specification == blr_trim_both || specification == blr_trim_trailing)
{
for (; offsetTrail - charactersCanonicalLen >= offsetLead; offsetTrail -= charactersCanonicalLen)
{
if (memcmp(charactersCanonical.begin(), &valueCanonical[offsetTrail - charactersCanonicalLen],
charactersCanonicalLen) != 0)
{
break;
}
}
}
}
if (value->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, value, impure);
blb* newBlob = BLB_create(tdbb, tdbb->getRequest()->req_transaction, &impure->vlu_misc.vlu_bid);
BLB_put_data(tdbb, newBlob, valueCanonical.begin(), len);
BLB_close(tdbb, newBlob);
}
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;
}

View File

@ -326,12 +326,6 @@ const int e_strlen_type = 1;
const int e_strlen_count = 1;
const int e_strlen_length = 2;
const int e_trim_value = 0;
const int e_trim_characters = 1;
const int e_trim_specification = 2;
const int e_trim_count = 2;
const int e_trim_length = 3;
// nod_src_info
const int e_src_info_line = 0;
const int e_src_info_col = 1;

View File

@ -64,15 +64,12 @@ NODE(nod_from, from, "")
NODE(nod_literal, literal, "")
NODE(nod_scalar, scalar, "")
NODE(nod_prot_mask, prot_mask, "")
NODE(nod_upcase, upcase, "UPPER")
NODE(nod_lock_state, lock_state, "")
NODE(nod_null, null, "NULL")
NODE(nod_substr, substr, "")
NODE(nod_trim, trim, "")
NODE(nod_cast, cast, "CAST")
NODE(nod_rec_version, record_version, "RECORD VERSION")
NODE(nod_extract, extract, "EXTRACT")
NODE(nod_lowcase, lowcase, "LOWER")
NODE(nod_strlen, strlen, "STRLEN")
NODE(nod_domain_validation, domain_validation, "")
NODE(nod_derived_expr, derived_expr, "derived_expr")

View File

@ -135,10 +135,7 @@ bool JrdNodeVisitor::visitChildren(const JrdNode& node)
ret |= visit(jrdNode->nod_arg[e_strlen_value]);
break;
case nod_upcase:
case nod_lowcase:
case nod_substr:
case nod_trim:
case nod_derived_expr:
{
jrd_nod* /*const*/* ptr = jrdNode->nod_arg;

View File

@ -2284,25 +2284,6 @@ jrd_nod* PAR_parse_node(thread_db* tdbb, CompilerScratch* csb, USHORT expected)
*arg++ = PAR_parse_node(tdbb, csb, sub_type);
break;
case blr_trim:
{
node->nod_count = e_trim_count;
node->nod_arg[e_trim_specification] = (jrd_nod*)(U_IPTR) csb->csb_blr_reader.getByte();
BYTE trimWhat = csb->csb_blr_reader.getByte();
if (trimWhat == blr_trim_characters)
node->nod_arg[e_trim_characters] = PAR_parse_node(tdbb, csb, sub_type);
else
{
node->nod_arg[e_trim_characters] = NULL;
--node->nod_count;
}
node->nod_arg[e_trim_value] = PAR_parse_node(tdbb, csb, sub_type);
break;
}
case blr_prot_mask:
case blr_assignment:
*arg++ = PAR_parse_node(tdbb, csb, sub_type);
@ -2311,8 +2292,6 @@ jrd_nod* PAR_parse_node(thread_db* tdbb, CompilerScratch* csb, USHORT expected)
case blr_handler:
case blr_loop:
case blr_lock_state:
case blr_upcase:
case blr_lowcase:
*arg++ = PAR_parse_node(tdbb, csb, sub_type);
break;

View File

@ -137,7 +137,7 @@ static const VERB verbs[] =
PAIR(nod_class_exprnode_jrd, blr_negate, 1, 0, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_gen_id, 1, 0, VALUE, VALUE),
PAIR(nod_prot_mask, blr_prot_mask, e_pro_length, 2, VALUE, VALUE),
PAIR(nod_upcase, blr_upcase, 1, 1, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_upcase, 1, 0, VALUE, VALUE),
PAIR(nod_lock_state, blr_lock_state, 1, 1, VALUE, VALUE),
PAIR(nod_substr, blr_substring, 3, 3, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_subtract, 1, 0, VALUE, VALUE),
@ -208,9 +208,9 @@ static const VERB verbs[] =
PAIR(nod_class_exprnode_jrd, blr_current_role, 1, 0, VALUE, VALUE),
PAIR(nod_dcl_cursor, blr_dcl_cursor, e_dcl_cur_length, 2, STATEMENT, OTHER),
PAIR(nod_cursor_stmt, blr_cursor_stmt, e_cursor_stmt_length, 0, STATEMENT, OTHER),
PAIR(nod_lowcase, blr_lowcase, 1, 1, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_lowcase, 1, 0, VALUE, VALUE),
PAIR(nod_strlen, blr_strlen, e_strlen_length, e_strlen_count, VALUE, VALUE),
PAIR(nod_trim, blr_trim, e_trim_length, e_trim_count, VALUE, VALUE),
PAIR(nod_class_exprnode_jrd, blr_trim, 1, 0, VALUE, VALUE),
PAIR(nod_init_variable, blr_init_variable, e_init_var_length, 0, STATEMENT, OTHER),
PAIR(nod_class_exprnode_jrd, blr_sys_function, 1, 0, VALUE, VALUE),
PAIR(nod_class_stmtnode_jrd, blr_auto_trans, 1, 0, STATEMENT, STATEMENT),