8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 20:43:02 +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:
MrTeeett 2024-03-25 19:46:05 +03:00 committed by GitHub
parent 1844491fd9
commit 56e8c6224c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 129 additions and 27 deletions

View File

@ -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;

View File

@ -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)

View File

@ -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,9 +12801,13 @@ 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)
{
int charSize = charactersCanonical.getCount() / charactersLength;
if (!multi)
{ {
if (where == blr_trim_both || where == blr_trim_leading) if (where == blr_trim_both || where == blr_trim_leading)
{ {
@ -12832,6 +12837,55 @@ dsc* TrimNode::execute(thread_db* tdbb, Request* request) const
} }
} }
} }
else
{
if (where == blr_trim_both || where == blr_trim_leading)
{
while (offsetLead < valueCanonicalLen)
{
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;
}
}
}
}
if (valueDesc->isBlob()) if (valueDesc->isBlob())
{ {

View File

@ -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
}; };

View File

@ -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 ')'

View File

@ -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 */