You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2023/03/23 11:43:50 UTC
[iotdb] branch master updated: [IoTDB-5637] Add substring as built-in scalar function
This is an automated email from the ASF dual-hosted git repository.
jackietien pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 3ee95741cc [IoTDB-5637] Add substring as built-in scalar function
3ee95741cc is described below
commit 3ee95741cc4e6d87e2fe81e56ad2c915e67bb075
Author: Zhijia Cao <ca...@126.com>
AuthorDate: Thu Mar 23 19:43:42 2023 +0800
[IoTDB-5637] Add substring as built-in scalar function
---
.../org/apache/iotdb/db/qp/sql/IdentifierParser.g4 | 1 +
.../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 | 10 +
.../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 | 4 +
docs/UserGuide/Operators-Functions/String.md | 14 +-
docs/zh/UserGuide/Operators-Functions/Overview.md | 28 +--
docs/zh/UserGuide/Operators-Functions/String.md | 14 +-
.../itbase/constant/BuiltinScalarFunctionEnum.java | 1 +
.../BuiltinTimeSeriesGeneratingFunctionEnum.java | 1 -
.../scalar/IoTDBSubStringFunctionIT.java | 267 +++++++++++++++++++++
.../it/udf/IoTDBUDTFBuiltinScalarFunctionIT.java | 5 +-
.../commons/udf/builtin/BuiltinScalarFunction.java | 1 +
.../BuiltinTimeSeriesGeneratingFunction.java | 2 -
.../commons/udf/builtin/String/UDTFSubstr.java | 70 ------
.../org/apache/iotdb/db/constant/SqlConstant.java | 8 +
.../BuiltInScalarFunctionHelperFactory.java | 3 +
.../builtin/helper/SubStringFunctionHelper.java | 129 ++++++++++
.../iotdb/db/mpp/plan/parser/ASTVisitor.java | 29 +++
.../scalar/SubStringFunctionColumnTransformer.java | 63 +++++
.../unary/scalar/SubStringFunctionTransformer.java | 63 +++++
19 files changed, 609 insertions(+), 104 deletions(-)
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
index ce0b227f43..29874313e2 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
@@ -175,6 +175,7 @@ keyWords
| STATELESS
| STATEMENT
| STOP
+ | SUBSTRING
| SYSTEM
| TAGS
| TASK
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
index e7a8e8b603..00a9d5d481 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
@@ -983,6 +983,7 @@ functionName
scalarFunctionExpression
: CAST LR_BRACKET castInput=expression AS attributeValue RR_BRACKET
| REPLACE LR_BRACKET text=expression COMMA from=STRING_LITERAL COMMA to=STRING_LITERAL RR_BRACKET
+ | SUBSTRING subStringExpression
| ROUND LR_BRACKET input=expression (COMMA places=constant)? RR_BRACKET
;
@@ -1059,3 +1060,12 @@ connectorAttributesClause
connectorAttributeClause
: connectorKey=STRING_LITERAL OPERATOR_SEQ connectorValue=STRING_LITERAL
;
+
+subStringExpression
+ : LR_BRACKET input=expression COMMA startPosition=signedIntegerLiteral (COMMA length=signedIntegerLiteral)? RR_BRACKET
+ | LR_BRACKET input=expression FROM from=signedIntegerLiteral (FOR forLength=signedIntegerLiteral)? RR_BRACKET
+ ;
+
+signedIntegerLiteral
+ : (PLUS|MINUS)?INTEGER_LITERAL
+ ;
\ No newline at end of file
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
index 9e5bfcf417..7024c37c0d 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
@@ -646,6 +646,10 @@ STOP
: S T O P
;
+SUBSTRING
+ : S U B S T R I N G
+ ;
+
SYSTEM
: S Y S T E M
;
diff --git a/docs/UserGuide/Operators-Functions/String.md b/docs/UserGuide/Operators-Functions/String.md
index baef2100de..30d51bf489 100644
--- a/docs/UserGuide/Operators-Functions/String.md
+++ b/docs/UserGuide/Operators-Functions/String.md
@@ -359,19 +359,19 @@ Output series:
+-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
```
-## Substr
+## substring
### Usage
-The function is used to get the substring `start` to `end - 1`.
+Extracts a substring of a string, starting with the first specified character and stopping after the specified number of characters.The index start at 1 .The value range of from and for is an INT32.
-**Name:** SUBSTR
+**Name:** SUBSTRING
**Input Series:** Only support a single input series. The data type is TEXT.
**Parameter:**
-+ `start`: Indicates the start position of substring.
-+ `end`: Indicates the end position of substring.
++ `from`: Indicates the start position of substring.
++ `for`: Indicates how many characters to stop after of substring.
**Output Series:** Output a single series. The type is TEXT.
@@ -393,14 +393,14 @@ Input series:
SQL for query:
```sql
-select s1, substr(s1, "start"="0", "end"="2") from root.sg1.d1
+select s1, substring(s1 from 1 for 2) from root.sg1.d1
```
Output series:
```
+-----------------------------+--------------+----------------------------------------------+
-| Time|root.sg1.d1.s1|substr(root.sg1.d1.s1, "start"="0", "end"="2")|
+| Time|root.sg1.d1.s1|SUBSTRING(root.sg1.d1.s1 FROM 1 FOR 2) |
+-----------------------------+--------------+----------------------------------------------+
|1970-01-01T08:00:00.001+08:00| 1test1| 1t|
|1970-01-01T08:00:00.002+08:00| 22test22| 22|
diff --git a/docs/zh/UserGuide/Operators-Functions/Overview.md b/docs/zh/UserGuide/Operators-Functions/Overview.md
index be0b9ff3f9..257b4587f6 100644
--- a/docs/zh/UserGuide/Operators-Functions/Overview.md
+++ b/docs/zh/UserGuide/Operators-Functions/Overview.md
@@ -149,20 +149,20 @@ OR, |, ||
### 字符串处理函数
-| 函数名 | 输入序列类型 | 必要的属性参数 | 输出序列类型 | 功能描述 |
-| --------------- | ------------ | ------------------------------------ | ------------ | ----------------------------------------- |
-| STRING_CONTAINS | TEXT | `s`: 待搜寻的字符串 | BOOLEAN | 判断字符串中是否存在`s` |
-| STRING_MATCHES | TEXT | `regex`: Java 标准库风格的正则表达式 | BOOLEAN | 判断字符串是否能够被正则表达式`regex`匹配 |
-| LENGTH | TEXT | 无 | INT32 | 返回字符串的长度 |
-| LOCATE | TEXT | `target`: 需要被定位的子串 <br/> `reverse`: 指定是否需要倒序定位,默认值为`false`, 即从左至右定位 | INT32 | 获取`target`子串第一次出现在输入序列的位置,如果输入序列中不包含`target`则返回 -1 |
-| STARTSWITH | TEXT | `target`: 需要匹配的前缀 | BOOLEAN | 判断字符串是否有指定前缀 |
-| ENDSWITH | TEXT | `target`: 需要匹配的后缀 | BOOLEAN | 判断字符串是否有指定后缀 |
-| CONCAT | TEXT | `targets`: 一系列 K-V, key需要以`target`为前缀且不重复, value是待拼接的字符串。<br/>`series_behind`: 指定拼接时时间序列是否在后面,默认为`false`。 | TEXT | 拼接字符串和`target`字串 |
-| SUBSTR | TEXT | `start`: 指定子串开始下标 <br/>`end`: 指定子串结束下标 | TEXT | 获取下标从`start`到`end - 1`的子串 |
-| UPPER | TEXT | 无 | TEXT | 将字符串转化为大写 |
-| LOWER | TEXT | 无 | TEXT | 将字符串转化为小写 |
-| TRIM | TEXT | 无 | TEXT | 移除字符串前后的空格 |
-| STRCMP | TEXT | 无 | TEXT | 用于比较两个输入序列,如果值相同返回 `0` , 序列1的值小于序列2的值返回一个`负数`,序列1的值大于序列2的值返回一个`正数` |
+| 函数名 | 输入序列类型 | 必要的属性参数 | 输出序列类型 | 功能描述 |
+|-----------------| ------------ |-----------------------------------------------------------------------------------------------------------| ------------ |-------------------------------------------------------------------------|
+| STRING_CONTAINS | TEXT | `s`: 待搜寻的字符串 | BOOLEAN | 判断字符串中是否存在`s` |
+| STRING_MATCHES | TEXT | `regex`: Java 标准库风格的正则表达式 | BOOLEAN | 判断字符串是否能够被正则表达式`regex`匹配 |
+| LENGTH | TEXT | 无 | INT32 | 返回字符串的长度 |
+| LOCATE | TEXT | `target`: 需要被定位的子串 <br/> `reverse`: 指定是否需要倒序定位,默认值为`false`, 即从左至右定位 | INT32 | 获取`target`子串第一次出现在输入序列的位置,如果输入序列中不包含`target`则返回 -1 |
+| STARTSWITH | TEXT | `target`: 需要匹配的前缀 | BOOLEAN | 判断字符串是否有指定前缀 |
+| ENDSWITH | TEXT | `target`: 需要匹配的后缀 | BOOLEAN | 判断字符串是否有指定后缀 |
+| CONCAT | TEXT | `targets`: 一系列 K-V, key需要以`target`为前缀且不重复, value是待拼接的字符串。<br/>`series_behind`: 指定拼接时时间序列是否在后面,默认为`false`。 | TEXT | 拼接字符串和`target`字串 |
+| SUBSTRING | TEXT | `from`: 指定子串开始下标 <br/>`for`: 指定的字符个数之后停止 | TEXT | 提取字符串的子字符串,从指定的第一个字符开始,并在指定的字符数之后停止。下标从1开始。from 和 for的范围是 INT32 类型取值范围。 |
+| UPPER | TEXT | 无 | TEXT | 将字符串转化为大写 |
+| LOWER | TEXT | 无 | TEXT | 将字符串转化为小写 |
+| TRIM | TEXT | 无 | TEXT | 移除字符串前后的空格 |
+| STRCMP | TEXT | 无 | TEXT | 用于比较两个输入序列,如果值相同返回 `0` , 序列1的值小于序列2的值返回一个`负数`,序列1的值大于序列2的值返回一个`正数` |
详细说明及示例见文档 [字符串处理函数](./String.md)。
diff --git a/docs/zh/UserGuide/Operators-Functions/String.md b/docs/zh/UserGuide/Operators-Functions/String.md
index 2535364a48..f59bcc7abc 100644
--- a/docs/zh/UserGuide/Operators-Functions/String.md
+++ b/docs/zh/UserGuide/Operators-Functions/String.md
@@ -363,19 +363,19 @@ select s1, s2, concat(s1, s2, "target1"="IoT", "target2"="DB", "series_behind"="
+-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
```
-### Substr
+### Substring
#### 函数简介
+提取字符串的子字符串,从指定的第一个字符开始,并在指定的字符数之后停止。下标从1开始。from 和 for的范围是 INT32 类型取值范围。
-本函数用于获取下标从`start`到`end - 1`的子串
+**函数名:** SUBSTRING
-**函数名:** SUBSTR
**输入序列:** 仅支持单个输入序列,类型为TEXT。
**参数:**
-+ `start`: 指定子串开始下标。
-+ `end`: 指定子串结束下标。
++ `from`: 指定子串开始下标。
++ `for`: 指定多少个字符数后停止。
**输出序列:** 输出单个序列,类型为 TEXT。
@@ -397,14 +397,14 @@ select s1, s2, concat(s1, s2, "target1"="IoT", "target2"="DB", "series_behind"="
用于查询的 SQL 语句:
```sql
-select s1, substr(s1, "start"="0", "end"="2") from root.sg1.d1
+select s1, substring(s1 from 1 for 2) from root.sg1.d1
```
输出序列:
```
+-----------------------------+--------------+----------------------------------------------+
-| Time|root.sg1.d1.s1|substr(root.sg1.d1.s1, "start"="0", "end"="2")|
+| Time|root.sg1.d1.s1| SUBSTRING(root.sg1.d1.s1 FROM 1 FOR 2) |
+-----------------------------+--------------+----------------------------------------------+
|1970-01-01T08:00:00.001+08:00| 1test1| 1t|
|1970-01-01T08:00:00.002+08:00| 22test22| 22|
diff --git a/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinScalarFunctionEnum.java b/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinScalarFunctionEnum.java
index 3784af7c61..1010e1cac5 100644
--- a/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinScalarFunctionEnum.java
+++ b/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinScalarFunctionEnum.java
@@ -23,6 +23,7 @@ public enum BuiltinScalarFunctionEnum {
DIFF("diff"),
CAST("cast"),
REPLACE("replace"),
+ SUBSTRING("substring"),
ROUND("round");
;
diff --git a/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinTimeSeriesGeneratingFunctionEnum.java b/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinTimeSeriesGeneratingFunctionEnum.java
index cf3b130a48..337bdb9763 100644
--- a/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinTimeSeriesGeneratingFunctionEnum.java
+++ b/integration-test/src/main/java/org/apache/iotdb/itbase/constant/BuiltinTimeSeriesGeneratingFunctionEnum.java
@@ -49,7 +49,6 @@ public enum BuiltinTimeSeriesGeneratingFunctionEnum {
STRING_STARTS_WITH("STARTSWITH"),
STRING_ENDS_WITH("ENDSWITH"),
STRING_CONCAT("CONCAT"),
- STRING_SUBSTR("SUBSTR"),
STRING_UPPER("UPPER"),
STRING_LOWER("LOWER"),
STRING_TRIM("TRIM"),
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/builtinfunction/scalar/IoTDBSubStringFunctionIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/builtinfunction/scalar/IoTDBSubStringFunctionIT.java
new file mode 100644
index 0000000000..aff2fe3d14
--- /dev/null
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/builtinfunction/scalar/IoTDBSubStringFunctionIT.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.it.builtinfunction.scalar;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.assertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareData;
+import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualTest;
+import static org.junit.Assert.fail;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class, ClusterIT.class})
+public class IoTDBSubStringFunctionIT {
+ private static final String[] SQLs =
+ new String[] {
+ "CREATE DATABASE root.sg",
+ "CREATE TIMESERIES root.sg.s1 WITH DATATYPE=TEXT, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.sg.s2 WITH DATATYPE=INT32, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.sg.s3 WITH DATATYPE=INT64, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.sg.s4 WITH DATATYPE=FLOAT, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.sg.s5 WITH DATATYPE=DOUBLE, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.sg.s6 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+ "INSERT INTO root.sg(timestamp,s1,s2,s3,s4,s5,s6) values(1, 'abcd', 1, 1, 1, 1, true)",
+ "INSERT INTO root.sg(timestamp,s1) values(2, 'test')",
+ "INSERT INTO root.sg(timestamp,s1) values(3, 'abcdefg')",
+ "flush"
+ };
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ EnvFactory.getEnv().initClusterEnvironment();
+ prepareData(SQLs);
+ registerUDF();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ }
+
+ private static void registerUDF() {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute(
+ "create function constvalue as 'org.apache.iotdb.db.query.udf.example.ConstValue'");
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void testNewTransformer() {
+ // normal
+ String[] expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,SUBSTRING(root.sg.s1,1),SUBSTRING(root.sg.s1,1,3),SUBSTRING(root.sg.s1 FROM 1),SUBSTRING(root.sg.s1 FROM 1 FOR 3)"
+ };
+ String[] retArray =
+ new String[] {
+ "1,abcd,abcd,abc,abcd,abc,",
+ "2,test,test,tes,test,tes,",
+ "3,abcdefg,abcdefg,abc,abcdefg,abc,",
+ };
+ resultSetEqualTest(
+ "select s1,SUBSTRING(s1,1),SUBSTRING(s1,1,3),SUBSTRING(s1 from 1),SUBSTRING(s1 from 1 for 3) from root.sg",
+ expectedHeader,
+ retArray);
+
+ // negative start position
+ expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,SUBSTRING(root.sg.s1,-1),SUBSTRING(root.sg.s1,-1,3),SUBSTRING(root.sg.s1 FROM -1),SUBSTRING(root.sg.s1 FROM -1 FOR 3)"
+ };
+ retArray =
+ new String[] {
+ "1,abcd,abcd,a,abcd,a,", "2,test,test,t,test,t,", "3,abcdefg,abcdefg,a,abcdefg,a,",
+ };
+ resultSetEqualTest(
+ "select s1,SUBSTRING(s1,-1),SUBSTRING(s1,-1,3),SUBSTRING(s1 from -1),SUBSTRING(s1 from -1 for 3) from root.sg",
+ expectedHeader,
+ retArray);
+
+ // param 1 greater than input series length
+ expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,SUBSTRING(root.sg.s1,11),SUBSTRING(root.sg.s1,11,13),SUBSTRING(root.sg.s1 FROM 11),SUBSTRING(root.sg.s1 FROM 11 FOR 13)"
+ };
+ retArray =
+ new String[] {
+ "1,abcd,,,,,", "2,test,,,,,", "3,abcdefg,,,,,",
+ };
+ resultSetEqualTest(
+ "select s1,SUBSTRING(s1,11),SUBSTRING(s1,11,13),SUBSTRING(s1 from 11),SUBSTRING(s1 from 11 for 13) from root.sg",
+ expectedHeader,
+ retArray);
+
+ // param 2 greater than input series length
+ expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,SUBSTRING(root.sg.s1,-1),SUBSTRING(root.sg.s1,-1,13),SUBSTRING(root.sg.s1 FROM -1),SUBSTRING(root.sg.s1 FROM -1 FOR 13)"
+ };
+ retArray =
+ new String[] {
+ "1,abcd,abcd,abcd,abcd,abcd,",
+ "2,test,test,test,test,test,",
+ "3,abcdefg,abcdefg,abcdefg,abcdefg,abcdefg,",
+ };
+ resultSetEqualTest(
+ "select s1,SUBSTRING(s1,-1),SUBSTRING(s1,-1,13),SUBSTRING(s1 from -1),SUBSTRING(s1 from -1 for 13) from root.sg",
+ expectedHeader,
+ retArray);
+ }
+
+ @Test
+ public void testOldTransformer() {
+ // normal
+ String[] expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,change_points(root.sg.s1),SUBSTRING(root.sg.s1,1),SUBSTRING(root.sg.s1,1,3),SUBSTRING(root.sg.s1 FROM 1),SUBSTRING(root.sg.s1 FROM 1 FOR 3)"
+ };
+ String[] retArray =
+ new String[] {
+ "1,abcd,abcd,abcd,abc,abcd,abc,",
+ "2,test,test,test,tes,test,tes,",
+ "3,abcdefg,abcdefg,abcdefg,abc,abcdefg,abc,",
+ };
+ resultSetEqualTest(
+ "select s1,change_points(s1),SUBSTRING(s1,1),SUBSTRING(s1,1,3),SUBSTRING(s1 from 1),SUBSTRING(s1 from 1 for 3) from root.sg",
+ expectedHeader,
+ retArray);
+
+ // negative start position
+ expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,change_points(root.sg.s1),SUBSTRING(root.sg.s1,-1),SUBSTRING(root.sg.s1,-1,3),SUBSTRING(root.sg.s1 FROM -1),SUBSTRING(root.sg.s1 FROM -1 FOR 3)"
+ };
+ retArray =
+ new String[] {
+ "1,abcd,abcd,abcd,a,abcd,a,",
+ "2,test,test,test,t,test,t,",
+ "3,abcdefg,abcdefg,abcdefg,a,abcdefg,a,",
+ };
+ resultSetEqualTest(
+ "select s1,change_points(s1),SUBSTRING(s1,-1),SUBSTRING(s1,-1,3),SUBSTRING(s1 from -1),SUBSTRING(s1 from -1 for 3) from root.sg",
+ expectedHeader,
+ retArray);
+
+ // param 1 greater than input series length
+ expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,change_points(root.sg.s1),SUBSTRING(root.sg.s1,11),SUBSTRING(root.sg.s1,11,13),SUBSTRING(root.sg.s1 FROM 11),SUBSTRING(root.sg.s1 FROM 11 FOR 13)"
+ };
+ retArray =
+ new String[] {
+ "1,abcd,abcd,,,,,", "2,test,test,,,,,", "3,abcdefg,abcdefg,,,,,",
+ };
+ resultSetEqualTest(
+ "select s1,change_points(s1),SUBSTRING(s1,11),SUBSTRING(s1,11,13),SUBSTRING(s1 from 11),SUBSTRING(s1 from 11 for 13) from root.sg",
+ expectedHeader,
+ retArray);
+
+ // param 2 greater than input series length
+ expectedHeader =
+ new String[] {
+ "Time,root.sg.s1,change_points(root.sg.s1),SUBSTRING(root.sg.s1,-1),SUBSTRING(root.sg.s1,-1,13),SUBSTRING(root.sg.s1 FROM -1),SUBSTRING(root.sg.s1 FROM -1 FOR 13)"
+ };
+ retArray =
+ new String[] {
+ "1,abcd,abcd,abcd,abcd,abcd,abcd,",
+ "2,test,test,test,test,test,test,",
+ "3,abcdefg,abcdefg,abcdefg,abcdefg,abcdefg,abcdefg,",
+ };
+ resultSetEqualTest(
+ "select s1,change_points(s1),SUBSTRING(s1,-1),SUBSTRING(s1,-1,13),SUBSTRING(s1 from -1),SUBSTRING(s1 from -1 for 13) from root.sg",
+ expectedHeader,
+ retArray);
+ }
+
+ @Test
+ public void testRoundBooleanAndText() {
+ // using substring without start and end position.
+ assertTestFail(
+ "select s1,SUBSTRING(s1) from root.sg",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Argument exception,the scalar function [SUBSTRING] needs at least one argument,it must be a signed integer");
+
+ // wrong input type
+ assertTestFail(
+ "select SUBSTRING(s2,1,1) from root.**",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Input series of Scalar function [SUBSTRING] only supports numeric data types [TEXT]");
+
+ // wrong input type
+ assertTestFail(
+ "select SUBSTRING(s3,1,1) from root.**",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Input series of Scalar function [SUBSTRING] only supports numeric data types [TEXT]");
+
+ // wrong input type
+ assertTestFail(
+ "select SUBSTRING(s4,1,1) from root.**",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Input series of Scalar function [SUBSTRING] only supports numeric data types [TEXT]");
+
+ // wrong input type
+ assertTestFail(
+ "select SUBSTRING(s5,1,1) from root.**",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Input series of Scalar function [SUBSTRING] only supports numeric data types [TEXT]");
+
+ // wrong input type
+ assertTestFail(
+ "select SUBSTRING(s6,1,1) from root.**",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Input series of Scalar function [SUBSTRING] only supports numeric data types [TEXT]");
+
+ // using substring with float start position
+ assertTestFail(
+ "select SUBSTRING(s1,1.0,1) from root.**",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Argument exception,the scalar function [SUBSTRING] needs at least one argument,it must be a signed integer");
+
+ // using substring with float start and length
+ assertTestFail(
+ "select SUBSTRING(s1,1.0,1.1) from root.**",
+ TSStatusCode.SEMANTIC_ERROR.getStatusCode()
+ + ": Argument exception,the scalar function [SUBSTRING] needs at least one argument,it must be a signed integer");
+
+ // negative characters length
+ assertTestFail(
+ "select SUBSTRING(s1,1,-10) from root.**",
+ TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode()
+ + ": Argument exception,the scalar function [SUBSTRING] substring length has to be greater than 0");
+ }
+}
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/udf/IoTDBUDTFBuiltinScalarFunctionIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/udf/IoTDBUDTFBuiltinScalarFunctionIT.java
index 51f894438d..3d63fdd27f 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/udf/IoTDBUDTFBuiltinScalarFunctionIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/udf/IoTDBUDTFBuiltinScalarFunctionIT.java
@@ -1131,10 +1131,9 @@ public class IoTDBUDTFBuiltinScalarFunctionIT {
private void testSubStr(Statement statement) throws SQLException {
ResultSet resultSet =
statement.executeQuery(
- "select s1, substr(s1, \"start\"=\"3\", \"end\"=\"7\") "
- + "from root.testStringFunctions.d1");
+ "select s1, substring(s1, 3, 7) " + "from root.testStringFunctions.d1");
while (resultSet.next()) {
- assertEquals(resultSet.getString(2).substring(3, 7), resultSet.getString(3));
+ assertEquals(resultSet.getString(2).substring(2, 9), resultSet.getString(3));
}
}
diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinScalarFunction.java b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinScalarFunction.java
index d42ad6e39b..eaa41f7f39 100644
--- a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinScalarFunction.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinScalarFunction.java
@@ -31,6 +31,7 @@ public enum BuiltinScalarFunction {
CAST("cast"),
ROUND("round"),
REPLACE("replace"),
+ SUBSTRING("substring"),
;
private final String functionName;
diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinTimeSeriesGeneratingFunction.java b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinTimeSeriesGeneratingFunction.java
index d790d9aeba..bf7ddd58f2 100644
--- a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinTimeSeriesGeneratingFunction.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/BuiltinTimeSeriesGeneratingFunction.java
@@ -26,7 +26,6 @@ import org.apache.iotdb.commons.udf.builtin.String.UDTFStartsWith;
import org.apache.iotdb.commons.udf.builtin.String.UDTFStrCompare;
import org.apache.iotdb.commons.udf.builtin.String.UDTFStrLength;
import org.apache.iotdb.commons.udf.builtin.String.UDTFStrLocate;
-import org.apache.iotdb.commons.udf.builtin.String.UDTFSubstr;
import org.apache.iotdb.commons.udf.builtin.String.UDTFTrim;
import org.apache.iotdb.commons.udf.builtin.String.UDTFUpper;
@@ -61,7 +60,6 @@ public enum BuiltinTimeSeriesGeneratingFunction {
STRING_STARTS_WITH("STARTSWITH", UDTFStartsWith.class),
STRING_ENDS_WITH("ENDSWITH", UDTFEndsWith.class),
STRING_CONCAT("CONCAT", UDTFConcat.class),
- STRING_SUBSTR("SUBSTR", UDTFSubstr.class),
STRING_UPPER("UPPER", UDTFUpper.class),
STRING_LOWER("LOWER", UDTFLower.class),
STRING_TRIM("TRIM", UDTFTrim.class),
diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFSubstr.java b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFSubstr.java
deleted file mode 100644
index 5fb5df9d20..0000000000
--- a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFSubstr.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.iotdb.commons.udf.builtin.String;
-
-import org.apache.iotdb.udf.api.UDTF;
-import org.apache.iotdb.udf.api.access.Row;
-import org.apache.iotdb.udf.api.collector.PointCollector;
-import org.apache.iotdb.udf.api.customizer.config.UDTFConfigurations;
-import org.apache.iotdb.udf.api.customizer.parameter.UDFParameterValidator;
-import org.apache.iotdb.udf.api.customizer.parameter.UDFParameters;
-import org.apache.iotdb.udf.api.customizer.strategy.RowByRowAccessStrategy;
-import org.apache.iotdb.udf.api.type.Type;
-
-/*This function return a substring from target string, starting at position start and ending at position end - 1.
-If parameter "end" is not existed or more than length of target, return the substring from start to end of target.*/
-public class UDTFSubstr implements UDTF {
-
- int start;
- int end;
-
- @Override
- public void validate(UDFParameterValidator validator) throws Exception {
- int start = validator.getParameters().getInt("start");
- validator
- .validateInputSeriesNumber(1)
- .validateInputSeriesDataType(0, Type.TEXT)
- .validate(
- startPosition -> ((int) startPosition) >= 0,
- "start should be more or equal than 0",
- start)
- .validate(
- end -> ((int) end) >= start,
- "end should be more or equal than start",
- validator.getParameters().getIntOrDefault("end", Integer.MAX_VALUE));
- }
-
- @Override
- public void beforeStart(UDFParameters parameters, UDTFConfigurations configurations)
- throws Exception {
- start = parameters.getInt("start");
- end = parameters.getIntOrDefault("end", Integer.MAX_VALUE);
- configurations.setAccessStrategy(new RowByRowAccessStrategy()).setOutputDataType(Type.TEXT);
- }
-
- @Override
- public void transform(Row row, PointCollector collector) throws Exception {
- String series = row.getString(0);
- collector.putString(
- row.getTime(),
- (end >= series.length())
- ? row.getString(0).substring(start)
- : row.getString(0).substring(start, end));
- }
-}
diff --git a/server/src/main/java/org/apache/iotdb/db/constant/SqlConstant.java b/server/src/main/java/org/apache/iotdb/db/constant/SqlConstant.java
index 68a3e8f145..4f18776b6b 100644
--- a/server/src/main/java/org/apache/iotdb/db/constant/SqlConstant.java
+++ b/server/src/main/java/org/apache/iotdb/db/constant/SqlConstant.java
@@ -70,6 +70,14 @@ public class SqlConstant {
public static final String REPLACE_FROM = "FROM";
public static final String REPLACE_TO = "TO";
+ public static final String SUBSTRING_FUNCTION = "SUBSTRING";
+ public static final String SUBSTRING_START = "startPosition";
+ public static final String SUBSTRING_LENGTH = "length";
+
+ public static final String SUBSTRING_FROM = "FROM";
+ public static final String SUBSTRING_IS_STANDARD = "isStandard";
+ public static final String SUBSTRING_FOR = "FOR";
+
public static String[] getSingleRootArray() {
return SINGLE_ROOT_ARRAY;
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/builtin/BuiltInScalarFunctionHelperFactory.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/builtin/BuiltInScalarFunctionHelperFactory.java
index f47128b81e..4726c88a2c 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/builtin/BuiltInScalarFunctionHelperFactory.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/builtin/BuiltInScalarFunctionHelperFactory.java
@@ -23,6 +23,7 @@ import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.helper.CastFunction
import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.helper.DiffFunctionHelper;
import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.helper.ReplaceFunctionHelper;
import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.helper.RoundFunctionHelper;
+import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.helper.SubStringFunctionHelper;
public class BuiltInScalarFunctionHelperFactory {
public static BuiltInScalarFunctionHelper createHelper(String functionName) {
@@ -34,6 +35,8 @@ public class BuiltInScalarFunctionHelperFactory {
return new CastFunctionHelper();
case "REPLACE":
return new ReplaceFunctionHelper();
+ case "SUBSTRING":
+ return new SubStringFunctionHelper();
case "ROUND":
return new RoundFunctionHelper();
default:
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/builtin/helper/SubStringFunctionHelper.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/builtin/helper/SubStringFunctionHelper.java
new file mode 100644
index 0000000000..b6b8ce41fa
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/builtin/helper/SubStringFunctionHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.mpp.plan.expression.multi.builtin.helper;
+
+import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.BuiltInScalarFunctionHelper;
+import org.apache.iotdb.db.mpp.transformation.api.LayerPointReader;
+import org.apache.iotdb.db.mpp.transformation.dag.column.ColumnTransformer;
+import org.apache.iotdb.db.mpp.transformation.dag.column.unary.scalar.SubStringFunctionColumnTransformer;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.Transformer;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.unary.scalar.SubStringFunctionTransformer;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.common.type.TypeFactory;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_FOR;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_FROM;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_IS_STANDARD;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_LENGTH;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_START;
+
+public class SubStringFunctionHelper implements BuiltInScalarFunctionHelper {
+ public static final String BLANK_STRING = " ";
+ public static final String COMMA_STRING = ",";
+
+ @Override
+ public void checkBuiltInScalarFunctionInputSize(FunctionExpression functionExpression)
+ throws SemanticException {
+ if (functionExpression.getFunctionAttributes().isEmpty()
+ || functionExpression.getExpressions().size() != 1) {
+ throw new SemanticException(
+ "Argument exception,the scalar function [SUBSTRING] needs at least one argument,it must be a signed integer");
+ }
+ }
+
+ @Override
+ public void checkBuiltInScalarFunctionInputDataType(TSDataType tsDataType)
+ throws SemanticException {
+ if (tsDataType.getType() == TSDataType.TEXT.getType()) {
+ return;
+ }
+ throw new SemanticException(
+ "Input series of Scalar function [SUBSTRING] only supports numeric data types [TEXT]");
+ }
+
+ @Override
+ public TSDataType getBuiltInScalarFunctionReturnType(FunctionExpression functionExpression) {
+ return TSDataType.TEXT;
+ }
+
+ @Override
+ public ColumnTransformer getBuiltInScalarFunctionColumnTransformer(
+ FunctionExpression expression, ColumnTransformer columnTransformer) {
+ LinkedHashMap<String, String> functionAttributes = expression.getFunctionAttributes();
+ String subStringLength =
+ functionAttributes.getOrDefault(SUBSTRING_LENGTH, String.valueOf(Integer.MAX_VALUE));
+ if (Long.parseLong(subStringLength) < 0) {
+ throw new SemanticException(
+ "Argument exception,the scalar function [SUBSTRING] substring length has to be greater than 0");
+ }
+
+ return new SubStringFunctionColumnTransformer(
+ TypeFactory.getType(this.getBuiltInScalarFunctionReturnType(expression)),
+ columnTransformer,
+ Integer.parseInt(functionAttributes.getOrDefault(SUBSTRING_START, "0")),
+ Integer.parseInt(subStringLength));
+ }
+
+ @Override
+ public Transformer getBuiltInScalarFunctionTransformer(
+ FunctionExpression expression, LayerPointReader layerPointReader) {
+ LinkedHashMap<String, String> functionAttributes = expression.getFunctionAttributes();
+ String subStringLength =
+ functionAttributes.getOrDefault(SUBSTRING_LENGTH, String.valueOf(Integer.MAX_VALUE));
+ if (Long.parseLong(subStringLength) < 0) {
+ throw new SemanticException(
+ "Argument exception,the scalar function [SUBSTRING] substring length has to be greater than 0");
+ }
+ return new SubStringFunctionTransformer(
+ layerPointReader,
+ Integer.parseInt(functionAttributes.getOrDefault(SUBSTRING_START, "0")),
+ Integer.parseInt(subStringLength));
+ }
+
+ @Override
+ public void appendFunctionAttributes(
+ boolean hasExpression, StringBuilder builder, Map<String, String> functionAttributes) {
+
+ if (functionAttributes.containsKey(SUBSTRING_IS_STANDARD)) {
+ builder
+ .append(BLANK_STRING)
+ .append(SUBSTRING_FROM)
+ .append(BLANK_STRING)
+ .append(functionAttributes.get(SUBSTRING_START));
+ if (functionAttributes.containsKey(SUBSTRING_LENGTH)) {
+ builder
+ .append(BLANK_STRING)
+ .append(SUBSTRING_FOR)
+ .append(BLANK_STRING)
+ .append(functionAttributes.get(SUBSTRING_LENGTH));
+ }
+ } else {
+ builder.append(COMMA_STRING).append(functionAttributes.get(SUBSTRING_START));
+ if (functionAttributes.containsKey(SUBSTRING_LENGTH)) {
+ builder.append(COMMA_STRING).append(functionAttributes.get(SUBSTRING_LENGTH));
+ }
+ }
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
index 463559c02c..7515f4076d 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
@@ -214,6 +214,10 @@ import static org.apache.iotdb.db.constant.SqlConstant.REPLACE_FUNCTION;
import static org.apache.iotdb.db.constant.SqlConstant.REPLACE_TO;
import static org.apache.iotdb.db.constant.SqlConstant.ROUND_FUNCTION;
import static org.apache.iotdb.db.constant.SqlConstant.ROUND_PLACES;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_FUNCTION;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_IS_STANDARD;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_LENGTH;
+import static org.apache.iotdb.db.constant.SqlConstant.SUBSTRING_START;
import static org.apache.iotdb.db.metadata.MetadataConstant.ALL_RESULT_NODES;
/** Parse AST to Statement. */
@@ -2370,6 +2374,8 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
return parseReplaceFunction(context, canUseFullPath);
} else if (context.ROUND() != null) {
return parseRoundFunction(context, canUseFullPath);
+ } else if (context.SUBSTRING() != null) {
+ return parseSubStrFunction(context, canUseFullPath);
}
throw new UnsupportedOperationException();
}
@@ -2391,6 +2397,29 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
return functionExpression;
}
+ private Expression parseSubStrFunction(
+ IoTDBSqlParser.ScalarFunctionExpressionContext subStrClause, boolean canUseFullPath) {
+ FunctionExpression functionExpression = new FunctionExpression(SUBSTRING_FUNCTION);
+ IoTDBSqlParser.SubStringExpressionContext subStringExpression =
+ subStrClause.subStringExpression();
+ functionExpression.addExpression(parseExpression(subStringExpression.input, canUseFullPath));
+ if (subStringExpression.startPosition != null) {
+ functionExpression.addAttribute(SUBSTRING_START, subStringExpression.startPosition.getText());
+ if (subStringExpression.length != null) {
+ functionExpression.addAttribute(SUBSTRING_LENGTH, subStringExpression.length.getText());
+ }
+ }
+ if (subStringExpression.from != null) {
+ functionExpression.addAttribute(SUBSTRING_IS_STANDARD, "0");
+ functionExpression.addAttribute(
+ SUBSTRING_START, parseStringLiteral(subStringExpression.from.getText()));
+ if (subStringExpression.forLength != null) {
+ functionExpression.addAttribute(SUBSTRING_LENGTH, subStringExpression.forLength.getText());
+ }
+ }
+ return functionExpression;
+ }
+
private Expression parseRoundFunction(
IoTDBSqlParser.ScalarFunctionExpressionContext roundClause, boolean canUseFullPath) {
FunctionExpression functionExpression = new FunctionExpression(ROUND_FUNCTION);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/column/unary/scalar/SubStringFunctionColumnTransformer.java b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/column/unary/scalar/SubStringFunctionColumnTransformer.java
new file mode 100644
index 0000000000..d04a406ab4
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/column/unary/scalar/SubStringFunctionColumnTransformer.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.mpp.transformation.dag.column.unary.scalar;
+
+import org.apache.iotdb.db.mpp.transformation.dag.column.ColumnTransformer;
+import org.apache.iotdb.db.mpp.transformation.dag.column.unary.UnaryColumnTransformer;
+import org.apache.iotdb.tsfile.read.common.block.column.Column;
+import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
+import org.apache.iotdb.tsfile.read.common.type.Type;
+import org.apache.iotdb.tsfile.utils.Binary;
+
+public class SubStringFunctionColumnTransformer extends UnaryColumnTransformer {
+
+ private int beginPosition;
+ private int endPosition;
+ public static final String EMPTY_STRING = "";
+
+ public SubStringFunctionColumnTransformer(
+ Type returnType, ColumnTransformer childColumnTransformer, int beginPosition, int length) {
+ super(returnType, childColumnTransformer);
+ this.endPosition =
+ (length == Integer.MAX_VALUE ? Integer.MAX_VALUE : beginPosition + length - 1);
+ this.beginPosition = beginPosition > 0 ? beginPosition - 1 : 0;
+ }
+
+ @Override
+ protected void doTransform(Column column, ColumnBuilder columnBuilder) {
+ for (int i = 0, n = column.getPositionCount(); i < n; i++) {
+ if (!column.isNull(i)) {
+ String currentValue = column.getBinary(i).getStringValue();
+ if (beginPosition >= currentValue.length() || endPosition < 0) {
+ currentValue = EMPTY_STRING;
+ } else {
+ if (endPosition >= currentValue.length()) {
+ currentValue = currentValue.substring(beginPosition);
+ } else {
+ currentValue = currentValue.substring(beginPosition, endPosition);
+ }
+ }
+ columnBuilder.writeBinary(Binary.valueOf(currentValue));
+ } else {
+ columnBuilder.appendNull();
+ }
+ }
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/unary/scalar/SubStringFunctionTransformer.java b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/unary/scalar/SubStringFunctionTransformer.java
new file mode 100644
index 0000000000..6786968431
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/unary/scalar/SubStringFunctionTransformer.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.mpp.transformation.dag.transformer.unary.scalar;
+
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.mpp.transformation.api.LayerPointReader;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.unary.UnaryTransformer;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.utils.Binary;
+
+import java.io.IOException;
+
+import static org.apache.iotdb.db.mpp.transformation.dag.column.unary.scalar.SubStringFunctionColumnTransformer.EMPTY_STRING;
+
+public class SubStringFunctionTransformer extends UnaryTransformer {
+ private int beginPosition;
+ private int endPosition;
+
+ public SubStringFunctionTransformer(
+ LayerPointReader layerPointReader, int beginPosition, int length) {
+ super(layerPointReader);
+ this.endPosition =
+ (length == Integer.MAX_VALUE ? Integer.MAX_VALUE : beginPosition + length - 1);
+ this.beginPosition = beginPosition > 0 ? beginPosition - 1 : 0;
+ }
+
+ @Override
+ public TSDataType getDataType() {
+ return TSDataType.TEXT;
+ }
+
+ @Override
+ protected void transformAndCache() throws QueryProcessException, IOException {
+ String currentValue = layerPointReader.currentBinary().getStringValue();
+ if (beginPosition >= currentValue.length() || endPosition < 0) {
+ currentValue = EMPTY_STRING;
+ } else {
+ if (endPosition >= currentValue.length()) {
+ currentValue = currentValue.substring(beginPosition);
+ } else {
+ currentValue = currentValue.substring(beginPosition, endPosition);
+ }
+ }
+ cachedBinary = Binary.valueOf(currentValue);
+ }
+}