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

qiaojialin 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 d976dcf530 [IOTDB-3434] Implement the Built-in Functions of String 
(#6250)
d976dcf530 is described below

commit d976dcf5301990fe935f12dc44f8a49ed027b151
Author: Weihao Li <[email protected]>
AuthorDate: Tue Jun 14 19:46:38 2022 +0800

    [IOTDB-3434] Implement the Built-in Functions of String (#6250)
---
 docs/UserGuide/UDF-Library/String-Processing.md    |  656 +++++++++--
 docs/zh/UserGuide/UDF-Library/String-Processing.md |  652 +++++++++--
 .../iotdb/db/it/IoTDBUDTFBuiltinFunctionIT.java    | 1230 ++++++++++++++++++++
 .../BuiltinTimeSeriesGeneratingFunction.java       |   21 +
 .../commons/udf/builtin/String/UDTFConcat.java     |   76 ++
 .../commons/udf/builtin/String/UDTFEndsWith.java   |   53 +
 .../commons/udf/builtin/String/UDTFLower.java      |   50 +
 .../commons/udf/builtin/String/UDTFStartsWith.java |   52 +
 .../commons/udf/builtin/String/UDTFStrCompare.java |   57 +
 .../commons/udf/builtin/String/UDTFStrLength.java  |   50 +
 .../commons/udf/builtin/String/UDTFStrLocate.java  |   62 +
 .../commons/udf/builtin/String/UDTFSubstr.java     |   72 ++
 .../iotdb/commons/udf/builtin/String/UDTFTrim.java |   50 +
 .../commons/udf/builtin/String/UDTFUpper.java      |   50 +
 14 files changed, 2974 insertions(+), 157 deletions(-)

diff --git a/docs/UserGuide/UDF-Library/String-Processing.md 
b/docs/UserGuide/UDF-Library/String-Processing.md
index f339369692..5d3e4e9cf3 100644
--- a/docs/UserGuide/UDF-Library/String-Processing.md
+++ b/docs/UserGuide/UDF-Library/String-Processing.md
@@ -21,6 +21,583 @@
 
 # String Processing
 
+## Length
+
+### Usage
+
+The function is used to get the length of input series.
+
+**Name:** LENGTH
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Output Series:** Output a single series. The type is INT32.
+
+**Note:** Returns NULL if input is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, length(s1) from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+----------------------+
+|                         Time|root.sg1.d1.s1|length(root.sg1.d1.s1)|
++-----------------------------+--------------+----------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                     6|
+|1970-01-01T08:00:00.002+08:00|      22test22|                     8|
++-----------------------------+--------------+----------------------+
+```
+
+## StrLocate
+
+### Usage
+
+The function is used to get the position of the first occurrence of substring 
`target` in input series. Returns -1 if there are no `target` in input.
+
+**Name:** LENGTH
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Parameter:**
+
++ `target`: The substring to be located.
++ `reverse`: Indicates whether reverse locate is required. The default value 
is `false`, means left-to-right locate.
+
+**Output Series:** Output a single series. The type is INT32.
+
+**Note:** The index begins from 0.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, locate(s1, "target"="1") from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+------------------------------------+
+|                         Time|root.sg1.d1.s1|locate(root.sg1.d1.s1, 
"target"="1")|
++-----------------------------+--------------+------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
  0|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
 -1|
++-----------------------------+--------------+------------------------------------+
+```
+
+Another SQL for query:
+
+```sql
+select s1, locate(s1, "target"="1", "reverse"="true") from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+------------------------------------------------------+
+|                         Time|root.sg1.d1.s1|locate(root.sg1.d1.s1, 
"target"="1", "reverse"="true")|
++-----------------------------+--------------+------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
                    5|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
                   -1|
++-----------------------------+--------------+------------------------------------------------------+
+```
+
+## StartsWith
+
+### Usage
+
+The function is used to check whether input series starts with the specified 
prefix.
+
+**Name:** STARTSWITH
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Parameter:**
++ `target`: The prefix to be checked.
+
+**Output Series:** Output a single series. The type is BOOLEAN.
+
+**Note:** Returns NULL if input is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, startswith(s1, "target"="1") from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+----------------------------------------+
+|                         Time|root.sg1.d1.s1|startswith(root.sg1.d1.s1, 
"target"="1")|
++-----------------------------+--------------+----------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
   true|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
  false|
++-----------------------------+--------------+----------------------------------------+
+```
+
+## EndsWith
+
+### Usage
+
+The function is used to check whether input series ends with the specified 
suffix.
+
+**Name:** ENDSWITH
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Parameter:**
++ `target`: The suffix to be checked.
+
+**Output Series:** Output a single series. The type is BOOLEAN.
+
+**Note:** Returns NULL if input is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, endswith(s1, "target"="1") from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+--------------------------------------+
+|                         Time|root.sg1.d1.s1|endswith(root.sg1.d1.s1, 
"target"="1")|
++-----------------------------+--------------+--------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
 true|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
false|
++-----------------------------+--------------+--------------------------------------+
+```
+
+## Concat
+
+### Usage
+
+The function is used to concat input series and target strings.
+
+**Name:** CONCAT
+
+**Input Series:** At least one input series. The data type is TEXT.
+
+**Parameter:**
++ `targets`: A series of K-V, key needs to start with `target` and be not 
duplicated, value is the string you want to concat.
++ `series_behind`: Indicates whether series behind targets. The default value 
is `false`.
+
+**Output Series:** Output a single series. The type is TEXT.
+
+**Note:** 
++ If value of input series is NULL, it will be skipped.
++ We can only concat input series and `targets` separately. `concat(s1, 
"target1"="IoT", s2, "target2"="DB")` and
+  `concat(s1, s2, "target1"="IoT", "target2"="DB")` gives the same result.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+--------------+
+|                         Time|root.sg1.d1.s1|root.sg1.d1.s2|
++-----------------------------+--------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|
++-----------------------------+--------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, s2, concat(s1, s2, "target1"="IoT", "target2"="DB") from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------+
+|                         
Time|root.sg1.d1.s1|root.sg1.d1.s2|concat(root.sg1.d1.s1, root.sg1.d1.s2, 
"target1"="IoT", "target2"="DB")|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|                  
                                          1test1IoTDB|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|                  
                                22test222222testIoTDB|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------+
+```
+
+Another SQL for query:
+
+```sql
+select s1, s2, concat(s1, s2, "target1"="IoT", "target2"="DB", 
"series_behind"="true") from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
+|                         
Time|root.sg1.d1.s1|root.sg1.d1.s2|concat(root.sg1.d1.s1, root.sg1.d1.s2, 
"target1"="IoT", "target2"="DB", "series_behind"="true")|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|                  
                                                                  IoTDB1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|                  
                                                        IoTDB22test222222test|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
+```
+
+## Substr
+
+### Usage
+
+The function is used to get the substring `start` to `end - 1`. 
+
+**Name:** SUBSTR
+
+**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.
+
+**Output Series:** Output a single series. The type is TEXT.
+
+**Note:** Returns NULL if input is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, substr(s1, "start"="0", "end"="2") from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+----------------------------------------------+
+|                         Time|root.sg1.d1.s1|substr(root.sg1.d1.s1, 
"start"="0", "end"="2")|
++-----------------------------+--------------+----------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
           1t|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
           22|
++-----------------------------+--------------+----------------------------------------------+
+```
+
+## Upper
+
+### Usage
+
+The function is used to get the string of input series with all characters 
changed to uppercase.
+
+**Name:** UPPER
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Output Series:** Output a single series. The type is TEXT.
+
+**Note:** Returns NULL if input is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, upper(s1) from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+---------------------+
+|                         Time|root.sg1.d1.s1|upper(root.sg1.d1.s1)|
++-----------------------------+--------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|               1TEST1|
+|1970-01-01T08:00:00.002+08:00|      22test22|             22TEST22|
++-----------------------------+--------------+---------------------+
+```
+
+## Lower
+
+### Usage
+
+The function is used to get the string of input series with all characters 
changed to lowercase.
+
+**Name:** LOWER
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Output Series:** Output a single series. The type is TEXT.
+
+**Note:** Returns NULL if input is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1TEST1|
+|1970-01-01T08:00:00.002+08:00|      22TEST22|
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, lower(s1) from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+---------------------+
+|                         Time|root.sg1.d1.s1|lower(root.sg1.d1.s1)|
++-----------------------------+--------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|        1TEST1|               1test1|
+|1970-01-01T08:00:00.002+08:00|      22TEST22|             22test22|
++-----------------------------+--------------+---------------------+
+```
+
+## Trim
+
+### Usage
+
+The function is used to get the string whose value is same to input series, 
with all leading and trailing space removed.
+
+**Name:** TRIM
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Output Series:** Output a single series. The type is TEXT.
+
+**Note:** Returns NULL if input is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s3|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.002+08:00|   3querytest3|
+|1970-01-01T08:00:00.003+08:00|  3querytest3 |
++-----------------------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s3, trim(s3) from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+--------------------+
+|                         Time|root.sg1.d1.s3|trim(root.sg1.d1.s3)|
++-----------------------------+--------------+--------------------+
+|1970-01-01T08:00:00.002+08:00|   3querytest3|         3querytest3|
+|1970-01-01T08:00:00.003+08:00|  3querytest3 |         3querytest3|
++-----------------------------+--------------+--------------------+
+```
+
+## StrCmp
+
+### Usage
+
+The function is used to get the compare result of two input series. Returns 
`0` if series value are the same, a `negative integer` if value of series1 is 
smaller than series2, 
+a `positive integer` if value of series1  is more than series2.
+
+**Name:** StrCmp
+
+**Input Series:** Support two input series. Data types are all the TEXT.
+
+**Output Series:** Output a single series. The type is INT32.
+
+**Note:** Returns NULL either series value is NULL.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+--------------+--------------+
+|                         Time|root.sg1.d1.s1|root.sg1.d1.s2|
++-----------------------------+--------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|
++-----------------------------+--------------+--------------+
+```
+
+SQL for query:
+
+```sql
+select s1, s2, strcmp(s1, s2) from root.sg1.d1
+```
+
+Output series:
+
+```
++-----------------------------+--------------+--------------+--------------------------------------+
+|                         
Time|root.sg1.d1.s1|root.sg1.d1.s2|strcmp(root.sg1.d1.s1, root.sg1.d1.s2)|
++-----------------------------+--------------+--------------+--------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|                  
                null|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|                  
                  66|
++-----------------------------+--------------+--------------+--------------------------------------+
+```
+
+
+## StrReplace
+
+### Usage
+
+The function is used to replace the specific substring with given string.
+
+**Name:** STRREPLACE
+
+**Input Series:** Only support a single input series. The data type is TEXT.
+
+**Parameter:**
+
++ `target`: The target substring to be replaced.
++ `replace`: The string to be put on.
++ `limit`: The number of matches to be replaced which should be an integer no 
less than -1,
+  default to -1 which means all matches will be replaced.
++ `offset`: The number of matches to be skipped, which means the first 
`offset` matches will not be replaced, default to 0.
++ `reverse`: Whether to count all the matches reversely, default to 'false'.
+
+**Output Series:** Output a single series. The type is TEXT.
+
+### Examples
+
+Input series:
+
+```
++-----------------------------+---------------+
+|                         Time|root.test.d1.s1|
++-----------------------------+---------------+
+|2021-01-01T00:00:01.000+08:00|      A,B,A+,B-|
+|2021-01-01T00:00:02.000+08:00|      A,A+,A,B+|
+|2021-01-01T00:00:03.000+08:00|         B+,B,B|
+|2021-01-01T00:00:04.000+08:00|      A+,A,A+,A|
+|2021-01-01T00:00:05.000+08:00|       A,B-,B,B|
++-----------------------------+---------------+
+```
+
+SQL for query:
+
+```sql
+select strreplace(s1, "target"=",", "replace"="/", "limit"="2") from 
root.test.d1
+```
+
+Output series:
+
+```
++-----------------------------+-----------------------------------------+
+|                         Time|strreplace(root.test.d1.s1, "target"=",",|
+|                             |              "replace"="/", "limit"="2")|
++-----------------------------+-----------------------------------------+
+|2021-01-01T00:00:01.000+08:00|                                A/B/A+,B-|
+|2021-01-01T00:00:02.000+08:00|                                A/A+/A,B+|
+|2021-01-01T00:00:03.000+08:00|                                   B+/B/B|
+|2021-01-01T00:00:04.000+08:00|                                A+/A/A+,A|
+|2021-01-01T00:00:05.000+08:00|                                 A/B-/B,B|
++-----------------------------+-----------------------------------------+
+```
+
+Another SQL for query:
+
+```sql
+select strreplace(s1, "target"=",", "replace"="/", "limit"="1", "offset"="1", 
"reverse"="true") from root.test.d1
+```
+
+Output series:
+
+```
++-----------------------------+-----------------------------------------------------+
+|                         Time|strreplace(root.test.d1.s1, "target"=",", 
"replace"= | 
+|                             |    "|", "limit"="1", "offset"="1", 
"reverse"="true")|
++-----------------------------+-----------------------------------------------------+
+|2021-01-01T00:00:01.000+08:00|                                            
A,B/A+,B-|
+|2021-01-01T00:00:02.000+08:00|                                            
A,A+/A,B+|
+|2021-01-01T00:00:03.000+08:00|                                               
B+/B,B|
+|2021-01-01T00:00:04.000+08:00|                                            
A+,A/A+,A|
+|2021-01-01T00:00:05.000+08:00|                                             
A,B-/B,B|
++-----------------------------+-----------------------------------------------------+
+```
+
 ## RegexMatch
 
 ### Usage
@@ -217,83 +794,4 @@ Output series:
 |2021-01-01T00:00:04.000+08:00|                                                
    A|
 |2021-01-01T00:00:05.000+08:00|                                                
    B|
 
+-----------------------------+-----------------------------------------------------+
-```
-
-## StrReplace
-
-### Usage
-
-The function is used to replace the specific substring with given string.
-
-**Name:** STRREPLACE
-
-**Input Series:** Only support a single input series. The data type is TEXT.
-
-**Parameter:**
-
-+ `target`: The target substring to be replaced.
-+ `replace`: The string to be put on.
-+ `limit`: The number of matches to be replaced which should be an integer no 
less than -1,
-  default to -1 which means all matches will be replaced.
-+ `offset`: The number of matches to be skipped, which means the first 
`offset` matches will not be replaced, default to 0.
-+ `reverse`: Whether to count all the matches reversely, default to 'false'.
-
-**Output Series:** Output a single series. The type is TEXT.
-
-### Examples
-
-Input series:
-
-```
-+-----------------------------+---------------+
-|                         Time|root.test.d1.s1|
-+-----------------------------+---------------+
-|2021-01-01T00:00:01.000+08:00|      A,B,A+,B-|
-|2021-01-01T00:00:02.000+08:00|      A,A+,A,B+|
-|2021-01-01T00:00:03.000+08:00|         B+,B,B|
-|2021-01-01T00:00:04.000+08:00|      A+,A,A+,A|
-|2021-01-01T00:00:05.000+08:00|       A,B-,B,B|
-+-----------------------------+---------------+
-```
-
-SQL for query:
-
-```sql
-select strreplace(s1, "target"=",", "replace"="/", "limit"="2") from 
root.test.d1
-```
-
-Output series:
-
-```
-+-----------------------------+-----------------------------------------+
-|                         Time|strreplace(root.test.d1.s1, "target"=",",|
-|                             |              "replace"="/", "limit"="2")|
-+-----------------------------+-----------------------------------------+
-|2021-01-01T00:00:01.000+08:00|                                A/B/A+,B-|
-|2021-01-01T00:00:02.000+08:00|                                A/A+/A,B+|
-|2021-01-01T00:00:03.000+08:00|                                   B+/B/B|
-|2021-01-01T00:00:04.000+08:00|                                A+/A/A+,A|
-|2021-01-01T00:00:05.000+08:00|                                 A/B-/B,B|
-+-----------------------------+-----------------------------------------+
-```
-
-Another SQL for query:
-
-```sql
-select strreplace(s1, "target"=",", "replace"="/", "limit"="1", "offset"="1", 
"reverse"="true") from root.test.d1
-```
-
-Output series:
-
-```
-+-----------------------------+-----------------------------------------------------+
-|                         Time|strreplace(root.test.d1.s1, "target"=",", 
"replace"= | 
-|                             |    "|", "limit"="1", "offset"="1", 
"reverse"="true")|
-+-----------------------------+-----------------------------------------------------+
-|2021-01-01T00:00:01.000+08:00|                                            
A,B/A+,B-|
-|2021-01-01T00:00:02.000+08:00|                                            
A,A+/A,B+|
-|2021-01-01T00:00:03.000+08:00|                                               
B+/B,B|
-|2021-01-01T00:00:04.000+08:00|                                            
A+,A/A+,A|
-|2021-01-01T00:00:05.000+08:00|                                             
A,B-/B,B|
-+-----------------------------+-----------------------------------------------------+
 ```
\ No newline at end of file
diff --git a/docs/zh/UserGuide/UDF-Library/String-Processing.md 
b/docs/zh/UserGuide/UDF-Library/String-Processing.md
index 2b5fec4e83..4ff0d7c2d6 100644
--- a/docs/zh/UserGuide/UDF-Library/String-Processing.md
+++ b/docs/zh/UserGuide/UDF-Library/String-Processing.md
@@ -21,6 +21,580 @@
 
 # 字符串处理
 
+## Length
+
+### 函数简介
+
+本函数用于获取输入序列的长度。
+
+**函数名:** LENGTH
+
+**输入序列:** 仅支持单个输入序列,类型为 TEXT。
+
+**输出序列:** 输出单个序列,类型为 INT32。
+
+**提示:** 如果输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, length(s1) from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+----------------------+
+|                         Time|root.sg1.d1.s1|length(root.sg1.d1.s1)|
++-----------------------------+--------------+----------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                     6|
+|1970-01-01T08:00:00.002+08:00|      22test22|                     8|
++-----------------------------+--------------+----------------------+
+```
+
+## StrLocate
+
+### 函数简介
+
+本函数用于获取`target`子串第一次出现在输入序列的位置,如果输入序列中不包含`target`则返回 -1 。
+
+**函数名:** LENGTH
+
+**输入序列:** 仅支持单个输入序列,类型为 TEXT。
+
+**参数:**
+
++ `target`: 需要被定位的子串。
++ `reverse`: 指定是否需要倒序定位,默认值为`false`, 即从左至右定位。
+
+**输出序列:** 输出单个序列,类型为INT32。
+
+**提示:** 下标从 0 开始。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, locate(s1, "target"="1") from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+------------------------------------+
+|                         Time|root.sg1.d1.s1|locate(root.sg1.d1.s1, 
"target"="1")|
++-----------------------------+--------------+------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
  0|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
 -1|
++-----------------------------+--------------+------------------------------------+
+```
+
+另一个用于查询的 SQL 语句:
+
+```sql
+select s1, locate(s1, "target"="1", "reverse"="true") from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+------------------------------------------------------+
+|                         Time|root.sg1.d1.s1|locate(root.sg1.d1.s1, 
"target"="1", "reverse"="true")|
++-----------------------------+--------------+------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
                    5|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
                   -1|
++-----------------------------+--------------+------------------------------------------------------+
+```
+
+## StartsWith
+
+### 函数简介
+
+本函数用于判断输入序列是否有指定前缀。
+
+**函数名:** STARTSWITH
+
+**输入序列:** 仅支持单个输入序列,类型为 TEXT。
+
+**参数:**
++ `target`: 需要匹配的前缀。
+
+**输出序列:** 输出单个序列,类型为 BOOLEAN。
+
+**提示:** 如果输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, startswith(s1, "target"="1") from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+----------------------------------------+
+|                         Time|root.sg1.d1.s1|startswith(root.sg1.d1.s1, 
"target"="1")|
++-----------------------------+--------------+----------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
   true|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
  false|
++-----------------------------+--------------+----------------------------------------+
+```
+
+## EndsWith
+
+### 函数简介
+
+本函数用于判断输入序列是否有指定后缀。
+
+**函数名:** ENDSWITH
+
+**输入序列:** 仅支持单个输入序列,类型为 TEXT。
+
+**参数:**
++ `target`: 需要匹配的后缀。
+
+**输出序列:** 输出单个序列,类型为 BOOLEAN。
+
+**提示:** 如果输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, endswith(s1, "target"="1") from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+--------------------------------------+
+|                         Time|root.sg1.d1.s1|endswith(root.sg1.d1.s1, 
"target"="1")|
++-----------------------------+--------------+--------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
 true|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
false|
++-----------------------------+--------------+--------------------------------------+
+```
+
+## Concat
+
+### 函数简介
+
+本函数用于拼接输入序列和`target`字串。
+
+**函数名:** CONCAT
+
+**输入序列:** 至少一个输入序列,类型为 TEXT。
+
+**参数:**
++ `targets`: 一系列 K-V, key需要以`target`为前缀且不重复, value是待拼接的字符串。
++ `series_behind`: 指定拼接时时间序列是否在后面,默认为`false`。
+
+**输出序列:** 输出单个序列,类型为 TEXT。
+
+**提示:**
++ 如果输入序列是NULL, 跳过该序列的拼接。
++ 函数只能将输入序列和`targets`区分开各自拼接。`concat(s1, "target1"="IoT", s2, "target2"="DB")`和
+  `concat(s1, s2, "target1"="IoT", "target2"="DB")`得到的结果是一样的。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+--------------+
+|                         Time|root.sg1.d1.s1|root.sg1.d1.s2|
++-----------------------------+--------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|
++-----------------------------+--------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, s2, concat(s1, s2, "target1"="IoT", "target2"="DB") from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------+
+|                         
Time|root.sg1.d1.s1|root.sg1.d1.s2|concat(root.sg1.d1.s1, root.sg1.d1.s2, 
"target1"="IoT", "target2"="DB")|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|                  
                                          1test1IoTDB|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|                  
                                22test222222testIoTDB|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------+
+```
+
+另一个用于查询的 SQL 语句:
+
+```sql
+select s1, s2, concat(s1, s2, "target1"="IoT", "target2"="DB", 
"series_behind"="true") from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
+|                         
Time|root.sg1.d1.s1|root.sg1.d1.s2|concat(root.sg1.d1.s1, root.sg1.d1.s2, 
"target1"="IoT", "target2"="DB", "series_behind"="true")|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|                  
                                                                  IoTDB1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|                  
                                                        IoTDB22test222222test|
++-----------------------------+--------------+--------------+-----------------------------------------------------------------------------------------------+
+```
+
+## Substr
+
+### 函数简介
+
+本函数用于获取下标从`start`到`end - 1`的子串
+
+**函数名:** SUBSTR
+
+**输入序列:** 仅支持单个输入序列,类型为TEXT。
+
+**参数:**
++ `start`: 指定子串开始下标。
++ `end`: 指定子串结束下标。
+
+**输出序列:** 输出单个序列,类型为 TEXT。
+
+**提示:** 如果输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, substr(s1, "start"="0", "end"="2") from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+----------------------------------------------+
+|                         Time|root.sg1.d1.s1|substr(root.sg1.d1.s1, 
"start"="0", "end"="2")|
++-----------------------------+--------------+----------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|                                 
           1t|
+|1970-01-01T08:00:00.002+08:00|      22test22|                                 
           22|
++-----------------------------+--------------+----------------------------------------------+
+```
+
+## Upper
+
+### 函数简介
+
+本函数用于将输入序列转化为大写。
+
+**函数名:** UPPER
+
+**输入序列:** 仅支持单个输入序列,类型为TEXT。
+
+**输出序列:** 输出单个序列,类型为 TEXT。
+
+**提示:** 如果输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|
+|1970-01-01T08:00:00.002+08:00|      22test22|
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, upper(s1) from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+---------------------+
+|                         Time|root.sg1.d1.s1|upper(root.sg1.d1.s1)|
++-----------------------------+--------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|               1TEST1|
+|1970-01-01T08:00:00.002+08:00|      22test22|             22TEST22|
++-----------------------------+--------------+---------------------+
+```
+
+## Lower
+
+### 函数简介
+
+本函数用于将输入序列转换为小写。
+
+**函数名:** LOWER
+
+**输入序列:** 仅支持单个输入序列,类型为TEXT。
+
+**输出序列:** 输出单个序列,类型为 TEXT。
+
+**提示:** 如果输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s1|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1TEST1|
+|1970-01-01T08:00:00.002+08:00|      22TEST22|
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, lower(s1) from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+---------------------+
+|                         Time|root.sg1.d1.s1|lower(root.sg1.d1.s1)|
++-----------------------------+--------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|        1TEST1|               1test1|
+|1970-01-01T08:00:00.002+08:00|      22TEST22|             22test22|
++-----------------------------+--------------+---------------------+
+```
+
+## Trim
+
+### 函数简介
+
+本函数用于移除输入序列前后的空格。
+
+**函数名:** TRIM
+
+**输入序列:** 仅支持单个输入序列,类型为TEXT。
+
+**输出序列:** 输出单个序列,类型为 TEXT。
+
+**提示:** 如果输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+
+|                         Time|root.sg1.d1.s3|
++-----------------------------+--------------+
+|1970-01-01T08:00:00.002+08:00|   3querytest3|
+|1970-01-01T08:00:00.003+08:00|  3querytest3 |
++-----------------------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s3, trim(s3) from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+--------------------+
+|                         Time|root.sg1.d1.s3|trim(root.sg1.d1.s3)|
++-----------------------------+--------------+--------------------+
+|1970-01-01T08:00:00.002+08:00|   3querytest3|         3querytest3|
+|1970-01-01T08:00:00.003+08:00|  3querytest3 |         3querytest3|
++-----------------------------+--------------+--------------------+
+```
+
+## StrCmp
+
+### 函数简介
+
+本函数用于比较两个输入序列。 如果值相同返回 `0` , 序列1的值小于序列2的值返回一个`负数`,序列1的值大于序列2的值返回一个`正数`。
+
+**函数名:** StrCmp
+
+**输入序列:** 输入两个序列,类型均为 TEXT。
+
+**输出序列:** 输出单个序列,类型为 TEXT。
+
+**提示:** 如果任何一个输入是NULL,返回NULL。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+--------------+--------------+
+|                         Time|root.sg1.d1.s1|root.sg1.d1.s2|
++-----------------------------+--------------+--------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|
++-----------------------------+--------------+--------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select s1, s2, strcmp(s1, s2) from root.sg1.d1
+```
+
+输出序列:
+
+```
++-----------------------------+--------------+--------------+--------------------------------------+
+|                         
Time|root.sg1.d1.s1|root.sg1.d1.s2|strcmp(root.sg1.d1.s1, root.sg1.d1.s2)|
++-----------------------------+--------------+--------------+--------------------------------------+
+|1970-01-01T08:00:00.001+08:00|        1test1|          null|                  
                null|
+|1970-01-01T08:00:00.002+08:00|      22test22|      2222test|                  
                  66|
++-----------------------------+--------------+--------------+--------------------------------------+
+```
+
+## StrReplace
+
+### 函数简介
+
+本函数用于将文本中的子串替换为指定的字符串。
+
+**函数名:** STRREPLACE
+
+**输入序列:** 仅支持单个输入序列,类型为 TEXT。
+
+**参数:**
+
++ `target`: 需要替换的字符子串
++ `replace`: 替换后的字符串。
++ `limit`: 替换次数,大于等于 -1 的整数,默认为 -1 表示所有匹配的子串都会被替换。
++ `offset`: 需要跳过的匹配次数,即前`offset`次匹配到的字符子串并不会被替换,默认为 0。
++ `reverse`: 是否需要反向计数,默认为 false 即按照从左向右的次序。
+
+**输出序列:** 输出单个序列,类型为 TEXT。
+
+### 使用示例
+
+输入序列:
+
+```
++-----------------------------+---------------+
+|                         Time|root.test.d1.s1|
++-----------------------------+---------------+
+|2021-01-01T00:00:01.000+08:00|      A,B,A+,B-|
+|2021-01-01T00:00:02.000+08:00|      A,A+,A,B+|
+|2021-01-01T00:00:03.000+08:00|         B+,B,B|
+|2021-01-01T00:00:04.000+08:00|      A+,A,A+,A|
+|2021-01-01T00:00:05.000+08:00|       A,B-,B,B|
++-----------------------------+---------------+
+```
+
+用于查询的 SQL 语句:
+
+```sql
+select strreplace(s1, "target"=",", "replace"="/", "limit"="2") from 
root.test.d1
+```
+
+输出序列:
+
+```
++-----------------------------+-----------------------------------------+
+|                         Time|strreplace(root.test.d1.s1, "target"=",",|
+|                             |              "replace"="/", "limit"="2")|
++-----------------------------+-----------------------------------------+
+|2021-01-01T00:00:01.000+08:00|                                A/B/A+,B-|
+|2021-01-01T00:00:02.000+08:00|                                A/A+/A,B+|
+|2021-01-01T00:00:03.000+08:00|                                   B+/B/B|
+|2021-01-01T00:00:04.000+08:00|                                A+/A/A+,A|
+|2021-01-01T00:00:05.000+08:00|                                 A/B-/B,B|
++-----------------------------+-----------------------------------------+
+```
+
+另一个用于查询的 SQL 语句:
+
+```sql
+select strreplace(s1, "target"=",", "replace"="/", "limit"="1", "offset"="1", 
"reverse"="true") from root.test.d1
+```
+
+输出序列:
+
+```
++-----------------------------+-----------------------------------------------------+
+|                         Time|strreplace(root.test.d1.s1, "target"=",", 
"replace"= | 
+|                             |    "|", "limit"="1", "offset"="1", 
"reverse"="true")|
++-----------------------------+-----------------------------------------------------+
+|2021-01-01T00:00:01.000+08:00|                                            
A,B/A+,B-|
+|2021-01-01T00:00:02.000+08:00|                                            
A,A+/A,B+|
+|2021-01-01T00:00:03.000+08:00|                                               
B+/B,B|
+|2021-01-01T00:00:04.000+08:00|                                            
A+,A/A+,A|
+|2021-01-01T00:00:05.000+08:00|                                             
A,B-/B,B|
++-----------------------------+-----------------------------------------------------+
+```
+
 ## RegexMatch
 
 ### 函数简介
@@ -209,82 +783,4 @@ select regexsplit(s1, "regex"=",", "index"="3") from 
root.test.d1
 |2021-01-01T00:00:04.000+08:00|                                                
    A|
 |2021-01-01T00:00:05.000+08:00|                                                
    B|
 
+-----------------------------+-----------------------------------------------------+
-```
-
-## StrReplace
-
-### 函数简介
-
-本函数用于将文本中的子串替换为指定的字符串。
-
-**函数名:** STRREPLACE
-
-**输入序列:** 仅支持单个输入序列,类型为 TEXT。
-
-**参数:**
-
-+ `target`: 需要替换的字符子串
-+ `replace`: 替换后的字符串。
-+ `limit`: 替换次数,大于等于 -1 的整数,默认为 -1 表示所有匹配的子串都会被替换。
-+ `offset`: 需要跳过的匹配次数,即前`offset`次匹配到的字符子串并不会被替换,默认为 0。
-+ `reverse`: 是否需要反向计数,默认为 false 即按照从左向右的次序。
-
-**输出序列:** 输出单个序列,类型为 TEXT。
-
-### 使用示例
-
-输入序列:
-
-```
-+-----------------------------+---------------+
-|                         Time|root.test.d1.s1|
-+-----------------------------+---------------+
-|2021-01-01T00:00:01.000+08:00|      A,B,A+,B-|
-|2021-01-01T00:00:02.000+08:00|      A,A+,A,B+|
-|2021-01-01T00:00:03.000+08:00|         B+,B,B|
-|2021-01-01T00:00:04.000+08:00|      A+,A,A+,A|
-|2021-01-01T00:00:05.000+08:00|       A,B-,B,B|
-+-----------------------------+---------------+
-```
-
-用于查询的 SQL 语句:
-
-```sql
-select strreplace(s1, "target"=",", "replace"="/", "limit"="2") from 
root.test.d1
-```
-
-输出序列:
-
-```
-+-----------------------------+-----------------------------------------+
-|                         Time|strreplace(root.test.d1.s1, "target"=",",|
-|                             |              "replace"="/", "limit"="2")|
-+-----------------------------+-----------------------------------------+
-|2021-01-01T00:00:01.000+08:00|                                A/B/A+,B-|
-|2021-01-01T00:00:02.000+08:00|                                A/A+/A,B+|
-|2021-01-01T00:00:03.000+08:00|                                   B+/B/B|
-|2021-01-01T00:00:04.000+08:00|                                A+/A/A+,A|
-|2021-01-01T00:00:05.000+08:00|                                 A/B-/B,B|
-+-----------------------------+-----------------------------------------+
-```
-
-另一个用于查询的 SQL 语句:
-
-```sql
-select strreplace(s1, "target"=",", "replace"="/", "limit"="1", "offset"="1", 
"reverse"="true") from root.test.d1
-```
-
-输出序列:
-
-```
-+-----------------------------+-----------------------------------------------------+
-|                         Time|strreplace(root.test.d1.s1, "target"=",", 
"replace"= | 
-|                             |    "|", "limit"="1", "offset"="1", 
"reverse"="true")|
-+-----------------------------+-----------------------------------------------------+
-|2021-01-01T00:00:01.000+08:00|                                            
A,B/A+,B-|
-|2021-01-01T00:00:02.000+08:00|                                            
A,A+/A,B+|
-|2021-01-01T00:00:03.000+08:00|                                               
B+/B,B|
-|2021-01-01T00:00:04.000+08:00|                                            
A+,A/A+,A|
-|2021-01-01T00:00:05.000+08:00|                                             
A,B-/B,B|
-+-----------------------------+-----------------------------------------------------+
 ```
\ No newline at end of file
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBUDTFBuiltinFunctionIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBUDTFBuiltinFunctionIT.java
new file mode 100644
index 0000000000..278353c122
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBUDTFBuiltinFunctionIT.java
@@ -0,0 +1,1230 @@
+/*
+ * 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;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+// TODO @Category({LocalStandaloneIT.class, ClusterIT.class})
+// After bug of init MultiInputColumnIntermediateLayer fixed
+@Category({LocalStandaloneIT.class})
+public class IoTDBUDTFBuiltinFunctionIT {
+
+  private static final double E = 0.0001;
+
+  private static final String[] INSERTION_SQLS = {
+    "insert into root.sg.d1(time, s1, s2, s3, s4, s5, s6, s7, s8) values (0, 
0, 0, 0, 0, true, '0', 0, 0)",
+    "insert into root.sg.d1(time, s1, s2, s3, s4, s5, s6, s7) values (2, 1, 1, 
1, 1, false, '1', 1)",
+    "insert into root.sg.d1(time, s1, s2, s3, s4, s5, s6, s7) values (4, 2, 2, 
2, 2, false, '2', 2)",
+    "insert into root.sg.d1(time, s1, s2, s3, s4, s5, s6, s8) values (6, 3, 3, 
3, 3, true, '3', 3)",
+    "insert into root.sg.d1(time, s1, s2, s3, s4, s5, s6, s8) values (8, 4, 4, 
4, 4, true, '4', 4)",
+  };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initBeforeClass();
+    createTimeSeries();
+    generateData();
+  }
+
+  private static void createTimeSeries() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET STORAGE GROUP TO root.sg");
+      statement.execute("CREATE TIMESERIES root.sg.d1.s1 with 
datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d1.s2 with 
datatype=INT64,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d1.s3 with 
datatype=FLOAT,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d1.s4 with 
datatype=DOUBLE,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d1.s5 with 
datatype=BOOLEAN,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d1.s6 with 
datatype=TEXT,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  private static void generateData() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (String dataGenerationSql : INSERTION_SQLS) {
+        statement.execute(dataGenerationSql);
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanAfterClass();
+  }
+
+  @Test
+  public void testMathFunctions() {
+    testMathFunction("sin", Math::sin);
+    testMathFunction("cos", Math::cos);
+    testMathFunction("tan", Math::tan);
+    testMathFunction("asin", Math::asin);
+    testMathFunction("acos", Math::acos);
+    testMathFunction("atan", Math::atan);
+    testMathFunction("sinh", Math::sinh);
+    testMathFunction("cosh", Math::cosh);
+    testMathFunction("tanh", Math::tanh);
+    testMathFunction("degrees", Math::toDegrees);
+    testMathFunction("radians", Math::toRadians);
+    testMathFunction("abs", Math::abs);
+    testMathFunction("sign", Math::signum);
+    testMathFunction("ceil", Math::ceil);
+    testMathFunction("floor", Math::floor);
+    testMathFunction("round", Math::rint);
+    testMathFunction("exp", Math::exp);
+    testMathFunction("ln", Math::log);
+    testMathFunction("log10", Math::log10);
+    testMathFunction("sqrt", Math::sqrt);
+  }
+
+  private interface MathFunctionProxy {
+
+    double invoke(double x);
+  }
+
+  private void testMathFunction(String functionName, MathFunctionProxy 
functionProxy) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1), %s(s2), %s(s3), %s(s4) from root.sg.d1",
+                  functionName, functionName, functionName, functionName));
+
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 4, columnCount);
+
+      for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+        resultSet.next();
+        for (int j = 0; j < 4; ++j) {
+          double expected = functionProxy.invoke(i);
+          double actual = Double.parseDouble(resultSet.getString(2 + j));
+          assertEquals(expected, actual, E);
+        }
+      }
+      resultSet.close();
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testSelectorFunctions() {
+    final String TOP_K = "TOP_K";
+    final String BOTTOM_K = "BOTTOM_K";
+    final String K = "'k'='2'";
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1, %s), %s(s2, %s), %s(s3, %s), %s(s4, %s), 
%s(s6, %s) from root.sg.d1",
+                  TOP_K, K, TOP_K, K, TOP_K, K, TOP_K, K, TOP_K, K));
+
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 5, columnCount);
+
+      for (int i = INSERTION_SQLS.length - 2; i < INSERTION_SQLS.length; ++i) {
+        resultSet.next();
+        for (int j = 0; j < 5; ++j) {
+          assertEquals(i, Double.parseDouble(resultSet.getString(2 + j)), E);
+        }
+      }
+      resultSet.close();
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1, %s), %s(s2, %s), %s(s3, %s), %s(s4, %s), 
%s(s6, %s) from root.sg.d1",
+                  BOTTOM_K, K, BOTTOM_K, K, BOTTOM_K, K, BOTTOM_K, K, 
BOTTOM_K, K));
+
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 5, columnCount);
+
+      for (int i = 0; i < 2; ++i) {
+        resultSet.next();
+        for (int j = 0; j < 5; ++j) {
+          assertEquals(i, Double.parseDouble(resultSet.getString(2 + j)), E);
+        }
+      }
+      resultSet.close();
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testStringProcessingFunctions() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      ResultSet resultSet =
+          statement.executeQuery(
+              "select STRING_CONTAINS(s6, 's'='0'), STRING_MATCHES(s6, 
'regex'='\\d') from root.sg.d1");
+
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 2, columnCount);
+
+      for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+        resultSet.next();
+        if (i == 0) {
+          assertTrue(Boolean.parseBoolean(resultSet.getString(2)));
+        } else {
+          assertFalse(Boolean.parseBoolean(resultSet.getString(2)));
+        }
+        assertTrue(Boolean.parseBoolean(resultSet.getString(2 + 1)));
+      }
+      resultSet.close();
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testVariationTrendCalculationFunctions() {
+    testVariationTrendCalculationFunction("TIME_DIFFERENCE", 2);
+    testVariationTrendCalculationFunction("DIFFERENCE", 1);
+    testVariationTrendCalculationFunction("NON_NEGATIVE_DIFFERENCE", 1);
+    testVariationTrendCalculationFunction("DERIVATIVE", 0.5);
+    testVariationTrendCalculationFunction("NON_NEGATIVE_DERIVATIVE", 0.5);
+  }
+
+  public void testVariationTrendCalculationFunction(String functionName, 
double expected) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1), %s(s2), %s(s3), %s(s4) from root.sg.d1",
+                  functionName, functionName, functionName, functionName));
+
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 4, columnCount);
+
+      for (int i = 0; i < INSERTION_SQLS.length - 1; ++i) {
+        resultSet.next();
+        for (int j = 0; j < 4; ++j) {
+          assertEquals(expected, Double.parseDouble(resultSet.getString(2 + 
j)), E);
+        }
+      }
+      resultSet.close();
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testConstantTimeSeriesGeneratingFunctions() {
+    String[] expected = {
+      "0, 0.0, 0.0, 1024, 3.141592653589793, 2.718281828459045, ",
+      "2, 1.0, null, 1024, 3.141592653589793, 2.718281828459045, ",
+      "4, 2.0, null, 1024, 3.141592653589793, 2.718281828459045, ",
+      "6, null, 3.0, null, null, 2.718281828459045, ",
+      "8, null, 4.0, null, null, 2.718281828459045, ",
+    };
+
+    try (Connection connection = EnvFactory.getEnv().getConnection()) {
+
+      try (Statement statement = connection.createStatement();
+          ResultSet resultSet =
+              statement.executeQuery(
+                  "select s7, s8, const(s7, 'value'='1024', 'type'='INT64'), 
pi(s7, s7), e(s7, s8, s7, s8) from root.sg.d1")) {
+        assertEquals(1 + 5, resultSet.getMetaData().getColumnCount());
+
+        for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+          resultSet.next();
+          StringBuilder actual = new StringBuilder();
+          for (int j = 0; j < 1 + 5; ++j) {
+            actual.append(resultSet.getString(1 + j)).append(", ");
+          }
+          assertEquals(expected[i], actual.toString());
+        }
+
+        assertFalse(resultSet.next());
+      }
+
+      try (Statement statement = connection.createStatement();
+          ResultSet ignored =
+              statement.executeQuery("select const(s7, 'value'='1024') from 
root.sg.d1")) {
+        fail();
+      } catch (SQLException e) {
+        assertTrue(e.getMessage().contains("attribute \"type\" is required but 
was not provided"));
+      }
+
+      try (Statement statement = connection.createStatement();
+          ResultSet ignored =
+              statement.executeQuery("select const(s8, 'type'='INT64') from 
root.sg.d1")) {
+        fail();
+      } catch (SQLException e) {
+        assertTrue(e.getMessage().contains("attribute \"value\" is required 
but was not provided"));
+      }
+
+      try (Statement statement = connection.createStatement();
+          ResultSet ignored =
+              statement.executeQuery(
+                  "select const(s8, 'value'='1024', 'type'='long') from 
root.sg.d1")) {
+        fail();
+      } catch (SQLException e) {
+        assertTrue(e.getMessage().contains("the given value type is not 
supported"));
+      }
+
+      try (Statement statement = connection.createStatement();
+          ResultSet ignored =
+              statement.executeQuery(
+                  "select const(s8, 'value'='1024e', 'type'='INT64') from 
root.sg.d1")) {
+        fail();
+      } catch (SQLException e) {
+        assertTrue(e.getMessage().contains("java.lang.NumberFormatException"));
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testConversionFunction() {
+    String[] expected = {
+      "0, 0, 0.0, 1, 0.0, ",
+      "2, 1, 1.0, 0, 1.0, ",
+      "4, 2, 2.0, 0, 2.0, ",
+      "6, 3, 3.0, 1, null, ",
+      "8, 4, 4.0, 1, null, ",
+    };
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      ResultSet resultSet =
+          statement.executeQuery(
+              "select cast(s1, 'type'='TEXT'), cast(s3, 'type'='FLOAT'), 
cast(s5, 'type'='INT32'), cast(s7, 'type'='DOUBLE') from root.sg.d1");
+
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(5, columnCount);
+
+      for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+        resultSet.next();
+        StringBuilder actual = new StringBuilder();
+        for (int j = 0; j < 1 + 4; ++j) {
+          actual.append(resultSet.getString(1 + j)).append(", ");
+        }
+        assertEquals(expected[i], actual.toString());
+      }
+      resultSet.close();
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testContinuouslySatisfies() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("CREATE TIMESERIES root.sg.d2.s1 with 
datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d2.s2 with 
datatype=INT64,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d2.s3 with 
datatype=FLOAT,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d2.s4 with 
datatype=DOUBLE,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d2.s5 with 
datatype=BOOLEAN,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    // create timeseries with only 0,1 values
+    String[] ZERO_ONE_SQL = {
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (0, 0, 0, 0, 0, 
false)",
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (1, 1, 1, 1, 1, 
true)",
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (2, 1, 1, 1, 1, 
true)",
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (3, 0, 0, 0, 0, 
false)",
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (4, 1, 1, 1, 1, 
true)",
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (5, 0, 0, 0, 0, 
false)",
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (6, 0, 0, 0, 0, 
false)",
+      "insert into root.sg.d2(time, s1, s2, s3, s4, s5) values (7, 1, 1, 1, 1, 
true)",
+    };
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (String dataGenerationSql : ZERO_ONE_SQL) {
+        statement.execute(dataGenerationSql);
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    // test ZERO_DURATION
+    // result should be (0,0),(3,0),(5,1)
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      int[] timestamps = {0, 3, 5};
+      int[] durations = {0, 0, 1};
+      String functionName = "zero_duration";
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1), %s(s2), %s(s3), %s(s4), %s(s5) from 
root.sg.d2",
+                  functionName, functionName, functionName, functionName, 
functionName));
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 5, columnCount);
+
+      for (int i = 0; i < timestamps.length; ++i) {
+        resultSet.next();
+        long expectedTimestamp = timestamps[i];
+        long actualTimestamp = Long.parseLong(resultSet.getString(1));
+        assertEquals(expectedTimestamp, actualTimestamp);
+
+        long expectedDuration = durations[i];
+        for (int j = 0; j < 5; ++j) {
+          long actualDuration = Long.parseLong(resultSet.getString(2 + j));
+          assertEquals(expectedDuration, actualDuration);
+        }
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    // test NON_ZERO_DURATION
+    // result should be (1,1),(4,0),(7,0)
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "non_zero_duration";
+      int[] timestamps = {1, 4, 7};
+      int[] durations = {1, 0, 0};
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1), %s(s2), %s(s3), %s(s4), %s(s5) from 
root.sg.d2",
+                  functionName, functionName, functionName, functionName, 
functionName));
+
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 5, columnCount);
+
+      for (int i = 0; i < timestamps.length; ++i) {
+        resultSet.next();
+        long expectedTimestamp = timestamps[i];
+        long actualTimestamp = Long.parseLong(resultSet.getString(1));
+        assertEquals(expectedTimestamp, actualTimestamp);
+
+        long expectedDuration = durations[i];
+        for (int j = 0; j < 5; ++j) {
+          long actualDuration = Long.parseLong(resultSet.getString(2 + j));
+          assertEquals(expectedDuration, actualDuration);
+        }
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    // test ZERO_COUNT
+    // result should be (0,1),(3,1),(5,2)
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "zero_count";
+      int[] timestamps = {0, 3, 5};
+      int[] durations = {1, 1, 2};
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1), %s(s2), %s(s3), %s(s4), %s(s5) from 
root.sg.d2",
+                  functionName, functionName, functionName, functionName, 
functionName));
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 5, columnCount);
+
+      for (int i = 0; i < timestamps.length; ++i) {
+        resultSet.next();
+        long expectedTimestamp = timestamps[i];
+        long actualTimestamp = Long.parseLong(resultSet.getString(1));
+        assertEquals(expectedTimestamp, actualTimestamp);
+
+        long expectedDuration = durations[i];
+        for (int j = 0; j < 5; ++j) {
+          long actualDuration = Long.parseLong(resultSet.getString(2 + j));
+          assertEquals(expectedDuration, actualDuration);
+        }
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    // test NON_ZERO_COUNT
+    // result should be (1,2),(4,1),(7,1)
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "non_zero_count";
+      int[] timestamps = {1, 4, 7};
+      int[] durations = {2, 1, 1};
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1), %s(s2), %s(s3), %s(s4), %s(s5) from 
root.sg.d2",
+                  functionName, functionName, functionName, functionName, 
functionName));
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 5, columnCount);
+
+      for (int i = 0; i < timestamps.length; ++i) {
+        resultSet.next();
+        long expectedTimestamp = timestamps[i];
+        long actualTimestamp = Long.parseLong(resultSet.getString(1));
+        assertEquals(expectedTimestamp, actualTimestamp);
+
+        long expectedDuration = durations[i];
+        for (int j = 0; j < 5; ++j) {
+          long actualDuration = Long.parseLong(resultSet.getString(2 + j));
+          assertEquals(expectedDuration, actualDuration);
+        }
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testOnOffFunction() {
+    Double[] thresholds = {Double.MAX_VALUE, -1.0, 0.0, 1.0, Double.MAX_VALUE};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (Double threshold : thresholds) {
+        ResultSet resultSet =
+            statement.executeQuery(
+                String.format(
+                    "select on_off(s1,'threshold'='%f'), 
on_off(s2,'threshold'='%f'), on_off(s3,'threshold'='%f'), 
on_off(s4,'threshold'='%f') from root.sg.d1",
+                    threshold, threshold, threshold, threshold));
+
+        int columnCount = resultSet.getMetaData().getColumnCount();
+        assertEquals(1 + 4, columnCount);
+
+        for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+          resultSet.next();
+          for (int j = 0; j < 4; ++j) {
+            Boolean expected = i >= threshold;
+            Boolean actual = Boolean.parseBoolean(resultSet.getString(2 + j));
+            assertEquals(expected, actual);
+          }
+        }
+        resultSet.close();
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testInRange() {
+    Double[] lowers = {-1.0, 0.0, 1.5, 2.0, 4.0};
+    Double[] uppers = {0.0, 2.0, 4.5, 2.0, 1.0};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (int k = 0; k < lowers.length; ++k) {
+        Double lower = lowers[k];
+        Double upper = uppers[k];
+        ResultSet resultSet =
+            statement.executeQuery(
+                String.format(
+                    "select in_range(s1,'upper'='%f','lower'='%f'), 
in_range(s2,'upper'='%f','lower'='%f'), "
+                        + "in_range(s3,'upper'='%f','lower'='%f'), 
in_range(s4,'upper'='%f','lower'='%f') from root.sg.d1",
+                    upper, lower, upper, lower, upper, lower, upper, lower));
+
+        int columnCount = resultSet.getMetaData().getColumnCount();
+        assertEquals(1 + 4, columnCount);
+
+        for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+          resultSet.next();
+          for (int j = 0; j < 4; ++j) {
+            Boolean expected = (i >= lower && i <= upper);
+            Boolean actual = Boolean.parseBoolean(resultSet.getString(2 + j));
+            assertEquals(expected, actual);
+          }
+        }
+        resultSet.close();
+      }
+    } catch (SQLException e) {
+      assertTrue(e.getMessage().contains("Upper can not be smaller than 
lower."));
+    }
+  }
+
+  @Test
+  public void testEqualBucketSampleForRandom() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("CREATE TIMESERIES root.sg.d5.s1 with 
datatype=INT32,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+    String[] SQL_FOR_SAMPLE_S1 = new String[100];
+    for (int i = 0; i < 100; i++) {
+      SQL_FOR_SAMPLE_S1[i] =
+          String.format("insert into root.sg.d5(time, s1) values (%d, %d)", i, 
i);
+    }
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (int i = 0; i < 100; i++) {
+        statement.execute(SQL_FOR_SAMPLE_S1[i]);
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "EQUAL_SIZE_BUCKET_RANDOM_SAMPLE";
+      double proportionValue = 0.1;
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select " + "%s(s1, 'proportion'='%f') from root.sg.d5",
+                  functionName, proportionValue));
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 1, columnCount);
+      int count = 0;
+      while (resultSet.next()) {
+        count++;
+      }
+      assertEquals(10, count);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Test
+  public void testEqualBucketSampleForAgg() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("CREATE TIMESERIES root.sg.d4.s1 with 
datatype=FLOAT,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d4.s2 with 
datatype=DOUBLE,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d4.s3 with 
datatype=INT64,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d4.s4 with 
datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d4.s5 with 
datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d4.s6 with 
datatype=DOUBLE,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+    String[] SQL_FOR_SAMPLE_S1 = new String[100];
+    String[] SQL_FOR_SAMPLE_S2 = new String[100];
+    String[] SQL_FOR_SAMPLE_S3 = new String[100];
+    String[] SQL_FOR_SAMPLE_S4 = new String[100];
+    String[] SQL_FOR_SAMPLE_S5 = new String[100];
+    String[] SQL_FOR_SAMPLE_S6 = new String[100];
+
+    for (int i = 0; i < 100; i++) {
+      SQL_FOR_SAMPLE_S1[i] =
+          String.format("insert into root.sg.d4(time, s1) values (%d, %f)", i, 
i * 1.0);
+      SQL_FOR_SAMPLE_S2[i] =
+          String.format("insert into root.sg.d4(time, s2) values (%d, %f)", i, 
i * 1.0);
+      SQL_FOR_SAMPLE_S3[i] =
+          String.format("insert into root.sg.d4(time, s3) values (%d, %d)", i, 
i);
+      SQL_FOR_SAMPLE_S4[i] =
+          String.format("insert into root.sg.d4(time, s4) values (%d, %d)", i, 
i);
+      SQL_FOR_SAMPLE_S5[i] =
+          String.format("insert into root.sg.d4(time, s5) values (%d, %d)", i, 
-i);
+      SQL_FOR_SAMPLE_S6[i] =
+          String.format("insert into root.sg.d4(time, s6) values (%d, %f)", i, 
i * 1.0);
+    }
+    float[] ANSWER1 =
+        new float[] {4.5F, 14.5F, 24.5F, 34.5F, 44.5F, 54.5F, 64.5F, 74.5F, 
84.5F, 94.5F};
+    double[] ANSWER2 = new double[] {0, 10, 20, 30, 40, 50, 60, 70, 80, 90};
+    long[] ANSWER3 = new long[] {9, 19, 29, 39, 49, 59, 69, 79, 89, 99};
+    long[] ANSWER4 = new long[] {45, 145, 245, 345, 445, 545, 645, 745, 845, 
945};
+    int[] ANSWER5 = new int[] {-9, -19, -29, -39, -49, -59, -69, -79, -89, 
-99};
+    double[] ANSWER6 = new double[] {8.25, 8.25, 8.25, 8.25, 8.25, 8.25, 8.25, 
8.25, 8.25, 8.25};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (int i = 0; i < 100; i++) {
+        statement.execute(SQL_FOR_SAMPLE_S1[i]);
+        statement.execute(SQL_FOR_SAMPLE_S2[i]);
+        statement.execute(SQL_FOR_SAMPLE_S3[i]);
+        statement.execute(SQL_FOR_SAMPLE_S4[i]);
+        statement.execute(SQL_FOR_SAMPLE_S5[i]);
+        statement.execute(SQL_FOR_SAMPLE_S6[i]);
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "EQUAL_SIZE_BUCKET_AGG_SAMPLE";
+      double proportionValue = 0.1;
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select "
+                      + "%s(s1, 'proportion'='%f'), "
+                      + "%s(s2, 'type'='%s', 'proportion'='%f'), "
+                      + "%s(s3, 'type'='%s', 'proportion'='%f'), "
+                      + "%s(s4, 'type'='%s', 'proportion'='%f'), "
+                      + "%s(s5, 'type'='%s', 'proportion'='%f'), "
+                      + "%s(s6, 'type'='%s', 'proportion'='%f')"
+                      + "from root.sg.d4",
+                  functionName,
+                  proportionValue,
+                  functionName,
+                  "min",
+                  proportionValue,
+                  functionName,
+                  "max",
+                  proportionValue,
+                  functionName,
+                  "sum",
+                  proportionValue,
+                  functionName,
+                  "extreme",
+                  proportionValue,
+                  functionName,
+                  "variance",
+                  proportionValue));
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 6, columnCount);
+      for (int i = 0; i < 10; i++) {
+        resultSet.next();
+        assertEquals(ANSWER1[i], resultSet.getDouble(2), 0.01);
+        assertEquals(ANSWER2[i], resultSet.getDouble(3), 0.01);
+        assertEquals(ANSWER3[i], resultSet.getLong(4));
+        assertEquals(ANSWER4[i], resultSet.getLong(5));
+        assertEquals(ANSWER5[i], resultSet.getInt(6));
+        assertEquals(ANSWER6[i], resultSet.getDouble(7), 0.01);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Test
+  public void testEqualBucketSampleForM4() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("CREATE TIMESERIES root.sg.d3.s1 with 
datatype=INT32,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+    String[] SQL_FOR_SAMPLE = new String[100];
+    for (int i = 0; i < 100; i++) {
+      SQL_FOR_SAMPLE[i] =
+          String.format("insert into root.sg.d3(time, s1) values (%d, %d)", i, 
i + 1);
+    }
+    int[] ANSWER1 = new int[] {1, 2, 39, 40, 41, 42, 79, 80, 81, 82, 99, 100};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (String dataGenerationSql : SQL_FOR_SAMPLE) {
+        statement.execute(dataGenerationSql);
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "EQUAL_SIZE_BUCKET_M4_SAMPLE";
+      String methodName = "m4";
+      double proportionValue = 0.1;
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1, 'method'='%s', 'proportion'='%f') from 
root.sg.d3",
+                  functionName, methodName, proportionValue));
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 1, columnCount);
+      for (int j : ANSWER1) {
+        resultSet.next();
+        assertEquals(j, resultSet.getInt(2));
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Test
+  public void testEqualBucketSampleForOutlier() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("CREATE TIMESERIES root.sg.d6.s1 with 
datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d6.s2 with 
datatype=INT64,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d6.s3 with 
datatype=DOUBLE,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d6.s4 with 
datatype=FLOAT,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d6.s5 with 
datatype=FLOAT,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+    String[] SQL_FOR_SAMPLE_S1 = new String[100];
+    String[] SQL_FOR_SAMPLE_S2 = new String[100];
+    String[] SQL_FOR_SAMPLE_S3 = new String[100];
+    String[] SQL_FOR_SAMPLE_S4 = new String[100];
+    String[] SQL_FOR_SAMPLE_S5 = new String[20];
+    for (int i = 0; i < 100; i++) {
+      SQL_FOR_SAMPLE_S1[i] =
+          String.format(
+              "insert into root.sg.d6(time, s1) values (%d, %d)",
+              i, i % 5 == 0 && i % 10 != 0 ? i + 100 : i);
+      SQL_FOR_SAMPLE_S2[i] =
+          String.format(
+              "insert into root.sg.d6(time, s2) values (%d, %d)", i, i % 10 == 
6 ? i + 100 : i);
+      if (i % 10 == 9 || i % 20 == 0) {
+        SQL_FOR_SAMPLE_S2[i] = String.format("insert into root.sg.d6(time, s2) 
values (%d, 0)", i);
+      }
+      SQL_FOR_SAMPLE_S3[i] =
+          String.format(
+              "insert into root.sg.d6(time, s3) values (%d, %d)", i, i % 10 == 
7 ? i + 100 : i);
+      SQL_FOR_SAMPLE_S4[i] =
+          String.format(
+              "insert into root.sg.d6(time, s4) values (%d, %d)", i, i % 10 == 
8 ? i + 100 : i);
+    }
+    for (int i = 0; i < 20; i++) {
+      SQL_FOR_SAMPLE_S5[i] =
+          String.format("insert into root.sg.d6(time, s5) values (%d, %d)", i, 
i);
+    }
+    int[] ANSWER1 = new int[] {105, 115, 125, 135, 145, 155, 165, 175, 185, 
195};
+    long[] ANSWER2 = new long[] {106, 116, 126, 136, 146, 156, 166, 176, 186, 
196};
+    double[] ANSWER3 = new double[] {107, 117, 127, 137, 147, 157, 167, 177, 
187, 197};
+    float[] ANSWER4 = new float[] {108, 118, 128, 138, 148, 158, 168, 178, 
188, 198};
+    float[] ANSWER5 = new float[] {0, 2, 4, 6, 8, 10, 12, 14, 16, 18};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (int i = 0; i < 100; i++) {
+        statement.execute(SQL_FOR_SAMPLE_S1[i]);
+        statement.execute(SQL_FOR_SAMPLE_S2[i]);
+        statement.execute(SQL_FOR_SAMPLE_S3[i]);
+        statement.execute(SQL_FOR_SAMPLE_S4[i]);
+      }
+      for (int i = 0; i < 20; i++) {
+        statement.execute(SQL_FOR_SAMPLE_S5[i]);
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "EQUAL_SIZE_BUCKET_OUTLIER_SAMPLE";
+      double proportionValue = 0.1;
+
+      ResultSet resultSet0 =
+          statement.executeQuery(
+              String.format(
+                  "select "
+                      + "%s(s1, 'proportion'='%f', 'type'='%s', 'number'='%d') 
"
+                      + "from root.sg.d6",
+                  functionName, proportionValue, "avg", 2));
+      int columnCount0 = resultSet0.getMetaData().getColumnCount();
+      assertEquals(1 + 1, columnCount0);
+      for (int i = 0; i < 10; i++) {
+        resultSet0.next();
+        assertEquals(ANSWER1[i], resultSet0.getInt(2));
+      }
+
+      ResultSet resultSet1 =
+          statement.executeQuery(
+              String.format(
+                  "select "
+                      + "%s(s2, 'proportion'='%f', 'type'='%s', 'number'='%d') 
"
+                      + "from root.sg.d6",
+                  functionName, proportionValue, "stendis", 2));
+      int columnCount1 = resultSet1.getMetaData().getColumnCount();
+      assertEquals(1 + 1, columnCount1);
+      for (int i = 0; i < 10; i++) {
+        resultSet1.next();
+        assertEquals(ANSWER2[i], resultSet1.getLong(2));
+      }
+
+      ResultSet resultSet2 =
+          statement.executeQuery(
+              String.format(
+                  "select "
+                      + "%s(s3, 'proportion'='%f', 'type'='%s', 'number'='%d') 
"
+                      + "from root.sg.d6",
+                  functionName, proportionValue, "cos", 2));
+      int columnCount2 = resultSet2.getMetaData().getColumnCount();
+      assertEquals(1 + 1, columnCount2);
+      for (int i = 0; i < 10; i++) {
+        resultSet2.next();
+        assertEquals(ANSWER3[i], resultSet2.getDouble(2), 0.01);
+      }
+
+      ResultSet resultSet3 =
+          statement.executeQuery(
+              String.format(
+                  "select "
+                      + "%s(s4, 'proportion'='%f', 'type'='%s', 'number'='%d') 
"
+                      + "from root.sg.d6",
+                  functionName, proportionValue, "prenextdis", 2));
+      int columnCount3 = resultSet3.getMetaData().getColumnCount();
+      assertEquals(1 + 1, columnCount3);
+      for (int i = 0; i < 10; i++) {
+        resultSet3.next();
+        assertEquals(ANSWER4[i], resultSet3.getFloat(2), 0.01);
+      }
+
+      ResultSet resultSet4 =
+          statement.executeQuery(
+              String.format(
+                  "select "
+                      + "%s(s5, 'proportion'='%f', 'type'='%s', 'number'='%d') 
"
+                      + "from root.sg.d6",
+                  functionName, 0.5, "cos", 1));
+      int columnCount4 = resultSet4.getMetaData().getColumnCount();
+      assertEquals(1 + 1, columnCount4);
+      for (int i = 0; i < 10; i++) {
+        resultSet4.next();
+        assertEquals(ANSWER5[i], resultSet4.getFloat(2), 0.01);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Test
+  public void testUDTFJexl() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("CREATE TIMESERIES root.sg.d7.s1 with 
datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s2 with 
datatype=FLOAT,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s3 with 
datatype=DOUBLE,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s4 with 
datatype=TEXT,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s5 with 
datatype=BOOLEAN,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s6 with 
datatype=INT64,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s7 with 
datatype=INT64,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s8 with 
datatype=FLOAT,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.sg.d7.s9 with 
datatype=TEXT,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+    String[] SQL_FOR_SAMPLE_1 = new String[5];
+    String[] SQL_FOR_SAMPLE_2 = new String[5];
+    String[] SQL_FOR_SAMPLE_3 = new String[5];
+    String[] SQL_FOR_SAMPLE_4 = new String[5];
+    String[] SQL_FOR_SAMPLE_5 = new String[5];
+    String[] SQL_FOR_SAMPLE_6 = new String[5];
+    String[] SQL_FOR_SAMPLE_7 = new String[5];
+    String[] SQL_FOR_SAMPLE_8 = new String[5];
+    String[] SQL_FOR_SAMPLE_9 = new String[5];
+    for (int i = 0; i < 5; i++) {
+      SQL_FOR_SAMPLE_1[i] =
+          String.format("insert into root.sg.d7(time, s1) values (%d, %d)", i, 
i + 1);
+      SQL_FOR_SAMPLE_2[i] =
+          String.format("insert into root.sg.d7(time, s2) values (%d, %f)", i, 
i + 1.0);
+      SQL_FOR_SAMPLE_3[i] =
+          String.format("insert into root.sg.d7(time, s3) values (%d, %f)", i, 
i + 1.0);
+      SQL_FOR_SAMPLE_4[i] =
+          String.format("insert into root.sg.d7(time, s4) values (%d, '%s')", 
i, "string");
+      SQL_FOR_SAMPLE_5[i] = String.format("insert into root.sg.d7(time, s5) 
values (%d, true)", i);
+      SQL_FOR_SAMPLE_6[i] =
+          String.format("insert into root.sg.d7(time, s6) values (%d, %d)", i, 
i + 8);
+      SQL_FOR_SAMPLE_7[i] =
+          String.format("insert into root.sg.d7(time, s7) values (%d, %d)", i, 
i + 1);
+      SQL_FOR_SAMPLE_8[i] =
+          String.format("insert into root.sg.d7(time, s8) values (%d, %f)", i, 
i + 1.0);
+      SQL_FOR_SAMPLE_9[i] =
+          String.format("insert into root.sg.d7(time, s9) values (%d, '%s')", 
i, "string");
+    }
+    double[] ANSWER1 = new double[] {2, 4, 6, 8, 10};
+    double[] ANSWER2 = new double[] {2, 4, 6, 8, 10};
+    double[] ANSWER3 = new double[] {4, 7, 10, 13, 16};
+    String[] ANSWER4 = new String[] {"string2", "string2", "string2", 
"string2", "string2"};
+    double[] ANSWER7 = new double[] {1, 4, 9, 16, 25};
+    String[] ANSWER8 = new String[] {"string1", "string4", "string9", 
"string16", "string25"};
+    double[] ANSWER9 = new double[] {2, 9, 28, 65, 126};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (int i = 0; i < 5; i++) {
+        statement.execute(SQL_FOR_SAMPLE_1[i]);
+        statement.execute(SQL_FOR_SAMPLE_2[i]);
+        statement.execute(SQL_FOR_SAMPLE_3[i]);
+        statement.execute(SQL_FOR_SAMPLE_4[i]);
+        statement.execute(SQL_FOR_SAMPLE_5[i]);
+        statement.execute(SQL_FOR_SAMPLE_6[i]);
+        statement.execute(SQL_FOR_SAMPLE_7[i]);
+        statement.execute(SQL_FOR_SAMPLE_8[i]);
+        statement.execute(SQL_FOR_SAMPLE_9[i]);
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      String functionName = "JEXL";
+      String expr1 = "x -> {2 * x}";
+      String expr2 = "x -> {x + x}";
+      String expr3 = "x -> {x * 3 + 1}";
+      String expr4 = "x -> {x + 2}";
+      String expr5 = "x -> {x == true}";
+      String expr6 = "x -> {x == x}";
+      String expr7 = "(x, y) -> {x * y}";
+      String expr8 = "(x, y, z) -> {x + y * z}";
+      String expr9 = "(x, y, z, a) -> {x * y * z + (a ? 1 : -1)}";
+      ResultSet resultSet =
+          statement.executeQuery(
+              String.format(
+                  "select %s(s1, 'expr'='%s'), "
+                      + "%s(s2, 'expr'='%s'), "
+                      + "%s(s3, 'expr'='%s'), "
+                      + "%s(s4, 'expr'='%s'), "
+                      + "%s(s5, 'expr'='%s'), "
+                      + "%s(s6, 'expr'='%s'), "
+                      + "%s(s7, s8, 'expr'='%s'), "
+                      + "%s(s4, s7, s1, 'expr'='%s'), "
+                      + "%s(s1, s7, s8, s5, 'expr'='%s') "
+                      + "from root.sg.d7",
+                  functionName,
+                  expr1,
+                  functionName,
+                  expr2,
+                  functionName,
+                  expr3,
+                  functionName,
+                  expr4,
+                  functionName,
+                  expr5,
+                  functionName,
+                  expr6,
+                  functionName,
+                  expr7,
+                  functionName,
+                  expr8,
+                  functionName,
+                  expr9));
+      int columnCount = resultSet.getMetaData().getColumnCount();
+      assertEquals(1 + 9, columnCount);
+      for (int i = 0; i < 5; i++) {
+        resultSet.next();
+        assertEquals(ANSWER1[i], resultSet.getDouble(2), 0.01);
+        assertEquals(ANSWER2[i], resultSet.getDouble(3), 0.01);
+        assertEquals(ANSWER3[i], resultSet.getDouble(4), 0.01);
+        assertEquals(ANSWER4[i], resultSet.getString(5));
+        assertTrue(resultSet.getBoolean(6));
+        assertTrue(resultSet.getBoolean(7));
+        assertEquals(ANSWER7[i], resultSet.getDouble(8), 0.01);
+        assertEquals(ANSWER8[i], resultSet.getString(9));
+        assertEquals(ANSWER9[i], resultSet.getDouble(10), 0.01);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Test
+  public void testStringFunctions() {
+    String[] createSqls =
+        new String[] {
+          "SET STORAGE GROUP TO root.testStringFunctions",
+          "CREATE TIMESERIES root.testStringFunctions.d1.s1 WITH 
DATATYPE=TEXT, ENCODING=PLAIN",
+          "CREATE TIMESERIES root.testStringFunctions.d1.s2 WITH 
DATATYPE=TEXT, ENCODING=PLAIN",
+        };
+
+    String[] insertSqls =
+        new String[] {
+          "INSERT INTO root.testStringFunctions.d1(timestamp,s1,s2) values(1, 
\"1111test1111\", \"  1111test1111 \")",
+          "INSERT INTO root.testStringFunctions.d1(timestamp,s1) values(2, 
\"2222test2222\")"
+        };
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+
+      for (String createSql : createSqls) {
+        statement.execute(createSql);
+      }
+
+      for (String insertSql : insertSqls) {
+        // TODO statement.addBatch(insertSql);
+        statement.execute(insertSql);
+      }
+      // TODO statement.executeBatch();
+
+      testStrLength(statement);
+      testStrLocate(statement);
+      testStartsWith(statement);
+      testEndsWith(statement);
+      testConcat(statement);
+      testSubStr(statement);
+      testUpper(statement);
+      testLower(statement);
+      testTrim(statement);
+      testStrCmp(statement);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void testTrim(Statement statement) {
+    String s2;
+    try (ResultSet resultSet =
+        statement.executeQuery("select s2, trim(s2) " + "from 
root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        s2 = resultSet.getString(3);
+        if (s2 == null) {
+          continue;
+        }
+        assertEquals(resultSet.getString(2).trim(), resultSet.getString(3));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testStrCmp(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select s1, s2, strcmp(s1, s2) " + "from 
root.testStringFunctions.d1")) {
+      String s1, s2;
+      while (resultSet.next()) {
+        s1 = resultSet.getString(2);
+        s2 = resultSet.getString(3);
+        if (s1 == null || s2 == null) {
+          continue;
+        }
+        assertEquals(s1.compareTo(s2), resultSet.getInt(4));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testLower(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery("select s1, lower(s1) " + "from 
root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(resultSet.getString(2).toLowerCase(), 
resultSet.getString(3));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testUpper(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery("select s1, upper(s1) " + "from 
root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(resultSet.getString(2).toUpperCase(), 
resultSet.getString(3));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testSubStr(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select s1, substr(s1, \"start\"=\"3\", \"end\"=\"7\") "
+                + "from root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(resultSet.getString(2).substring(3, 7), 
resultSet.getString(3));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testConcat(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select s1, s2, "
+                + "concat(s1, s2, \"target1\"=\"IoT\", \"target2\"=\"DB\"), "
+                + "concat(s1, s2, \"target1\"=\"IoT\", \"target2\"=\"DB\", 
\"series_behind\"=\"true\") "
+                + "from root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(
+            (resultSet.getString(2) + resultSet.getString(3) + 
"IoTDB").replace("null", ""),
+            resultSet.getString(4));
+        assertEquals(
+            ("IoTDB" + (resultSet.getString(2) + 
resultSet.getString(3))).replace("null", ""),
+            resultSet.getString(5));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testEndsWith(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select s1, endsWith(s1, \"target\"=\"1111\") " + "from 
root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(resultSet.getString(2).endsWith("1111"), 
resultSet.getBoolean(3));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testStartsWith(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select s1, startsWith(s1, \"target\"=\"1111\") "
+                + "from root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(resultSet.getString(2).startsWith("1111"), 
resultSet.getBoolean(3));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testStrLocate(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select s1, locate(s1, \"target\"=\"1111\"), locate(s1, 
\"target\"=\"1111\", \"reverse\"=\"true\") from root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(resultSet.getString(2).indexOf("1111"), 
resultSet.getInt(3));
+        assertEquals(resultSet.getString(2).lastIndexOf("1111"), 
resultSet.getInt(4));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void testStrLength(Statement statement) {
+    try (ResultSet resultSet =
+        statement.executeQuery("select s1, length(s1) from 
root.testStringFunctions.d1")) {
+      while (resultSet.next()) {
+        assertEquals(resultSet.getString(2).length(), resultSet.getInt(3));
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+}
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 4208735bcb..a5b72955f8 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
@@ -19,6 +19,17 @@
 
 package org.apache.iotdb.commons.udf.builtin;
 
+import org.apache.iotdb.commons.udf.builtin.String.UDTFConcat;
+import org.apache.iotdb.commons.udf.builtin.String.UDTFEndsWith;
+import org.apache.iotdb.commons.udf.builtin.String.UDTFLower;
+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;
+
 /** All built-in UDFs need to register their function names and classes here. 
*/
 public enum BuiltinTimeSeriesGeneratingFunction {
   CONST("CONST", UDTFConst.class),
@@ -46,6 +57,16 @@ public enum BuiltinTimeSeriesGeneratingFunction {
   SQRT("SQRT", UDTFSqrt.class),
   STRING_CONTAINS("STRING_CONTAINS", UDTFContains.class),
   STRING_MATCHES("STRING_MATCHES", UDTFMatches.class),
+  STRING_LENGTH("LENGTH", UDTFStrLength.class),
+  STRING_LOCATE("LOCATE", UDTFStrLocate.class),
+  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),
+  STRING_CMP("STRCMP", UDTFStrCompare.class),
   DIFFERENCE("DIFFERENCE", UDTFCommonValueDifference.class),
   NON_NEGATIVE_DIFFERENCE("NON_NEGATIVE_DIFFERENCE", 
UDTFNonNegativeValueDifference.class),
   TIME_DIFFERENCE("TIME_DIFFERENCE", UDTFTimeDifference.class),
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFConcat.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFConcat.java
new file mode 100644
index 0000000000..03af33f7e8
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFConcat.java
@@ -0,0 +1,76 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*This function Returns the concat string by input series and targets.
+startsEnd: Indicates whether series behind targets. The default value is 
false.*/
+public class UDTFConcat implements UDTF {
+
+  private boolean seriesBehind;
+  private final StringBuilder concatTargets = new StringBuilder();
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    int size = validator.getParameters().getChildExpressions().size();
+    for (int i = 0; i < size; i++) {
+      validator.validateInputSeriesDataType(i, TSDataType.TEXT);
+    }
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    parameters
+        .getAttributes()
+        .forEach(
+            (key, value) -> {
+              if (key.startsWith("target") && value != null) 
concatTargets.append(value);
+            });
+    seriesBehind = parameters.getBooleanOrDefault("series_behind", false);
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.TEXT);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    StringBuilder concatSeries = new StringBuilder();
+    for (int i = 0; i < row.size(); i++) {
+      if (row.isNull(i)) {
+        continue;
+      }
+      concatSeries.append(row.getString(i));
+    }
+
+    collector.putString(
+        row.getTime(),
+        seriesBehind
+            ? concatSeries.insert(0, concatTargets).toString()
+            : concatSeries.append(concatTargets).toString());
+  }
+}
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFEndsWith.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFEndsWith.java
new file mode 100644
index 0000000000..2f267e7b23
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFEndsWith.java
@@ -0,0 +1,53 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*This function returns if input series ends with the specified suffix.*/
+public class UDTFEndsWith implements UDTF {
+
+  private String target;
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator.validateInputSeriesNumber(1).validateInputSeriesDataType(0, 
TSDataType.TEXT);
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    target = parameters.getString("target");
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.BOOLEAN);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    collector.putBoolean(row.getTime(), row.getString(0).endsWith(target));
+  }
+}
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFLower.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFLower.java
new file mode 100644
index 0000000000..0a1db7d92c
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFLower.java
@@ -0,0 +1,50 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*Returns a string with all characters of target changed to lowercase, or NULL 
if target is NULL*/
+public class UDTFLower implements UDTF {
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator.validateInputSeriesNumber(1).validateInputSeriesDataType(0, 
TSDataType.TEXT);
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.TEXT);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    collector.putString(row.getTime(), row.getString(0).toLowerCase());
+  }
+}
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStartsWith.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStartsWith.java
new file mode 100644
index 0000000000..2382473410
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStartsWith.java
@@ -0,0 +1,52 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*This function returns if input series starts with the specified prefix.*/
+public class UDTFStartsWith implements UDTF {
+  private String target;
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator.validateInputSeriesNumber(1).validateInputSeriesDataType(0, 
TSDataType.TEXT);
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    target = parameters.getString("target");
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.BOOLEAN);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    collector.putBoolean(row.getTime(), row.getString(0).startsWith(target));
+  }
+}
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrCompare.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrCompare.java
new file mode 100644
index 0000000000..52b8366434
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrCompare.java
@@ -0,0 +1,57 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*This function returns  0 if targets are the same, -1 if targtet1 is smaller 
than targtet2,
+and NULL if either argument is NULL. It returns 1 otherwise.*/
+public class UDTFStrCompare implements UDTF {
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator
+        .validateInputSeriesNumber(2)
+        .validateInputSeriesDataType(0, TSDataType.TEXT)
+        .validateInputSeriesDataType(1, TSDataType.TEXT);
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.INT32);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    if (row.isNull(0) || row.isNull(1)) {
+      return;
+    }
+    collector.putInt(row.getTime(), 
row.getString(0).compareTo(row.getString(1)));
+  }
+}
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrLength.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrLength.java
new file mode 100644
index 0000000000..9e546615ae
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrLength.java
@@ -0,0 +1,50 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*This function returns length of string from an input series.*/
+public class UDTFStrLength implements UDTF {
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator.validateInputSeriesNumber(1).validateInputSeriesDataType(0, 
TSDataType.TEXT);
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.INT32);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    collector.putInt(row.getTime(), row.getString(0).length());
+  }
+}
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrLocate.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrLocate.java
new file mode 100644
index 0000000000..6fe7b59fe6
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFStrLocate.java
@@ -0,0 +1,62 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*This function returns position of target in an input series.*/
+public class UDTFStrLocate implements UDTF {
+  private String target;
+  private boolean reverse;
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator
+        .validateInputSeriesNumber(1)
+        .validateInputSeriesDataType(0, TSDataType.TEXT)
+        .validate(
+            target -> ((String) target).length() > 0,
+            "target should not be empty",
+            validator.getParameters().getString("target"));
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    target = parameters.getString("target");
+    reverse = parameters.getBooleanOrDefault("reverse", false);
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.INT32);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    collector.putInt(
+        row.getTime(),
+        reverse ? row.getString(0).lastIndexOf(target) : 
row.getString(0).indexOf(target));
+  }
+}
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
new file mode 100644
index 0000000000..4b8673955c
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFSubstr.java
@@ -0,0 +1,72 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*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, TSDataType.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(TSDataType.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/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFTrim.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFTrim.java
new file mode 100644
index 0000000000..2c30dc2541
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFTrim.java
@@ -0,0 +1,50 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*This function returns the string whose value is target, with all leading and 
trailing space removed.*/
+public class UDTFTrim implements UDTF {
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator.validateInputSeriesNumber(1).validateInputSeriesDataType(0, 
TSDataType.TEXT);
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.TEXT);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    collector.putString(row.getTime(), row.getString(0).trim());
+  }
+}
diff --git 
a/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFUpper.java
 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFUpper.java
