This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch mysql_trim
in repository https://gitbox.apache.org/repos/asf/doris.git

commit b57c527ba4e81fb66f71464c208dd744f91f2b61
Author: morrySnow <[email protected]>
AuthorDate: Tue Jul 29 16:47:58 2025 +0800

    [feature](function) support MySQL dialect of function TRIM
    
    MySQL doc: 
https://dev.mysql.com/doc/refman/8.4/en/string-functions.html#function_trim
    
    ```sql
    TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str), TRIM([remstr FROM] 
str)
    ```
---
 .../antlr4/org/apache/doris/nereids/DorisLexer.g4  |  3 +
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |  2 +
 .../doris/nereids/parser/LogicalPlanBuilder.java   | 16 +++++
 .../doris/nereids/parser/NereidsParserTest.java    | 77 ++++++++++++++++++++++
 4 files changed, 98 insertions(+)

diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
index f6dd3858509..4e0fa471caf 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
@@ -106,6 +106,7 @@ BITOR: 'BITOR';
 BITXOR: 'BITXOR';
 BLOB: 'BLOB';
 BOOLEAN: 'BOOLEAN';
+BOTH: 'BOTH';
 BRANCH: 'BRANCH';
 BRIEF: 'BRIEF';
 BROKER: 'BROKER';
@@ -316,6 +317,7 @@ LAST: 'LAST';
 LATERAL: 'LATERAL';
 LDAP: 'LDAP';
 LDAP_ADMIN_PASSWORD: 'LDAP_ADMIN_PASSWORD';
+LEADING: 'LEADING';
 LEFT: 'LEFT';
 LESS: 'LESS';
 LEVEL: 'LEVEL';
@@ -524,6 +526,7 @@ TINYINT: 'TINYINT';
 TO: 'TO';
 TOKENIZER: 'TOKENIZER';
 TOKEN_FILTER: 'TOKEN_FILTER';
+TRAILING: 'TRAILING';
 TRANSACTION: 'TRANSACTION';
 TRASH: 'TRASH';
 TREE: 'TREE';
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index c3af5ac02b7..c111a86701d 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -1568,6 +1568,8 @@ primaryExpression
         (ORDER BY sortItem (COMMA sortItem)*)?
         (SEPARATOR sep=expression)? RIGHT_PAREN
         (OVER windowSpec)?                                                     
                #groupConcat
+    | TRIM LEFT_PAREN
+        ((BOTH | LEADING | TRAILING) expression? | expression) FROM expression 
RIGHT_PAREN     #trim
     | functionCallExpression                                                   
                #functionCall
     | value=primaryExpression LEFT_BRACKET index=valueExpression RIGHT_BRACKET 
                #elementAt
     | value=primaryExpression LEFT_BRACKET begin=valueExpression
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 57558335ae5..1e922fb6abf 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -441,6 +441,7 @@ import 
org.apache.doris.nereids.DorisParser.TableNameContext;
 import org.apache.doris.nereids.DorisParser.TableSnapshotContext;
 import org.apache.doris.nereids.DorisParser.TableValuedFunctionContext;
 import org.apache.doris.nereids.DorisParser.TabletListContext;
+import org.apache.doris.nereids.DorisParser.TrimContext;
 import org.apache.doris.nereids.DorisParser.TypeConstructorContext;
 import org.apache.doris.nereids.DorisParser.UninstallPluginContext;
 import org.apache.doris.nereids.DorisParser.UnitIdentifierContext;
@@ -3043,6 +3044,21 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         });
     }
 
+    @Override
+    public Object visitTrim(TrimContext ctx) {
+        return ParserUtils.withOrigin(ctx, () -> {
+            List<Expression> params = visit(ctx.expression(), 
Expression.class);
+            params = Lists.reverse(params);
+            String name = "trim";
+            if (ctx.LEADING() != null) {
+                name = "ltrim";
+            } else if (ctx.TRAILING() != null) {
+                name = "rtrim";
+            }
+            return processUnboundFunction(ctx, null, name, false, params, 
null, null);
+        });
+    }
+
     @Override
     public Expression 
visitFunctionCallExpression(DorisParser.FunctionCallExpressionContext ctx) {
         return ParserUtils.withOrigin(ctx, () -> {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
index 00292a01c99..b606483bf53 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
@@ -876,6 +876,83 @@ public class NereidsParserTest extends ParserTestBase {
         }
     }
 
+    @Test
+    public void testTrim() {
+        NereidsParser parser = new NereidsParser();
+        String sql;
+        Expression e;
+        UnboundFunction unboundFunction;
+
+        sql = "trim('1', '2')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("trim", unboundFunction.getName());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+        Assertions.assertEquals("2", ((StringLikeLiteral) 
unboundFunction.child(1)).getStringValue());
+
+        sql = "trim('2' from '1')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("trim", unboundFunction.getName());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+        Assertions.assertEquals("2", ((StringLikeLiteral) 
unboundFunction.child(1)).getStringValue());
+
+        sql = "trim(both '2' from '1')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("trim", unboundFunction.getName());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+        Assertions.assertEquals("2", ((StringLikeLiteral) 
unboundFunction.child(1)).getStringValue());
+
+        sql = "trim(leading '2' from '1')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("ltrim", unboundFunction.getName());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+        Assertions.assertEquals("2", ((StringLikeLiteral) 
unboundFunction.child(1)).getStringValue());
+
+        sql = "trim(trailing '2' from '1')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("rtrim", unboundFunction.getName());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+        Assertions.assertEquals("2", ((StringLikeLiteral) 
unboundFunction.child(1)).getStringValue());
+
+        sql = "trim(both from '1')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("trim", unboundFunction.getName());
+        Assertions.assertEquals(1, unboundFunction.arity());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+
+        sql = "trim(leading from '1')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("ltrim", unboundFunction.getName());
+        Assertions.assertEquals(1, unboundFunction.arity());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+
+        sql = "trim(trailing from '1')";
+        e = parser.parseExpression(sql);
+        Assertions.assertInstanceOf(UnboundFunction.class, e);
+        unboundFunction = (UnboundFunction) e;
+        Assertions.assertEquals("rtrim", unboundFunction.getName());
+        Assertions.assertEquals(1, unboundFunction.arity());
+        Assertions.assertEquals("1", ((StringLikeLiteral) 
unboundFunction.child(0)).getStringValue());
+
+        Assertions.assertThrowsExactly(ParseException.class, () -> 
parser.parseExpression("trim(invalid '2' from '1')"));
+        Assertions.assertThrowsExactly(ParseException.class, () -> 
parser.parseExpression("trim(invalid '2' '1')"));
+        Assertions.assertThrowsExactly(ParseException.class, () -> 
parser.parseExpression("trim(from '1')"));
+        Assertions.assertThrowsExactly(ParseException.class, () -> 
parser.parseExpression("trim(both '1')"));
+    }
+
     @Test
     public void testNoBackSlashEscapes() {
         testNoBackSlashEscapes("''", "", "");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to