mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 17:23:03 +01:00
Add multi-character TRIM function (#8015)
* Add multi-character TRIM function * Follow Adriano's suggestion Co-authored-by: Adriano dos Santos Fernandes <529415+asfernandes@users.noreply.github.com> --------- Co-authored-by: Dmitry Yemanov <dyemanov@users.noreply.github.com> Co-authored-by: Adriano dos Santos Fernandes <529415+asfernandes@users.noreply.github.com>
This commit is contained in:
parent
1844491fd9
commit
56e8c6224c
@ -18,11 +18,16 @@ Format:
|
||||
<trim character> ::=
|
||||
<value expression>
|
||||
|
||||
<multi-character trim function> ::=
|
||||
{ BTRIM | LTRIM | RTRIM } <left paren> <value expression> [ <comma> <trim character> ] <right paren>
|
||||
|
||||
Syntax Rules:
|
||||
1) If <trim specification> is not specified, BOTH is assumed.
|
||||
2) If <trim character> is not specified, ' ' is assumed.
|
||||
3) If <trim specification> and/or <trim character> is specified, FROM should be specified.
|
||||
4) If <trim specification> and <trim character> is not specified, FROM should not be specified.
|
||||
5) multi-character trim function accepts a sequence of characters as the second argument and will remove all
|
||||
leading, trailing, or both occurrences of any of these characters, regardless of their ordering.
|
||||
|
||||
Examples:
|
||||
A)
|
||||
@ -36,3 +41,8 @@ B)
|
||||
trim(rdb$relation_name) || ' is a system table'
|
||||
from rdb$relations
|
||||
where rdb$system_flag = 1;
|
||||
|
||||
C)
|
||||
select
|
||||
ltrim('baobab is a tree', 'aboe')
|
||||
from rdb$database;
|
||||
|
@ -507,6 +507,9 @@ PARSER_TOKEN(TOK_TRANSACTION, "TRANSACTION", true)
|
||||
PARSER_TOKEN(TOK_TRAPS, "TRAPS", true)
|
||||
PARSER_TOKEN(TOK_TRIGGER, "TRIGGER", false)
|
||||
PARSER_TOKEN(TOK_TRIM, "TRIM", false)
|
||||
PARSER_TOKEN(TOK_BTRIM, "BTRIM", false)
|
||||
PARSER_TOKEN(TOK_LTRIM, "LTRIM", false)
|
||||
PARSER_TOKEN(TOK_RTRIM, "RTRIM", false)
|
||||
PARSER_TOKEN(TOK_TRUE, "TRUE", false)
|
||||
PARSER_TOKEN(TOK_TRUNC, "TRUNC", true)
|
||||
PARSER_TOKEN(TOK_TRUSTED, "TRUSTED", true)
|
||||
|
@ -12531,9 +12531,10 @@ ValueExprNode* SysFuncCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
|
||||
static RegisterNode<TrimNode> regTrimNode({blr_trim});
|
||||
|
||||
TrimNode::TrimNode(MemoryPool& pool, UCHAR aWhere, ValueExprNode* aValue, ValueExprNode* aTrimChars)
|
||||
TrimNode::TrimNode(MemoryPool& pool, UCHAR aWhere, UCHAR aWhat, ValueExprNode* aValue, ValueExprNode* aTrimChars)
|
||||
: TypedNode<ValueExprNode, ExprNode::TYPE_TRIM>(pool),
|
||||
where(aWhere),
|
||||
what(aWhat),
|
||||
value(aValue),
|
||||
trimChars(aTrimChars)
|
||||
{
|
||||
@ -12544,9 +12545,9 @@ DmlNode* TrimNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb
|
||||
UCHAR where = csb->csb_blr_reader.getByte();
|
||||
UCHAR what = csb->csb_blr_reader.getByte();
|
||||
|
||||
TrimNode* node = FB_NEW_POOL(pool) TrimNode(pool, where);
|
||||
TrimNode* node = FB_NEW_POOL(pool) TrimNode(pool, where, what);
|
||||
|
||||
if (what == blr_trim_characters)
|
||||
if (what == blr_trim_characters || what == blr_trim_multi_characters)
|
||||
node->trimChars = PAR_parse_value(tdbb, csb);
|
||||
|
||||
node->value = PAR_parse_value(tdbb, csb);
|
||||
@ -12567,7 +12568,7 @@ string TrimNode::internalPrint(NodePrinter& printer) const
|
||||
|
||||
ValueExprNode* TrimNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
{
|
||||
TrimNode* node = FB_NEW_POOL(dsqlScratch->getPool()) TrimNode(dsqlScratch->getPool(), where,
|
||||
TrimNode* node = FB_NEW_POOL(dsqlScratch->getPool()) TrimNode(dsqlScratch->getPool(), where, what,
|
||||
doDsqlPass(dsqlScratch, value), doDsqlPass(dsqlScratch, trimChars));
|
||||
|
||||
// Try to force trimChars to be same type as value: TRIM(? FROM FIELD)
|
||||
@ -12595,7 +12596,7 @@ void TrimNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
||||
|
||||
if (trimChars)
|
||||
{
|
||||
dsqlScratch->appendUChar(blr_trim_characters);
|
||||
dsqlScratch->appendUChar(what);
|
||||
GEN_expr(dsqlScratch, trimChars);
|
||||
}
|
||||
else
|
||||
@ -12665,7 +12666,7 @@ void TrimNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
||||
|
||||
ValueExprNode* TrimNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
||||
{
|
||||
TrimNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) TrimNode(*tdbb->getDefaultPool(), where);
|
||||
TrimNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) TrimNode(*tdbb->getDefaultPool(), where, what);
|
||||
node->value = copier.copy(tdbb, value);
|
||||
if (trimChars)
|
||||
node->trimChars = copier.copy(tdbb, trimChars);
|
||||
@ -12800,34 +12801,87 @@ dsc* TrimNode::execute(thread_db* tdbb, Request* request) const
|
||||
|
||||
SLONG offsetLead = 0;
|
||||
SLONG offsetTrail = valueCanonicalLen;
|
||||
const bool multi = what == blr_trim_multi_characters;
|
||||
|
||||
// CVC: Avoid endless loop with zero length trim chars.
|
||||
if (charactersCanonicalLen)
|
||||
{
|
||||
if (where == blr_trim_both || where == blr_trim_leading)
|
||||
int charSize = charactersCanonical.getCount() / charactersLength;
|
||||
if (!multi)
|
||||
{
|
||||
// CVC: Prevent surprises with offsetLead < valueCanonicalLen; it may fail.
|
||||
for (; offsetLead + charactersCanonicalLen <= valueCanonicalLen;
|
||||
offsetLead += charactersCanonicalLen)
|
||||
if (where == blr_trim_both || where == blr_trim_leading)
|
||||
{
|
||||
if (memcmp(charactersCanonical.begin(), &valueCanonical[offsetLead],
|
||||
charactersCanonicalLen) != 0)
|
||||
// CVC: Prevent surprises with offsetLead < valueCanonicalLen; it may fail.
|
||||
for (; offsetLead + charactersCanonicalLen <= valueCanonicalLen;
|
||||
offsetLead += charactersCanonicalLen)
|
||||
{
|
||||
break;
|
||||
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 (where == blr_trim_both || where == blr_trim_trailing)
|
||||
else
|
||||
{
|
||||
for (; offsetTrail - charactersCanonicalLen >= offsetLead;
|
||||
offsetTrail -= charactersCanonicalLen)
|
||||
if (where == blr_trim_both || where == blr_trim_leading)
|
||||
{
|
||||
if (memcmp(charactersCanonical.begin(),
|
||||
&valueCanonical[offsetTrail - charactersCanonicalLen],
|
||||
charactersCanonicalLen) != 0)
|
||||
while (offsetLead < valueCanonicalLen)
|
||||
{
|
||||
break;
|
||||
bool found = false;
|
||||
for (int i = 0; i < charactersCanonicalLen; i += charSize)
|
||||
{
|
||||
if (memcmp(&charactersCanonical[i],
|
||||
&valueCanonical[offsetLead],
|
||||
charSize) == 0)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
offsetLead += charSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (where == blr_trim_both || where == blr_trim_trailing)
|
||||
{
|
||||
while (offsetTrail - charSize >= offsetLead)
|
||||
{
|
||||
bool found = false;
|
||||
for (int i = 0; i < charactersCanonicalLen; i += charSize)
|
||||
{
|
||||
if (memcmp(&charactersCanonical[i],
|
||||
&valueCanonical[offsetTrail - charSize],
|
||||
charSize) == 0)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
offsetTrail -= charSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2107,7 +2107,7 @@ public:
|
||||
class TrimNode final : public TypedNode<ValueExprNode, ExprNode::TYPE_TRIM>
|
||||
{
|
||||
public:
|
||||
explicit TrimNode(MemoryPool& pool, UCHAR aWhere,
|
||||
explicit TrimNode(MemoryPool& pool, UCHAR aWhere, UCHAR aWhat,
|
||||
ValueExprNode* aValue = NULL, ValueExprNode* aTrimChars = NULL);
|
||||
|
||||
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
|
||||
@ -2137,6 +2137,7 @@ public:
|
||||
|
||||
public:
|
||||
UCHAR where;
|
||||
UCHAR what;
|
||||
NestConst<ValueExprNode> value;
|
||||
NestConst<ValueExprNode> trimChars; // may be NULL
|
||||
};
|
||||
|
@ -700,9 +700,12 @@ using namespace Firebird;
|
||||
// tokens added for Firebird 6.0
|
||||
|
||||
%token <metaNamePtr> ANY_VALUE
|
||||
%token <metaNamePtr> BTRIM
|
||||
%token <metaNamePtr> CALL
|
||||
%token <metaNamePtr> FORMAT
|
||||
%token <metaNamePtr> LTRIM
|
||||
%token <metaNamePtr> NAMED_ARG_ASSIGN
|
||||
%token <metaNamePtr> RTRIM
|
||||
|
||||
// precedence declarations for expression evaluation
|
||||
|
||||
@ -4494,7 +4497,10 @@ keyword_or_column
|
||||
| VARBINARY
|
||||
| WINDOW
|
||||
| WITHOUT
|
||||
| CALL // added in FB 6.0
|
||||
| BTRIM // added in FB 6.0
|
||||
| CALL
|
||||
| LTRIM
|
||||
| RTRIM
|
||||
;
|
||||
|
||||
col_opt
|
||||
@ -8735,6 +8741,9 @@ of_first_last_day_part
|
||||
string_value_function
|
||||
: substring_function
|
||||
| trim_function
|
||||
| btrim_function
|
||||
| ltrim_function
|
||||
| rtrim_function
|
||||
| UPPER '(' value ')'
|
||||
{ $$ = newNode<StrCaseNode>(blr_upcase, $3); }
|
||||
| LOWER '(' value ')'
|
||||
@ -8766,13 +8775,13 @@ string_length_opt
|
||||
%type <valueExprNode> trim_function
|
||||
trim_function
|
||||
: TRIM '(' trim_specification value FROM value ')'
|
||||
{ $$ = newNode<TrimNode>($3, $6, $4); }
|
||||
{ $$ = newNode<TrimNode>($3, blr_trim_characters, $6, $4); }
|
||||
| TRIM '(' value FROM value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_both, $5, $3); }
|
||||
{ $$ = newNode<TrimNode>(blr_trim_both, blr_trim_characters, $5, $3); }
|
||||
| TRIM '(' trim_specification FROM value ')'
|
||||
{ $$ = newNode<TrimNode>($3, $5); }
|
||||
{ $$ = newNode<TrimNode>($3, blr_trim_spaces, $5); }
|
||||
| TRIM '(' value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_both, $3); }
|
||||
{ $$ = newNode<TrimNode>(blr_trim_both, blr_trim_spaces, $3); }
|
||||
;
|
||||
|
||||
%type <blrOp> trim_specification
|
||||
@ -8782,6 +8791,30 @@ trim_specification
|
||||
| LEADING { $$ = blr_trim_leading; }
|
||||
;
|
||||
|
||||
%type <valueExprNode> btrim_function
|
||||
btrim_function
|
||||
: BTRIM '(' value ',' value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_both, blr_trim_multi_characters, $3, $5); }
|
||||
| BTRIM '(' value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_both, blr_trim_spaces, $3); }
|
||||
;
|
||||
|
||||
%type <valueExprNode> ltrim_function
|
||||
ltrim_function
|
||||
: LTRIM '(' value ',' value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_leading, blr_trim_multi_characters, $3, $5); }
|
||||
| LTRIM '(' value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_leading, blr_trim_spaces, $3); }
|
||||
;
|
||||
|
||||
%type <valueExprNode> rtrim_function
|
||||
rtrim_function
|
||||
: RTRIM '(' value ',' value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_trailing, blr_trim_multi_characters, $3, $5); }
|
||||
| RTRIM '(' value ')'
|
||||
{ $$ = newNode<TrimNode>(blr_trim_trailing, blr_trim_spaces, $3); }
|
||||
;
|
||||
|
||||
%type <valueExprNode> udf
|
||||
udf
|
||||
: symbol_UDF_call_name '(' argument_list_opt ')'
|
||||
|
@ -345,6 +345,7 @@
|
||||
/* second sub parameter for blr_trim */
|
||||
#define blr_trim_spaces (unsigned char)0
|
||||
#define blr_trim_characters (unsigned char)1
|
||||
#define blr_trim_multi_characters (unsigned char)2
|
||||
|
||||
/* These codes are actions for cursors */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user