You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2022/08/25 08:17:08 UTC

[iotdb] branch master updated: [IOTDB-4133] Throw illegal path exception contains number which has been surrounded with quote (#7017)

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

jackietien pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 4108e7c6f9 [IOTDB-4133] Throw illegal path exception contains number which has been surrounded with quote (#7017)
4108e7c6f9 is described below

commit 4108e7c6f9aaa5b0215e4c512867dab6895922a3
Author: Liao Lanyu <10...@users.noreply.github.com>
AuthorDate: Thu Aug 25 16:17:01 2022 +0800

    [IOTDB-4133] Throw illegal path exception contains number which has been surrounded with quote (#7017)
---
 docs/UserGuide/Reference/Syntax-Conventions.md     | 14 ++++----
 docs/zh/Download/README.md                         |  6 ++--
 docs/zh/UserGuide/Reference/Syntax-Conventions.md  | 18 +++++-----
 .../db/it/IoTDBSyntaxConventionIdentifierIT.java   | 40 ++++++++++++++++++++++
 .../org/apache/iotdb/commons/utils/PathUtils.java  | 10 ++++--
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |  4 +--
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |  3 +-
 .../tsfile/read/common/parser/PathVisitor.java     | 29 ++++++++++++++--
 8 files changed, 97 insertions(+), 27 deletions(-)

diff --git a/docs/UserGuide/Reference/Syntax-Conventions.md b/docs/UserGuide/Reference/Syntax-Conventions.md
index fbf0d3503c..cc5b3ef057 100644
--- a/docs/UserGuide/Reference/Syntax-Conventions.md
+++ b/docs/UserGuide/Reference/Syntax-Conventions.md
@@ -309,14 +309,14 @@ Below are basic constraints of identifiers, specific identifiers may have other
 - Permitted characters in unquoted identifiers:
   - [0-9 a-z A-Z _ ] (letters, digits and underscore)
   - ['\u2E80'..'\u9FFF'] (UNICODE Chinese characters)
-- Identifiers may begin with a digit, unquoted identifiers can not consists of solely digits.
+- Identifiers may begin with a digit, unquoted identifiers can not be a real number.
 - Identifiers are case sensitive.
 - Key words can be used as an identifier.
 
 **You need to quote the identifier with back quote(`) in the following cases:**
 
 - Identifier contains special characters.
-- Identifier consists of solely digits.
+- Identifier that is a real number.
 
 ### How to use quotations marks in quoted identifiers
 
@@ -355,14 +355,14 @@ Examples of case in which quoted identifier is used :
 - UDF name should be quoted in cases described above :
 
   ```sql
-  # create a funciton named 111, 111 consists of solely digits.
+  # create a funciton named 111, 111 is a real number.
   CREATE FUNCTION `111` AS 'org.apache.iotdb.udf.UDTFExample'
   ```
 
 - Template name should be quoted in cases described above :
 
   ```sql
-  # create a template named 111, 111 consists of solely digits.
+  # create a template named 111, 111 is a real number.
   create schema template `111` 
   (temperature FLOAT encoding=RLE, status BOOLEAN encoding=PLAIN compression=SNAPPY)
   ```
@@ -461,7 +461,7 @@ When node name is not wildcard, it is a identifier, which means the constraints
 # Node name contains special characters like ` and .,all nodes of this timeseries are: ["root","sg","www.`baidu.com"]
 create timeseries root.sg.`www.``baidu.com`.a with datatype=FLOAT,encoding=PLAIN;
 
-# Node name consists of solely digits.
+# Node name is a real number.
 create timeseries root.sg.`111` with datatype=FLOAT,encoding=PLAIN;
 ```
 
@@ -482,7 +482,7 @@ After executing above statments, execute "show timeseries",below is the result
 # Node name contains special characters like . and `
 insert into root.sg.`www.``baidu.com`(timestamp, a) values(1, 2);
 
-# Node name consists of solely digits.
+# Node name is a real number.
 insert into root.sg(timestamp, `111`) values (1, 2);
 ```
 
@@ -492,7 +492,7 @@ insert into root.sg(timestamp, `111`) values (1, 2);
 # Node name contains special characters like . and `
 select a from root.sg.`www.``baidu.com`;
 
-# Node name consists of solely digits.
+# Node name is a real number.
 select `111` from root.sg
 ```
 
diff --git a/docs/zh/Download/README.md b/docs/zh/Download/README.md
index 1e1aef4d36..0272a1a038 100644
--- a/docs/zh/Download/README.md
+++ b/docs/zh/Download/README.md
@@ -110,9 +110,9 @@
 - 如何从 v0.13.x 升级到 v0.14.x?
   
   - **0.14 版本进行了 SQL 语法约定的改动(请参考用户手册语法约定章节),不兼容之处如下:**
-    - **不使用反引号引用的标识符不允许为纯数字,不使用反引号引用的标识符,只允许包含字母、中文字符、下划线。如果标识符中出现上述情况,请使用反引号将标识符括起。**
+    - **不使用反引号引用的标识符不允许为实数,不使用反引号引用的标识符,只允许包含字母、中文字符、下划线。如果标识符中出现上述情况,请使用反引号将标识符括起。**
     - **标识符不再支持使用单引号和双引号进行引用,请统一改为使用反引号引用。**
-    - **Session 接口中使用路径结点名时,写法需要与 SQL 语句中的一致。如路径结点为纯数字111,在 SQL 语句中需要使用反引号括起,写作\`111\`, 那么使用 Session 接口时,相应参数也需要写作\`111\`。**
+    - **Session 接口中使用路径结点名时,写法需要与 SQL 语句中的一致。如路径结点为实数111,在 SQL 语句中需要使用反引号括起,写作\`111\`, 那么使用 Session 接口时,相应参数也需要写作\`111\`。**
   - 为了保证 UDF 相关 API 的稳定性,0.14 版本中 UDF 相关 API 被独立为一个单独的 module,不再依赖 tsfile 包,已经实现的 UDF 需要改写代码,将 `TsDataType` 替换为 `Type`,将 `org.apache.iotdb.tsfile.utils.Binary` 替换成 `org.apache.iotdb.udf.api.type.Binary`,并重新进行打包装载流程。
 
 # 如何升级
@@ -135,7 +135,7 @@
   * **0.13 进行了 SQL 语法的改动,不使用反引号括起的标识符中仅能包含如下字符,否则需要使用反引号括起。**
     * **[0-9 a-z A-Z _ : @ # $ { }] (字母,数字,部分特殊字符)**
     * **['\u2E80'..'\u9FFF'] (UNICODE 中文字符)**
-  * **0.13 中 `SELECT` 子句中路径结点名如果是纯数字,需要使用反引号引起,用于与表达式中的常数区分。如语句 "select 123 + \`123\` from root.sg",前一个123表示常数,后一个 \`123\`会和 root.sg 拼接,表示路径 root.sg.\`123\`。**
+  * **0.13 中 `SELECT` 子句中路径结点名如果是实数,需要使用反引号引起,用于与表达式中的常数区分。如语句 "select 123 + \`123\` from root.sg",前一个123表示常数,后一个 \`123\`会和 root.sg 拼接,表示路径 root.sg.\`123\`。**
   
 - 如何从 v0.11.x 或 v0.10.x 升级到 v0.12.x? 
   * 从 0.11 或 0.10 升级到 0.12 的过程与 v0.9 升级到 v0.10 类似,升级工具会自动进行数据文件的升级。
diff --git a/docs/zh/UserGuide/Reference/Syntax-Conventions.md b/docs/zh/UserGuide/Reference/Syntax-Conventions.md
index 8337ed3d57..1f543a4c6b 100644
--- a/docs/zh/UserGuide/Reference/Syntax-Conventions.md
+++ b/docs/zh/UserGuide/Reference/Syntax-Conventions.md
@@ -27,7 +27,7 @@
 
 ### 标识符限制增强
 
-在0.13及之前版本中,不使用反引号引用的标识符(包括路径结点)允许为纯数字(纯数字路径名在 `SELECT` 子句中需要用反引号括起),且允许包含部分特殊字符,**在0.14版本中,不使用反引号引用的标识符不允许为纯数字,不使用反引号引用的标识符,只允许包含字母、中文字符、下划线。**
+在0.13及之前版本中,不使用反引号引用的标识符(包括路径结点)允许为实数(实数路径名在 `SELECT` 子句中需要用反引号括起),且允许包含部分特殊字符,**在0.14版本中,不使用反引号引用的标识符不允许为实数,不使用反引号引用的标识符,只允许包含字母、中文字符、下划线。**
 
 ### 路径名使用的相关问题
 
@@ -319,7 +319,7 @@ MySQL 对字符串的定义可以参考:[MySQL :: MySQL 8.0 Reference Manual :
 **如果出现如下情况,标识符需要使用反引号进行引用:**
 
 - 标识符包含不允许的特殊字符。
-- 标识符为纯数字。
+- 标识符为实数。
 
 ### 如何在反引号引起的标识符中使用引号
 
@@ -358,14 +358,14 @@ create schema template `t1't"t`
 - UDF 名称出现上述特殊情况时需使用反引号引用:
 
   ```sql
-  # 创建名为 111 的 UDF,111 为纯数字,所以需要用反引号引用。
+  # 创建名为 111 的 UDF,111 为实数,所以需要用反引号引用。
   CREATE FUNCTION `111` AS 'org.apache.iotdb.udf.UDTFExample'
   ```
 
 - 元数据模板名称出现上述特殊情况时需使用反引号引用:
 
   ```sql
-  # 创建名为 111 的元数据模板,111 为纯数字,需要用反引号引用。
+  # 创建名为 111 的元数据模板,111 为实数,需要用反引号引用。
   create schema template `111` 
   (temperature FLOAT encoding=RLE, status BOOLEAN encoding=PLAIN compression=SNAPPY)
   ```
@@ -464,7 +464,7 @@ select a*b from root.sg
 # 路径结点名中包含特殊字符,时间序列各结点为["root","sg","www.`baidu.com"]
 create timeseries root.sg.`www.``baidu.com`.a with datatype=FLOAT,encoding=PLAIN;
 
-# 路径结点名为纯数字
+# 路径结点名为实数
 create timeseries root.sg.`111` with datatype=FLOAT,encoding=PLAIN;
 ```
 
@@ -485,7 +485,7 @@ create timeseries root.sg.`111` with datatype=FLOAT,encoding=PLAIN;
 # 路径结点名中包含特殊字符
 insert into root.sg.`www.``baidu.com`(timestamp, a) values(1, 2);
 
-# 路径结点名为纯数字
+# 路径结点名为实数
 insert into root.sg(timestamp, `111`) values (1, 2);
 ```
 
@@ -495,7 +495,7 @@ insert into root.sg(timestamp, `111`) values (1, 2);
 # 路径结点名中包含特殊字符
 select a from root.sg.`www.``baidu.com`;
 
-# 路径结点名为纯数字
+# 路径结点名为实数
 select `111` from root.sg
 ```
 
@@ -645,7 +645,7 @@ create timeseries root.sg.a with datatype=FLOAT,encoding=PLAIN,compressor=SNAPPY
 # 路径结点名中包含特殊字符,时间序列各结点为["root","sg","a.`\"b"]
 create timeseries root.sg.`a.``"b` with datatype=FLOAT,encoding=PLAIN,compressor=SNAPPY;
 
-# 路径结点名为纯数字
+# 路径结点名为实数
 create timeseries root.sg.`111` with datatype=FLOAT,encoding=PLAIN,compressor=SNAPPY;
 ```
 
@@ -709,7 +709,7 @@ select a from root.sg
 # 路径结点名中包含特殊字符
 select `a.``"b` from root.sg;
 
-# 路径结点名为纯数字
+# 路径结点名为实数
 select `111` from root.sg
 ```
 
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java
index 9969d2e699..7af57028af 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java
@@ -152,6 +152,16 @@ public class IoTDBSyntaxConventionIdentifierIT {
       "````",
       "`c.d.```",
       "`abc`",
+      "`+12`",
+      "`1e3`",
+      "`001`",
+      "`-1.0`",
+      "`01e-3`",
+      "`+0001`",
+      "`-0001`",
+      "`++1`",
+      "`+-1`",
+      "`--1`"
     };
 
     String[] resultTimeseries = {
@@ -166,6 +176,16 @@ public class IoTDBSyntaxConventionIdentifierIT {
       "root.sg1.d1.````",
       "root.sg1.d1.`c.d.```",
       "root.sg1.d1.abc",
+      "root.sg1.d1.`+12`",
+      "root.sg1.d1.`1e3`",
+      "root.sg1.d1.`001`",
+      "root.sg1.d1.`-1.0`",
+      "root.sg1.d1.`01e-3`",
+      "root.sg1.d1.`+0001`",
+      "root.sg1.d1.`-0001`",
+      "root.sg1.d1.`++1`",
+      "root.sg1.d1.`+-1`",
+      "root.sg1.d1.`--1`"
     };
 
     String[] selectNodeNames = {
@@ -180,6 +200,16 @@ public class IoTDBSyntaxConventionIdentifierIT {
       "````",
       "`c.d.```",
       "abc",
+      "`+12`",
+      "`1e3`",
+      "`001`",
+      "`-1.0`",
+      "`01e-3`",
+      "`+0001`",
+      "`-0001`",
+      "`++1`",
+      "`+-1`",
+      "`--1`"
     };
 
     String[] suffixInResultColumns = {
@@ -194,6 +224,16 @@ public class IoTDBSyntaxConventionIdentifierIT {
       "````",
       "`c.d.```",
       "abc",
+      "`+12`",
+      "`1e3`",
+      "`001`",
+      "`-1.0`",
+      "`01e-3`",
+      "`+0001`",
+      "`-0001`",
+      "`++1`",
+      "`+-1`",
+      "`--1`"
     };
 
     try (Connection connection = EnvFactory.getEnv().getConnection();
diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java b/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java
index ccdf8921ca..658df351ef 100644
--- a/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java
@@ -24,8 +24,7 @@ import org.apache.iotdb.commons.exception.MetadataException;
 import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
 import org.apache.iotdb.tsfile.exception.PathParseException;
 import org.apache.iotdb.tsfile.read.common.parser.PathNodesGenerator;
-
-import org.apache.commons.lang3.StringUtils;
+import org.apache.iotdb.tsfile.read.common.parser.PathVisitor;
 
 import java.util.List;
 
@@ -90,7 +89,7 @@ public class PathUtils {
         }
       }
       if (IoTDBConstant.reservedWords.contains(measurement.toUpperCase())
-          || StringUtils.isNumeric(measurement)
+          || isRealNumber(measurement)
           || !TsFileConstant.NODE_NAME_PATTERN.matcher(measurement).matches()) {
         throw new IllegalPathException(measurement);
       }
@@ -126,6 +125,11 @@ public class PathUtils {
     }
   }
 
+  /** Return true if the str is a real number. Examples: 1.0; +1.0; -1.0; 0011; 011e3; +23e-3 */
+  public static boolean isRealNumber(String str) {
+    return PathVisitor.isRealNumber(str);
+  }
+
   public static boolean isStartWith(String deviceName, String storageGroup) {
     return deviceName.equals(storageGroup) || deviceName.startsWith(storageGroup + ".");
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
index 0c51b1a958..7b598c96ae 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.commons.auth.entity.PrivilegeType;
 import org.apache.iotdb.commons.conf.IoTDBConstant;
 import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.utils.PathUtils;
 import org.apache.iotdb.db.conf.IoTDBConfig;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.exception.sql.SQLParserException;
@@ -156,7 +157,6 @@ import org.apache.iotdb.tsfile.read.common.TimeRange;
 import org.apache.iotdb.tsfile.utils.Pair;
 
 import org.apache.commons.lang.StringEscapeUtils;
-import org.apache.commons.lang3.StringUtils;
 
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -1463,7 +1463,7 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
     if (nodeName.startsWith(TsFileConstant.BACK_QUOTE_STRING)
         && nodeName.endsWith(TsFileConstant.BACK_QUOTE_STRING)) {
       String unWrapped = nodeName.substring(1, nodeName.length() - 1);
-      if (StringUtils.isNumeric(unWrapped)
+      if (PathUtils.isRealNumber(unWrapped)
           || !TsFileConstant.IDENTIFIER_PATTERN.matcher(unWrapped).matches()) {
         return nodeName;
       }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
index 03bb951a74..e4c2673a34 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
@@ -22,6 +22,7 @@ import org.apache.iotdb.commons.auth.entity.PrivilegeType;
 import org.apache.iotdb.commons.conf.IoTDBConstant;
 import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.utils.PathUtils;
 import org.apache.iotdb.db.conf.IoTDBConfig;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.engine.trigger.executor.TriggerEvent;
@@ -2674,7 +2675,7 @@ public class IoTDBSqlVisitor extends IoTDBSqlParserBaseVisitor<Operator> {
     if (nodeName.startsWith(TsFileConstant.BACK_QUOTE_STRING)
         && nodeName.endsWith(TsFileConstant.BACK_QUOTE_STRING)) {
       String unWrapped = nodeName.substring(1, nodeName.length() - 1);
-      if (StringUtils.isNumeric(unWrapped)
+      if (PathUtils.isRealNumber(unWrapped)
           || !TsFileConstant.IDENTIFIER_PATTERN.matcher(unWrapped).matches()) {
         return nodeName;
       }
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/parser/PathVisitor.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/parser/PathVisitor.java
index 537fd5b9a1..d804f6ef1b 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/parser/PathVisitor.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/parser/PathVisitor.java
@@ -23,7 +23,7 @@ import org.apache.iotdb.db.qp.sql.PathParser.NodeNameContext;
 import org.apache.iotdb.db.qp.sql.PathParserBaseVisitor;
 import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
 
-import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
 
 import java.util.List;
 
@@ -64,7 +64,7 @@ public class PathVisitor extends PathParserBaseVisitor<String[]> {
     if (nodeName.startsWith(TsFileConstant.BACK_QUOTE_STRING)
         && nodeName.endsWith(TsFileConstant.BACK_QUOTE_STRING)) {
       String unWrapped = nodeName.substring(1, nodeName.length() - 1);
-      if (StringUtils.isNumeric(unWrapped)
+      if (isRealNumber(unWrapped)
           || !TsFileConstant.IDENTIFIER_PATTERN.matcher(unWrapped).matches()) {
         return nodeName;
       }
@@ -72,4 +72,29 @@ public class PathVisitor extends PathParserBaseVisitor<String[]> {
     }
     return nodeName;
   }
+
+  /** Return true if the str is a real number. Examples: 1.0; +1.0; -1.0; 0011; 011e3; +23e-3 */
+  public static boolean isRealNumber(String str) {
+    if (str.startsWith("+") || str.startsWith("-")) {
+      String removeSign = str.substring(1);
+      if (removeSign.startsWith("+") || removeSign.startsWith("-")) {
+        return false;
+      } else {
+        str = removeSign;
+      }
+    }
+    int index = 0;
+    // remove zeros
+    for (int i = 0, n = str.length(); i < n; i++) {
+      if (str.charAt(i) != '0') {
+        index = i;
+        break;
+      }
+    }
+    return NumberUtils.isCreatable(str.substring(index));
+  }
+
+  public static void main(String[] args) {
+    System.out.println(isRealNumber("+-1"));
+  }
 }