new file mode 100644
index 0000000000..829756939a
--- /dev/null
+++ 
b/node-commons/src/main/java/org/apache/iotdb/commons/udf/builtin/String/UDTFUpper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.commons.udf.api.UDTF;
+import org.apache.iotdb.commons.udf.api.access.Row;
+import org.apache.iotdb.commons.udf.api.collector.PointCollector;
+import org.apache.iotdb.commons.udf.api.customizer.config.UDTFConfigurations;
+import 
org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.commons.udf.api.customizer.parameter.UDFParameters;
+import 
org.apache.iotdb.commons.udf.api.customizer.strategy.RowByRowAccessStrategy;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+/*Returns a string with all characters of target changed to uppercase, or NULL 
if target is NULL.*/
+public class UDTFUpper implements UDTF {
+
+  @Override
+  public void validate(UDFParameterValidator validator) throws Exception {
+    validator.validateInputSeriesNumber(1).validateInputSeriesDataType(0, 
TSDataType.TEXT);
+  }
+
+  @Override
+  public void beforeStart(UDFParameters parameters, UDTFConfigurations 
configurations)
+      throws Exception {
+    configurations
+        .setAccessStrategy(new RowByRowAccessStrategy())
+        .setOutputDataType(TSDataType.TEXT);
+  }
+
+  @Override
+  public void transform(Row row, PointCollector collector) throws Exception {
+    collector.putString(row.getTime(), row.getString(0).toUpperCase());
+  }
+}

Reply via email to