mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 18:03: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> ::=
|
<trim character> ::=
|
||||||
<value expression>
|
<value expression>
|
||||||
|
|
||||||
|
<multi-character trim function> ::=
|
||||||
|
{ BTRIM | LTRIM | RTRIM } <left paren> <value expression> [ <comma> <trim character> ] <right paren>
|
||||||
|
|
||||||
Syntax Rules:
|
Syntax Rules:
|
||||||
1) If <trim specification> is not specified, BOTH is assumed.
|
1) If <trim specification> is not specified, BOTH is assumed.
|
||||||
2) If <trim character> is not specified, ' ' 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.
|
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.
|
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:
|
Examples:
|
||||||
A)
|
A)
|
||||||
@ -36,3 +41,8 @@ B)
|
|||||||
trim(rdb$relation_name) || ' is a system table'
|
trim(rdb$relation_name) || ' is a system table'
|
||||||
from rdb$relations
|
from rdb$relations
|
||||||
where rdb$system_flag = 1;
|
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_TRAPS, "TRAPS", true)
|
||||||
PARSER_TOKEN(TOK_TRIGGER, "TRIGGER", false)
|
PARSER_TOKEN(TOK_TRIGGER, "TRIGGER", false)
|
||||||
PARSER_TOKEN(TOK_TRIM, "TRIM", 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_TRUE, "TRUE", false)
|
||||||
PARSER_TOKEN(TOK_TRUNC, "TRUNC", true)
|
PARSER_TOKEN(TOK_TRUNC, "TRUNC", true)
|
||||||
PARSER_TOKEN(TOK_TRUSTED, "TRUSTED", true)
|
PARSER_TOKEN(TOK_TRUSTED, "TRUSTED", true)
|
||||||
|
@ -12531,9 +12531,10 @@ ValueExprNode* SysFuncCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|||||||
|
|
||||||
static RegisterNode<TrimNode> regTrimNode({blr_trim});
|
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),
|
: TypedNode<ValueExprNode, ExprNode::TYPE_TRIM>(pool),
|
||||||
where(aWhere),
|
where(aWhere),
|
||||||
|
what(aWhat),
|
||||||
value(aValue),
|
value(aValue),
|
||||||
trimChars(aTrimChars)
|
trimChars(aTrimChars)
|
||||||
{
|
{
|
||||||
@ -12544,9 +12545,9 @@ DmlNode* TrimNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb
|
|||||||
UCHAR where = csb->csb_blr_reader.getByte();
|
UCHAR where = csb->csb_blr_reader.getByte();
|
||||||
UCHAR what = 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->trimChars = PAR_parse_value(tdbb, csb);
|
||||||
|
|
||||||
node->value = 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)
|
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));
|
doDsqlPass(dsqlScratch, value), doDsqlPass(dsqlScratch, trimChars));
|
||||||
|
|
||||||
// Try to force trimChars to be same type as value: TRIM(? FROM FIELD)
|
// Try to force trimChars to be same type as value: TRIM(? FROM FIELD)
|
||||||
@ -12595,7 +12596,7 @@ void TrimNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
|
|
||||||
if (trimChars)
|
if (trimChars)
|
||||||
{
|
{
|
||||||
dsqlScratch->appendUChar(blr_trim_characters);
|
dsqlScratch->appendUChar(what);
|
||||||
GEN_expr(dsqlScratch, trimChars);
|
GEN_expr(dsqlScratch, trimChars);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -12665,7 +12666,7 @@ void TrimNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
|
|||||||
|
|
||||||
ValueExprNode* TrimNode::copy(thread_db* tdbb, NodeCopier& copier) const
|
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);
|
node->value = copier.copy(tdbb, value);
|
||||||
if (trimChars)
|
if (trimChars)
|
||||||
node->trimChars = copier.copy(tdbb, trimChars);
|
node->trimChars = copier.copy(tdbb, trimChars);
|
||||||
@ -12800,34 +12801,87 @@ dsc* TrimNode::execute(thread_db* tdbb, Request* request) const
|
|||||||
|
|
||||||
SLONG offsetLead = 0;
|
SLONG offsetLead = 0;
|
||||||
SLONG offsetTrail = valueCanonicalLen;
|
SLONG offsetTrail = valueCanonicalLen;
|
||||||
|
const bool multi = what == blr_trim_multi_characters;
|
||||||
|
|
||||||
// CVC: Avoid endless loop with zero length trim chars.
|
// CVC: Avoid endless loop with zero length trim chars.
|
||||||
if (charactersCanonicalLen)
|
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.
|
if (where == blr_trim_both || where == blr_trim_leading)
|
||||||
for (; offsetLead + charactersCanonicalLen <= valueCanonicalLen;
|
|
||||||
offsetLead += charactersCanonicalLen)
|
|
||||||
{
|
{
|
||||||
if (memcmp(charactersCanonical.begin(), &valueCanonical[offsetLead],
|
// CVC: Prevent surprises with offsetLead < valueCanonicalLen; it may fail.
|
||||||
charactersCanonicalLen) != 0)
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (where == blr_trim_both || where == blr_trim_trailing)
|
|
||||||
{
|
{
|
||||||
for (; offsetTrail - charactersCanonicalLen >= offsetLead;
|
if (where == blr_trim_both || where == blr_trim_leading)
|
||||||
offsetTrail -= charactersCanonicalLen)
|
|
||||||
{
|
{
|
||||||
if (memcmp(charactersCanonical.begin(),
|
while (offsetLead < valueCanonicalLen)
|
||||||
&valueCanonical[offsetTrail - charactersCanonicalLen],
|
|
||||||
charactersCanonicalLen) != 0)
|
|
||||||
{
|
{
|
||||||
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>
|
class TrimNode final : public TypedNode<ValueExprNode, ExprNode::TYPE_TRIM>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit TrimNode(MemoryPool& pool, UCHAR aWhere,
|
explicit TrimNode(MemoryPool& pool, UCHAR aWhere, UCHAR aWhat,
|
||||||
ValueExprNode* aValue = NULL, ValueExprNode* aTrimChars = NULL);
|
ValueExprNode* aValue = NULL, ValueExprNode* aTrimChars = NULL);
|
||||||
|
|
||||||
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
|
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
|
||||||
@ -2137,6 +2137,7 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
UCHAR where;
|
UCHAR where;
|
||||||
|
UCHAR what;
|
||||||
NestConst<ValueExprNode> value;
|
NestConst<ValueExprNode> value;
|
||||||
NestConst<ValueExprNode> trimChars; // may be NULL
|
NestConst<ValueExprNode> trimChars; // may be NULL
|
||||||
};
|
};
|
||||||
|
@ -700,9 +700,12 @@ using namespace Firebird;
|
|||||||
// tokens added for Firebird 6.0
|
// tokens added for Firebird 6.0
|
||||||
|
|
||||||
%token <metaNamePtr> ANY_VALUE
|
%token <metaNamePtr> ANY_VALUE
|
||||||
|
%token <metaNamePtr> BTRIM
|
||||||
%token <metaNamePtr> CALL
|
%token <metaNamePtr> CALL
|
||||||
%token <metaNamePtr> FORMAT
|
%token <metaNamePtr> FORMAT
|
||||||
|
%token <metaNamePtr> LTRIM
|
||||||
%token <metaNamePtr> NAMED_ARG_ASSIGN
|
%token <metaNamePtr> NAMED_ARG_ASSIGN
|
||||||
|
%token <metaNamePtr> RTRIM
|
||||||
|
|
||||||
// precedence declarations for expression evaluation
|
// precedence declarations for expression evaluation
|
||||||
|
|
||||||
@ -4494,7 +4497,10 @@ keyword_or_column
|
|||||||
| VARBINARY
|
| VARBINARY
|
||||||
| WINDOW
|
| WINDOW
|
||||||
| WITHOUT
|
| WITHOUT
|
||||||
| CALL // added in FB 6.0
|
| BTRIM // added in FB 6.0
|
||||||
|
| CALL
|
||||||
|
| LTRIM
|
||||||
|
| RTRIM
|
||||||
;
|
;
|
||||||
|
|
||||||
col_opt
|
col_opt
|
||||||
@ -8735,6 +8741,9 @@ of_first_last_day_part
|
|||||||
string_value_function
|
string_value_function
|
||||||
: substring_function
|
: substring_function
|
||||||
| trim_function
|
| trim_function
|
||||||
|
| btrim_function
|
||||||
|
| ltrim_function
|
||||||
|
| rtrim_function
|
||||||
| UPPER '(' value ')'
|
| UPPER '(' value ')'
|
||||||
{ $$ = newNode<StrCaseNode>(blr_upcase, $3); }
|
{ $$ = newNode<StrCaseNode>(blr_upcase, $3); }
|
||||||
| LOWER '(' value ')'
|
| LOWER '(' value ')'
|
||||||
@ -8766,13 +8775,13 @@ string_length_opt
|
|||||||
%type <valueExprNode> trim_function
|
%type <valueExprNode> trim_function
|
||||||
trim_function
|
trim_function
|
||||||
: TRIM '(' trim_specification value FROM value ')'
|
: TRIM '(' trim_specification value FROM value ')'
|
||||||
{ $$ = newNode<TrimNode>($3, $6, $4); }
|
{ $$ = newNode<TrimNode>($3, blr_trim_characters, $6, $4); }
|
||||||
| TRIM '(' value FROM value ')'
|
| 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 ')'
|
| TRIM '(' trim_specification FROM value ')'
|
||||||
{ $$ = newNode<TrimNode>($3, $5); }
|
{ $$ = newNode<TrimNode>($3, blr_trim_spaces, $5); }
|
||||||
| TRIM '(' value ')'
|
| TRIM '(' value ')'
|
||||||
{ $$ = newNode<TrimNode>(blr_trim_both, $3); }
|
{ $$ = newNode<TrimNode>(blr_trim_both, blr_trim_spaces, $3); }
|
||||||
;
|
;
|
||||||
|
|
||||||
%type <blrOp> trim_specification
|
%type <blrOp> trim_specification
|
||||||
@ -8782,6 +8791,30 @@ trim_specification
|
|||||||
| LEADING { $$ = blr_trim_leading; }
|
| 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
|
%type <valueExprNode> udf
|
||||||
udf
|
udf
|
||||||
: symbol_UDF_call_name '(' argument_list_opt ')'
|
: symbol_UDF_call_name '(' argument_list_opt ')'
|
||||||
|
@ -345,6 +345,7 @@
|
|||||||
/* second sub parameter for blr_trim */
|
/* second sub parameter for blr_trim */
|
||||||
#define blr_trim_spaces (unsigned char)0
|
#define blr_trim_spaces (unsigned char)0
|
||||||
#define blr_trim_characters (unsigned char)1
|
#define blr_trim_characters (unsigned char)1
|
||||||
|
#define blr_trim_multi_characters (unsigned char)2
|
||||||
|
|
||||||
/* These codes are actions for cursors */
|
/* These codes are actions for cursors */
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user