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 de1a622 [IOTDB-1524] Support SELECT ... INTO ... clause (#3614)
de1a622 is described below
commit de1a622d90a5e453083af2e01a9d5fc4ca5d702a
Author: Steve Yurong Su (宇荣) <[email protected]>
AuthorDate: Tue Jul 27 06:24:09 2021 -0500
[IOTDB-1524] Support SELECT ... INTO ... clause (#3614)
---
.../antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4 | 46 +-
docs/UserGuide/Advanced-Features/Select-Into.md | 235 +++++++++
docs/zh/UserGuide/Advanced-Features/Select-Into.md | 234 +++++++++
.../main/java/org/apache/iotdb/SessionExample.java | 14 +
.../resources/conf/iotdb-engine.properties | 9 +
.../org/apache/iotdb/db/auth/AuthorityChecker.java | 1 +
.../java/org/apache/iotdb/db/conf/IoTDBConfig.java | 14 +
.../org/apache/iotdb/db/conf/IoTDBDescriptor.java | 15 +-
.../selectinto/InsertTabletPlanGenerator.java | 229 +++++++++
.../selectinto/InsertTabletPlansIterator.java | 139 +++++
.../main/java/org/apache/iotdb/db/qp/Planner.java | 22 +-
.../apache/iotdb/db/qp/constant/SQLConstant.java | 4 +
.../org/apache/iotdb/db/qp/logical/Operator.java | 2 +
.../db/qp/logical/crud/SelectIntoOperator.java | 110 ++++
.../apache/iotdb/db/qp/physical/PhysicalPlan.java | 17 +-
.../iotdb/db/qp/physical/crud/SelectIntoPlan.java | 113 +++++
.../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java | 125 +++--
.../iotdb/db/qp/strategy/LogicalChecker.java | 5 +
.../iotdb/db/query/control/QueryTimeManager.java | 14 +
.../org/apache/iotdb/db/service/TSServiceImpl.java | 246 +++++----
.../iotdb/db/integration/IoTDBSelectIntoIT.java | 564 +++++++++++++++++++++
.../iotdb/db/qp/physical/PhysicalPlanTest.java | 33 +-
site/src/main/.vuepress/config.js | 6 +-
.../test/java/org/apache/iotdb/db/sql/Cases.java | 25 +
.../java/org/apache/iotdb/tsfile/utils/BitMap.java | 5 +
25 files changed, 2040 insertions(+), 187 deletions(-)
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
index 2e157d8..f3d1c5a 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
@@ -105,7 +105,7 @@ statement
| START TRIGGER triggerName=ID #startTrigger
| STOP TRIGGER triggerName=ID #stopTrigger
| SHOW TRIGGERS #showTriggers
- | selectClause fromClause whereClause? specialClause? #selectStatement
+ | selectClause intoClause? fromClause whereClause? specialClause?
#selectStatement
| CREATE (CONTINUOUS QUERY | CQ) continuousQueryName=ID
resampleClause?
cqSelectIntoClause #createContinuousQueryStatement
@@ -114,26 +114,35 @@ statement
;
selectClause
- : SELECT (LAST | topClause)? resultColumn (COMMA resultColumn)*
- ;
+ : SELECT (LAST | topClause)? resultColumn (COMMA resultColumn)*
+ ;
resultColumn
- : expression (AS ID)?
- ;
+ : expression (AS ID)?
+ ;
expression
- : LR_BRACKET unary=expression RR_BRACKET
- | (PLUS | MINUS) unary=expression
- | leftExpression=expression (STAR | DIV | MOD) rightExpression=expression
- | leftExpression=expression (PLUS | MINUS) rightExpression=expression
- | functionName=suffixPath LR_BRACKET expression (COMMA expression)*
functionAttribute* RR_BRACKET
- | suffixPath
- | literal=SINGLE_QUOTE_STRING_LITERAL
- ;
+ : LR_BRACKET unary=expression RR_BRACKET
+ | (PLUS | MINUS) unary=expression
+ | leftExpression=expression (STAR | DIV | MOD) rightExpression=expression
+ | leftExpression=expression (PLUS | MINUS) rightExpression=expression
+ | functionName=suffixPath LR_BRACKET expression (COMMA expression)*
functionAttribute* RR_BRACKET
+ | suffixPath
+ | literal=SINGLE_QUOTE_STRING_LITERAL
+ ;
functionAttribute
- : COMMA functionAttributeKey=stringLiteral OPERATOR_EQ
functionAttributeValue=stringLiteral
- ;
+ : COMMA functionAttributeKey=stringLiteral OPERATOR_EQ
functionAttributeValue=stringLiteral
+ ;
+
+intoClause
+ : INTO intoPath (COMMA intoPath)*
+ ;
+
+intoPath
+ : fullPath
+ | nodeNameWithoutStar (DOT nodeNameWithoutStar)*
+ ;
alias
: LR_BRACKET ID RR_BRACKET
@@ -339,12 +348,7 @@ resampleClause
: RESAMPLE (EVERY DURATION)? (FOR DURATION)?;
cqSelectIntoClause
- : BEGIN
- selectClause
- INTO (fullPath | nodeNameWithoutStar)
- fromClause
- cqGroupByTimeClause
- END
+ : BEGIN selectClause INTO intoPath fromClause cqGroupByTimeClause END
;
cqGroupByTimeClause
diff --git a/docs/UserGuide/Advanced-Features/Select-Into.md
b/docs/UserGuide/Advanced-Features/Select-Into.md
new file mode 100644
index 0000000..8a914a1
--- /dev/null
+++ b/docs/UserGuide/Advanced-Features/Select-Into.md
@@ -0,0 +1,235 @@
+<!--
+
+ 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.
+
+-->
+
+
+
+# Query Write-back (SELECT INTO)
+
+The `SELECT ... INTO ...` statement copies data from query result set into
target time series.
+
+
+
+## SQL
+
+### Syntax
+
+**The following is the syntax definition of the `select` statement:**
+
+```sql
+selectClause
+intoClause?
+fromClause
+whereClause?
+specialClause?
+```
+
+If the `intoClause` is removed, then the `select` statement is a simple query
statement.
+
+The `intoClause` is the mark clause for query write-back.
+
+
+
+**The following is the definition of the `intoClause`:**
+
+```sql
+intoClause
+ : INTO intoPath (COMMA intoPath)*
+ ;
+
+intoPath
+ : fullPath
+ | nodeNameWithoutStar (DOT nodeNameWithoutStar)*
+ ;
+```
+
+There are 2 ways to specify target paths (`intoPath`).
+
+* Full target series name starting with `root`.
+
+ * Example:
+
+ ```sql
+ select s1, s1
+ into root.sg.d1.t1, root.sg.d1.t2
+ from root.sg.d1
+ ```
+
+* Suffix path does not start with `root`. In this case, the target series name
equals to the series prefix path in the `from` clause + the suffix path.
+
+ * Example:
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ ```
+
+ which equals to:
+
+ ```sql
+ select s1, s1
+ into root.sg.d1.t1, root.sg.d1.t2
+ from root.sg.d1
+ ```
+
+
+
+**In `intoPath`, you can also use `${i}` to represent part of the prefix path
in the `from` clause. **
+
+For example, for the path `root.sg1.d1.v1`, `${1}` means `sg1`, `${2}` means
`d1`, and `${3}` means `v1`.
+
+
+ * Example:
+
+ ```sql
+ select s1, s1, s1
+ into ${1}_t1, ${2}, root.${2}.${1}.t2
+ from root.sg.d1
+ ```
+
+ which equals to:
+
+ ```sql
+ select s1, s1, s1
+ into root.sg.d1.sg_t1, root.sg.d1.d1, root.d1.sg.t2
+ from root.sg.d1
+ ```
+
+
+
+### Supported Query Types
+
+**Note that except for the following types of queries, other types of queries
(such as `LAST` queries) are not supported. **
+
+* Raw time series query
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ ```
+
+* Time series generating function query(including UDF query)
+
+ ```sql
+ select s1, sin(s2)
+ into t1, t2
+ from root.sg.d1
+ ```
+
+* Arithmetic query
+
+ ```sql
+ select s1, sin(s2), s1 + s3
+ into t1, t2, t3
+ from root.sg.d1
+ ```
+
+* Fill query
+
+ ```sql
+ select s1
+ into fill_s1
+ from root.sg.d1
+ where time = 10
+ fill(float [linear, 1ms, 1ms])
+ ```
+
+* Group-by query
+
+ ```sql
+ select count(s1)
+ into group_by_s1
+ from root.sg.d1
+ group by ([1, 5), 1ms)
+ ```
+
+* Group-by-fill query
+
+ ```sql
+ select last_value(s1)
+ into group_by_fill_s1
+ from root.sg.d1
+ group by ([1, 10),1ms)
+ fill (float[PREVIOUS])
+ ```
+
+
+
+### Special Cluases Supported in Queries
+
+**Note that except for the following clauses, other query clauses (such as
`DESC`, `SOFFSET`, etc.) are not supported. **
+
+* Value filter
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ where s1 > 0 and s2 < 0
+ ```
+
+* Time filter
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ where time > 0
+ ```
+
+* LIMIT / OFFSET
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ limit 5 offset 1000
+ ```
+
+
+
+### Other Restrictions
+
+* The number of source series in the `select` clause and the number of target
series in the `into` clause must be the same.
+* The `select *` clause is not supported.
+* The target series in the `into` clause do not need to be created in advance.
+* When the target series in the `into` clause already exist, you need to
ensure that the source series in the `select` clause and the target series in
the `into` clause have the same data types.
+* The target series in the `into` clause must be different from each other.
+* Only one prefix path of a series is allowed in the `from` clause.
+
+
+
+## User Permission Management
+
+The user must have the following permissions to execute a query write-back
statement:
+
+* All `READ_TIMESERIES` permissions for the source series in the `select`
clause
+* All `INSERT_TIMESERIES` permissions for the target series in the `into`
clause
+
+For more user permissions related content, please refer to [Account Management
Statements](../Administration-Management/Administration.md).
+
+
+
+## Configurable Properties
+
+* `select_into_insert_tablet_plan_row_limit`: The maximum number of rows can
be processed in one insert-tablet-plan when executing select-into statements.
10000 by default.
+
diff --git a/docs/zh/UserGuide/Advanced-Features/Select-Into.md
b/docs/zh/UserGuide/Advanced-Features/Select-Into.md
new file mode 100644
index 0000000..f3f9460
--- /dev/null
+++ b/docs/zh/UserGuide/Advanced-Features/Select-Into.md
@@ -0,0 +1,234 @@
+<!--
+
+ 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.
+
+-->
+
+
+
+# 查询写回(SELECT ... INTO ...)
+
+`SELECT ... INTO ...` 语句允许您将查询结果集写回到指定序列上。
+
+
+
+## SQL
+
+### 语法
+
+**下面是 `select` 语句的语法定义:**
+
+```sql
+selectClause
+intoClause?
+fromClause
+whereClause?
+specialClause?
+```
+
+如果去除 `intoClause` 子句,那么 `select` 语句即是单纯的查询语句。
+
+`intoClause` 子句是写回功能的标记语句。
+
+
+
+**下面是 `intoClause` 子句的定义:**
+
+```sql
+intoClause
+ : INTO intoPath (COMMA intoPath)*
+ ;
+
+intoPath
+ : fullPath
+ | nodeNameWithoutStar (DOT nodeNameWithoutStar)*
+ ;
+```
+
+`intoPath`(目标序列)支持两种方式指定:
+
+* 以 `root` 开头的完整序列名指定
+
+ * 例子:
+
+ ```sql
+ select s1, s1
+ into root.sg.d1.t1, root.sg.d1.t2
+ from root.sg.d1
+ ```
+
+* 不以 `root` 开头的部分序列名指定,此时目标序列由 `from` 子句中的序列前缀和`intoPath`拼接而成
+
+ * 例子:
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ ```
+
+ 这等价于
+
+ ```sql
+ select s1, s1
+ into root.sg.d1.t1, root.sg.d1.t2
+ from root.sg.d1
+ ```
+
+
+
+**在`intoPath` 中,您还可以使用 `${i}`风格的路径匹配符来表示`from`子句中的部分路径。**
+
+比如,对于路径`root.sg1.d1.v1`而言,`${1}`表示`sg1`,`${2}`表示`d1`,`${3}`表示`v1`。
+
+
+ * 例子:
+
+ ```sql
+ select s1, s1, s1
+ into ${1}_t1, ${2}, root.${2}.${1}.t2
+ from root.sg.d1
+ ```
+
+ 这等价于
+
+ ```sql
+ select s1, s1, s1
+ into root.sg.d1.sg_t1, root.sg.d1.d1, root.d1.sg.t2
+ from root.sg.d1
+ ```
+
+
+
+### 支持写回的查询类型
+
+**注意,除了下述类型的查询,其余类型的查询(如`LAST`查询)都不被支持。**
+
+* 原始序列查询
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ ```
+
+* 时间序列生成函数查询(UDF查询)
+
+ ```sql
+ select s1, sin(s2)
+ into t1, t2
+ from root.sg.d1
+ ```
+
+* 数学表达式查询
+
+ ```sql
+ select s1, sin(s2), s1 + s3
+ into t1, t2, t3
+ from root.sg.d1
+ ```
+
+* Fill 查询
+
+ ```sql
+ select s1
+ into fill_s1
+ from root.sg.d1
+ where time = 10
+ fill(float [linear, 1ms, 1ms])
+ ```
+
+* Group By 查询
+
+ ```sql
+ select count(s1)
+ into group_by_s1
+ from root.sg.d1
+ group by ([1, 5), 1ms)
+ ```
+
+* Group By Fill 查询
+
+ ```sql
+ select last_value(s1)
+ into group_by_fill_s1
+ from root.sg.d1
+ group by ([1, 10),1ms)
+ fill (float[PREVIOUS])
+ ```
+
+
+
+### 支持写回的查询子句
+
+**注意,除了下述子句,其余查询子句(如 `DESC` / `SOFFSET` 等)都不被支持。**
+
+* 支持值过滤
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ where s1 > 0 and s2 < 0
+ ```
+
+* 支持时间过滤
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ where time > 0
+ ```
+
+* LIMIT / OFFSET
+
+ ```sql
+ select s1, s1
+ into t1, t2
+ from root.sg.d1
+ limit 5 offset 1000
+ ```
+
+
+
+### 其他限制
+
+* `select`子句中的源序列和`into`子句中的目标序列数量必须相同
+* `select`子句不支持带 `*` 查询
+* `into`子句中的目标序列不必预先创建(可使用自动创建schema功能)
+* 当`into`子句中的目标序列已存在时,您需要保证`select`子句中的源序列和`into`子句中的目标序列的数据类型一致
+* `into`子句中的目标序列必须是互不相同的
+* `from`子句只允许有一列序列前缀
+
+
+
+## 权限
+
+用户必须有下列权限才能正常执行查询写回语句:
+
+* 所有 `select` 子句中源序列的 `READ_TIMESERIES` 权限
+* 所有 `into` 子句中目标序列 `INSERT_TIMESERIES` 权限
+
+更多用户权限相关的内容,请参考[权限管理语句](../Administration-Management/Administration.md)。
+
+
+
+## 配置参数
+
+* `select_into_insert_tablet_plan_row_limit`:执行 select-into 语句时,一个
insert-tablet-plan 中可以处理的最大行数。 默认为 10000。
diff --git a/example/session/src/main/java/org/apache/iotdb/SessionExample.java
b/example/session/src/main/java/org/apache/iotdb/SessionExample.java
index e3f1243..0f1ad8b 100644
--- a/example/session/src/main/java/org/apache/iotdb/SessionExample.java
+++ b/example/session/src/main/java/org/apache/iotdb/SessionExample.java
@@ -74,6 +74,7 @@ public class SessionExample {
insertTablet();
insertTablets();
insertRecords();
+ selectInto();
createAndDropContinuousQueries();
nonQuery();
query();
@@ -487,6 +488,19 @@ public class SessionExample {
}
}
+ private static void selectInto() throws IoTDBConnectionException,
StatementExecutionException {
+ session.executeNonQueryStatement(
+ "select s1, s2, s3 into into_s1, into_s2, into_s3 from root.sg1.d1");
+
+ SessionDataSet dataSet =
+ session.executeQueryStatement("select into_s1, into_s2, into_s3 from
root.sg1.d1");
+ System.out.println(dataSet.getColumnNames());
+ while (dataSet.hasNext()) {
+ System.out.println(dataSet.next());
+ }
+ dataSet.closeOperationHandle();
+ }
+
private static void deleteData() throws IoTDBConnectionException,
StatementExecutionException {
String path = ROOT_SG1_D1_S1;
long deleteTime = 99;
diff --git a/server/src/assembly/resources/conf/iotdb-engine.properties
b/server/src/assembly/resources/conf/iotdb-engine.properties
index 63e79d4..c8fb395 100644
--- a/server/src/assembly/resources/conf/iotdb-engine.properties
+++ b/server/src/assembly/resources/conf/iotdb-engine.properties
@@ -837,6 +837,15 @@ timestamp_precision=ms
# continuous_query_min_every_interval=1s
####################
+### Select-Into Configuration
+####################
+
+# The maximum number of rows can be processed in insert-tablet-plan when
executing select-into statements.
+# When <= 0, use 10000.
+# Datatype: int
+# select_into_insert_tablet_plan_row_limit=10000
+
+####################
### Index Configuration
####################
diff --git
a/server/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
b/server/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
index c0f86b3..ac89e4b 100644
--- a/server/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
+++ b/server/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
@@ -131,6 +131,7 @@ public class AuthorityChecker {
case LAST:
case FILL:
case GROUP_BY_FILL:
+ case SELECT_INTO:
return PrivilegeType.READ_TIMESERIES.ordinal();
case INSERT:
case LOAD_DATA:
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
index 3979903..1ed7acd 100644
--- a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
+++ b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
@@ -563,6 +563,12 @@ public class IoTDBConfig {
*/
private long continuousQueryMinimumEveryInterval = 1000;
+ /**
+ * The maximum number of rows can be processed in insert-tablet-plan when
executing select-into
+ * statements.
+ */
+ private int selectIntoInsertTabletPlanRowLimit = 10000;
+
private MergeFileStrategy mergeFileStrategy =
MergeFileStrategy.MAX_SERIES_NUM;
/** Default system file storage is in local file system (unsupported) */
@@ -1488,6 +1494,14 @@ public class IoTDBConfig {
this.continuousQueryMinimumEveryInterval = minimumEveryInterval;
}
+ public int getSelectIntoInsertTabletPlanRowLimit() {
+ return selectIntoInsertTabletPlanRowLimit;
+ }
+
+ public void setSelectIntoInsertTabletPlanRowLimit(int
selectIntoInsertTabletPlanRowLimit) {
+ this.selectIntoInsertTabletPlanRowLimit =
selectIntoInsertTabletPlanRowLimit;
+ }
+
public int getMergeWriteThroughputMbPerSec() {
return mergeWriteThroughputMbPerSec;
}
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
index 1b0e4eb..149ae46 100644
--- a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
+++ b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
@@ -46,7 +46,7 @@ public class IoTDBDescriptor {
private static final Logger logger =
LoggerFactory.getLogger(IoTDBDescriptor.class);
- private IoTDBConfig conf = new IoTDBConfig();
+ private final IoTDBConfig conf = new IoTDBConfig();
protected IoTDBDescriptor() {
loadProps();
@@ -741,6 +741,12 @@ public class IoTDBDescriptor {
conf.setAdminPassword(properties.getProperty("admin_password",
conf.getAdminPassword()));
+ conf.setSelectIntoInsertTabletPlanRowLimit(
+ Integer.parseInt(
+ properties.getProperty(
+ "select_into_insert_tablet_plan_row_limit",
+
String.valueOf(conf.getSelectIntoInsertTabletPlanRowLimit()))));
+
// At the same time, set TSFileConfig
TSFileDescriptor.getInstance()
.getConfig()
@@ -1088,6 +1094,13 @@ public class IoTDBDescriptor {
properties.getProperty(
"merge_write_throughput_mb_per_sec",
Integer.toString(conf.getMergeWriteThroughputMbPerSec()))));
+
+ // update insert-tablet-plan's row limit for select-into
+ conf.setSelectIntoInsertTabletPlanRowLimit(
+ Integer.parseInt(
+ properties.getProperty(
+ "select_into_insert_tablet_plan_row_limit",
+
String.valueOf(conf.getSelectIntoInsertTabletPlanRowLimit()))));
} catch (Exception e) {
throw new QueryProcessException(String.format("Fail to reload
configuration because %s", e));
}
diff --git
a/server/src/main/java/org/apache/iotdb/db/engine/selectinto/InsertTabletPlanGenerator.java
b/server/src/main/java/org/apache/iotdb/db/engine/selectinto/InsertTabletPlanGenerator.java
new file mode 100644
index 0000000..4b12b26
--- /dev/null
+++
b/server/src/main/java/org/apache/iotdb/db/engine/selectinto/InsertTabletPlanGenerator.java
@@ -0,0 +1,229 @@
+/*
+ * 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.engine.selectinto;
+
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.physical.crud.InsertTabletPlan;
+import org.apache.iotdb.tsfile.exception.write.UnSupportedDataTypeException;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.common.Field;
+import org.apache.iotdb.tsfile.read.common.RowRecord;
+import org.apache.iotdb.tsfile.utils.Binary;
+import org.apache.iotdb.tsfile.utils.BitMap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** internallyConstructNewPlan -> collectRowRecord * N ->
generateInsertTabletPlan */
+public class InsertTabletPlanGenerator {
+
+ private final String targetDevice;
+ // the index of column in InsertTabletPlan -> the index of output column of
query data set
+ private final List<Integer> queryDataSetIndexes;
+ // the index of column in InsertTabletPlan -> the measurement id of the
column
+ private final List<String> targetMeasurementIds;
+
+ private final int tabletRowLimit;
+
+ // the following fields are used to construct plan
+ private int rowCount;
+ private long[] times;
+ private Object[] columns;
+ private BitMap[] bitMaps;
+ private TSDataType[] dataTypes;
+
+ private int numberOfInitializedColumns;
+
+ public InsertTabletPlanGenerator(String targetDevice, int tabletRowLimit) {
+ this.targetDevice = targetDevice;
+ queryDataSetIndexes = new ArrayList<>();
+ targetMeasurementIds = new ArrayList<>();
+
+ this.tabletRowLimit = tabletRowLimit;
+ }
+
+ public void collectTargetPathInformation(String targetMeasurementId, int
queryDataSetIndex) {
+ targetMeasurementIds.add(targetMeasurementId);
+ queryDataSetIndexes.add(queryDataSetIndex);
+ }
+
+ public void internallyConstructNewPlan() {
+ rowCount = 0;
+ times = new long[tabletRowLimit];
+ columns = new Object[targetMeasurementIds.size()];
+ bitMaps = new BitMap[targetMeasurementIds.size()];
+ for (int i = 0; i < bitMaps.length; ++i) {
+ bitMaps[i] = new BitMap(tabletRowLimit);
+ bitMaps[i].markAll();
+ }
+ dataTypes = new TSDataType[targetMeasurementIds.size()];
+
+ numberOfInitializedColumns = 0;
+ }
+
+ public void collectRowRecord(RowRecord rowRecord) {
+ if (numberOfInitializedColumns != columns.length) {
+ List<Integer> initializedDataTypeIndexes = trySetDataTypes(rowRecord);
+ tryInitColumns(initializedDataTypeIndexes);
+ numberOfInitializedColumns += initializedDataTypeIndexes.size();
+ }
+
+ times[rowCount] = rowRecord.getTimestamp();
+
+ for (int i = 0; i < columns.length; ++i) {
+ Field field = rowRecord.getFields().get(queryDataSetIndexes.get(i));
+
+ // if the field is NULL
+ if (field == null || field.getDataType() == null) {
+ // bit in bitMaps are marked as 1 (NULL) by default
+ continue;
+ }
+
+ bitMaps[i].unmark(rowCount);
+ switch (field.getDataType()) {
+ case INT32:
+ ((int[]) columns[i])[rowCount] = field.getIntV();
+ break;
+ case INT64:
+ ((long[]) columns[i])[rowCount] = field.getLongV();
+ break;
+ case FLOAT:
+ ((float[]) columns[i])[rowCount] = field.getFloatV();
+ break;
+ case DOUBLE:
+ ((double[]) columns[i])[rowCount] = field.getDoubleV();
+ break;
+ case BOOLEAN:
+ ((boolean[]) columns[i])[rowCount] = field.getBoolV();
+ break;
+ case TEXT:
+ ((Binary[]) columns[i])[rowCount] = field.getBinaryV();
+ break;
+ default:
+ throw new UnSupportedDataTypeException(
+ String.format(
+ "data type %s is not supported when convert data at client",
+ field.getDataType()));
+ }
+ }
+
+ ++rowCount;
+ }
+
+ private List<Integer> trySetDataTypes(RowRecord rowRecord) {
+ List<Integer> initializedDataTypeIndexes = new ArrayList<>();
+ List<Field> fields = rowRecord.getFields();
+
+ for (int i = 0; i < dataTypes.length; ++i) {
+ // if the data type is already set
+ if (dataTypes[i] != null) {
+ continue;
+ }
+
+ // get the field index of the row record
+ int queryDataSetIndex = queryDataSetIndexes.get(i);
+ // if the field is not null
+ if (fields.get(queryDataSetIndex) != null
+ && fields.get(queryDataSetIndex).getDataType() != null) {
+ // set the data type to the field type
+ dataTypes[i] = fields.get(queryDataSetIndex).getDataType();
+ initializedDataTypeIndexes.add(i);
+ }
+ }
+
+ for (int i = 0; i < dataTypes.length; ++i) {
+ if (dataTypes[i] == null && fields.get(i) != null &&
fields.get(i).getDataType() != null) {
+ dataTypes[i] = fields.get(i).getDataType();
+ initializedDataTypeIndexes.add(i);
+ }
+ }
+ return initializedDataTypeIndexes;
+ }
+
+ private void tryInitColumns(List<Integer> initializedDataTypeIndexes) {
+ for (int i : initializedDataTypeIndexes) {
+ switch (dataTypes[i]) {
+ case BOOLEAN:
+ columns[i] = new boolean[tabletRowLimit];
+ break;
+ case INT32:
+ columns[i] = new int[tabletRowLimit];
+ break;
+ case INT64:
+ columns[i] = new long[tabletRowLimit];
+ break;
+ case FLOAT:
+ columns[i] = new float[tabletRowLimit];
+ break;
+ case DOUBLE:
+ columns[i] = new double[tabletRowLimit];
+ break;
+ case TEXT:
+ columns[i] = new Binary[tabletRowLimit];
+ break;
+ default:
+ throw new UnSupportedDataTypeException(
+ String.format(
+ "data type %s is not supported when convert data at client",
dataTypes[i]));
+ }
+ }
+ }
+
+ public InsertTabletPlan generateInsertTabletPlan() throws
IllegalPathException {
+ List<String> nonEmptyColumnNames = new ArrayList<>();
+
+ int countOfNonEmptyColumns = 0;
+ for (int i = 0; i < columns.length; ++i) {
+ if (columns[i] == null) {
+ continue;
+ }
+
+ nonEmptyColumnNames.add(targetMeasurementIds.get(i));
+ times[countOfNonEmptyColumns] = times[i];
+ columns[countOfNonEmptyColumns] = columns[i];
+ bitMaps[countOfNonEmptyColumns] = bitMaps[i];
+ dataTypes[countOfNonEmptyColumns] = dataTypes[i];
+
+ ++countOfNonEmptyColumns;
+ }
+
+ InsertTabletPlan insertTabletPlan =
+ new InsertTabletPlan(new PartialPath(targetDevice),
nonEmptyColumnNames);
+
+ insertTabletPlan.setAligned(false);
+ insertTabletPlan.setRowCount(rowCount);
+
+ if (countOfNonEmptyColumns == columns.length) {
+ insertTabletPlan.setTimes(times);
+ insertTabletPlan.setColumns(columns);
+ insertTabletPlan.setBitMaps(bitMaps);
+ insertTabletPlan.setDataTypes(dataTypes);
+ } else {
+ insertTabletPlan.setTimes(Arrays.copyOf(times, countOfNonEmptyColumns));
+ insertTabletPlan.setColumns(Arrays.copyOf(columns,
countOfNonEmptyColumns));
+ insertTabletPlan.setBitMaps(Arrays.copyOf(bitMaps,
countOfNonEmptyColumns));
+ insertTabletPlan.setDataTypes(Arrays.copyOf(dataTypes,
countOfNonEmptyColumns));
+ }
+
+ return insertTabletPlan;
+ }
+}
diff --git
a/server/src/main/java/org/apache/iotdb/db/engine/selectinto/InsertTabletPlansIterator.java
b/server/src/main/java/org/apache/iotdb/db/engine/selectinto/InsertTabletPlansIterator.java
new file mode 100644
index 0000000..95051ff
--- /dev/null
+++
b/server/src/main/java/org/apache/iotdb/db/engine/selectinto/InsertTabletPlansIterator.java
@@ -0,0 +1,139 @@
+/*
+ * 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.engine.selectinto;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.physical.crud.InsertTabletPlan;
+import org.apache.iotdb.db.qp.physical.crud.QueryPlan;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.tsfile.read.common.RowRecord;
+import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class InsertTabletPlansIterator {
+
+ private static final Pattern leveledPathNodePattern =
Pattern.compile("\\$\\{\\w+}");
+
+ private final QueryPlan queryPlan;
+ private final QueryDataSet queryDataSet;
+
+ private final PartialPath fromPath;
+ private final List<PartialPath> intoPaths;
+
+ private final int tabletRowLimit;
+
+ private InsertTabletPlanGenerator[] insertTabletPlanGenerators;
+
+ public InsertTabletPlansIterator(
+ QueryPlan queryPlan,
+ QueryDataSet queryDataSet,
+ PartialPath fromPath,
+ List<PartialPath> intoPaths)
+ throws IllegalPathException {
+ this.queryPlan = queryPlan;
+ this.queryDataSet = queryDataSet;
+ this.fromPath = fromPath;
+ this.intoPaths = intoPaths;
+
+ tabletRowLimit =
+
IoTDBDescriptor.getInstance().getConfig().getSelectIntoInsertTabletPlanRowLimit();
+
+ generateActualIntoPaths();
+ constructInsertTabletPlanGenerators();
+ }
+
+ private void generateActualIntoPaths() throws IllegalPathException {
+ for (int i = 0; i < intoPaths.size(); ++i) {
+ intoPaths.set(i, generateActualIntoPath(i));
+ }
+ }
+
+ private PartialPath generateActualIntoPath(int index) throws
IllegalPathException {
+ String[] nodes = fromPath.getNodes();
+ StringBuffer sb = new StringBuffer();
+ Matcher m =
leveledPathNodePattern.matcher(intoPaths.get(index).getFullPath());
+ while (m.find()) {
+ String param = m.group();
+ String value = nodes[Integer.parseInt(param.substring(2, param.length()
- 1).trim())];
+ m.appendReplacement(sb, value == null ? "" : value);
+ }
+ m.appendTail(sb);
+ return new PartialPath(sb.toString());
+ }
+
+ private void constructInsertTabletPlanGenerators() {
+ final Map<String, Integer> sourcePathToQueryDataSetIndex =
queryPlan.getPathToIndex();
+ final List<ResultColumn> resultColumns = queryPlan.getResultColumns();
+
+ Map<String, InsertTabletPlanGenerator> deviceToPlanGeneratorMap = new
HashMap<>();
+ for (int i = 0, intoPathsSize = intoPaths.size(); i < intoPathsSize; i++) {
+ String device = intoPaths.get(i).getDevice();
+ if (!deviceToPlanGeneratorMap.containsKey(device)) {
+ deviceToPlanGeneratorMap.put(device, new
InsertTabletPlanGenerator(device, tabletRowLimit));
+ }
+ deviceToPlanGeneratorMap
+ .get(device)
+ .collectTargetPathInformation(
+ intoPaths.get(i).getMeasurement(),
+
sourcePathToQueryDataSetIndex.get(resultColumns.get(i).getResultColumnName()));
+ }
+
+ insertTabletPlanGenerators =
+ deviceToPlanGeneratorMap.values().toArray(new
InsertTabletPlanGenerator[0]);
+ }
+
+ public boolean hasNext() throws IOException {
+ return queryDataSet.hasNext();
+ }
+
+ public List<InsertTabletPlan> next() throws IOException,
IllegalPathException {
+ for (InsertTabletPlanGenerator insertTabletPlanGenerator :
insertTabletPlanGenerators) {
+ insertTabletPlanGenerator.internallyConstructNewPlan();
+ }
+
+ collectRowRecordIntoInsertTabletPlanGenerators();
+
+ List<InsertTabletPlan> insertTabletPlans = new ArrayList<>();
+ for (InsertTabletPlanGenerator insertTabletPlanGenerator :
insertTabletPlanGenerators) {
+
insertTabletPlans.add(insertTabletPlanGenerator.generateInsertTabletPlan());
+ }
+ return insertTabletPlans;
+ }
+
+ private void collectRowRecordIntoInsertTabletPlanGenerators() throws
IOException {
+ int count = 0;
+ while (queryDataSet.hasNext() && count < tabletRowLimit) {
+ RowRecord rowRecord = queryDataSet.next();
+ for (InsertTabletPlanGenerator insertTabletPlanGenerator :
insertTabletPlanGenerators) {
+ insertTabletPlanGenerator.collectRowRecord(rowRecord);
+ }
+ ++count;
+ }
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/Planner.java
b/server/src/main/java/org/apache/iotdb/db/qp/Planner.java
index 94b43ff..7d469a4 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/Planner.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/Planner.java
@@ -26,6 +26,7 @@ import
org.apache.iotdb.db.exception.query.QueryProcessException;
import org.apache.iotdb.db.qp.logical.Operator;
import org.apache.iotdb.db.qp.logical.crud.FilterOperator;
import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
+import org.apache.iotdb.db.qp.logical.crud.SelectIntoOperator;
import org.apache.iotdb.db.qp.logical.crud.WhereComponent;
import org.apache.iotdb.db.qp.physical.PhysicalPlan;
import org.apache.iotdb.db.qp.physical.crud.GroupByTimePlan;
@@ -42,9 +43,6 @@ import org.apache.iotdb.service.rpc.thrift.TSRawDataQueryReq;
import java.time.ZoneId;
-import static org.apache.iotdb.db.qp.logical.Operator.OperatorType.QUERY;
-import static org.apache.iotdb.db.qp.logical.Operator.OperatorType.QUERY_INDEX;
-
/** provide a integration method for other user. */
public class Planner {
@@ -109,9 +107,15 @@ public class Planner {
*/
protected Operator logicalOptimize(Operator operator)
throws LogicalOperatorException, PathNumOverLimitException {
- return operator.getType().equals(QUERY) ||
operator.getType().equals(QUERY_INDEX)
- ? optimizeQueryOperator((QueryOperator) operator)
- : operator;
+ switch (operator.getType()) {
+ case QUERY:
+ case QUERY_INDEX:
+ return optimizeQueryOperator((QueryOperator) operator);
+ case SELECT_INTO:
+ return optimizeSelectIntoOperator((SelectIntoOperator) operator);
+ default:
+ return operator;
+ }
}
/**
@@ -138,6 +142,12 @@ public class Planner {
return root;
}
+ private Operator optimizeSelectIntoOperator(SelectIntoOperator operator)
+ throws PathNumOverLimitException, LogicalOperatorException {
+
operator.setQueryOperator(optimizeQueryOperator(operator.getQueryOperator()));
+ return operator;
+ }
+
@TestOnly
public PhysicalPlan parseSQLToPhysicalPlan(String sqlStr) throws
QueryProcessException {
return parseSQLToPhysicalPlan(sqlStr, ZoneId.systemDefault());
diff --git
a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
index 7509bf0..bf2e9c2 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
@@ -179,6 +179,8 @@ public class SQLConstant {
public static final int TOK_CONTINUOUS_QUERY_DROP = 107;
public static final int TOK_SHOW_CONTINUOUS_QUERIES = 108;
+ public static final int TOK_SELECT_INTO = 109;
+
public static final Map<Integer, String> tokenNames = new HashMap<>();
public static String[] getSingleRootArray() {
@@ -247,6 +249,8 @@ public class SQLConstant {
tokenNames.put(TOK_CONTINUOUS_QUERY_CREATE, "TOK_CONTINUOUS_QUERY_CREATE");
tokenNames.put(TOK_CONTINUOUS_QUERY_DROP, "TOK_CONTINUOUS_QUERY_DROP");
tokenNames.put(TOK_SHOW_CONTINUOUS_QUERIES, "TOK_SHOW_CONTINUOUS_QUERIES");
+
+ tokenNames.put(TOK_SELECT_INTO, "TOK_SELECT_INTO");
}
public static boolean isReservedPath(PartialPath pathStr) {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
index 56eba91..53234cb 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
@@ -126,6 +126,8 @@ public abstract class Operator {
UDAF,
UDTF,
+ SELECT_INTO,
+
CREATE_FUNCTION,
DROP_FUNCTION,
diff --git
a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SelectIntoOperator.java
b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SelectIntoOperator.java
new file mode 100644
index 0000000..a91fd37
--- /dev/null
+++
b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SelectIntoOperator.java
@@ -0,0 +1,110 @@
+/*
+ * 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.qp.logical.crud;
+
+import org.apache.iotdb.db.exception.query.LogicalOperatorException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.constant.SQLConstant;
+import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.crud.QueryPlan;
+import org.apache.iotdb.db.qp.physical.crud.SelectIntoPlan;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
+
+import java.util.HashSet;
+import java.util.List;
+
+public class SelectIntoOperator extends Operator {
+
+ private QueryOperator queryOperator;
+
+ private List<PartialPath> intoPaths;
+
+ public SelectIntoOperator() {
+ super(SQLConstant.TOK_SELECT_INTO);
+ operatorType = OperatorType.SELECT_INTO;
+ }
+
+ @Override
+ public PhysicalPlan generatePhysicalPlan(PhysicalGenerator generator)
+ throws QueryProcessException {
+ QueryPlan queryPlan = (QueryPlan)
queryOperator.generatePhysicalPlan(generator);
+ if (intoPaths.size() != queryPlan.getPaths().size()) {
+ throw new QueryProcessException(
+ "select into: the number of source paths and the number of target
paths should be the same.");
+ }
+ return new SelectIntoPlan(
+ queryPlan, queryOperator.getFromComponent().getPrefixPaths().get(0),
intoPaths);
+ }
+
+ public void check() throws LogicalOperatorException {
+ queryOperator.check();
+
+ if (intoPaths.size() > new HashSet<>(intoPaths).size()) {
+ throw new LogicalOperatorException(
+ "select into: target paths in into clause should be different.");
+ }
+
+ if (queryOperator.isAlignByDevice()) {
+ throw new LogicalOperatorException("select into: align by device clauses
are not supported.");
+ }
+
+ // disable align
+ if (!queryOperator.isAlignByTime()) {
+ throw new LogicalOperatorException("select into: disable align clauses
are not supported.");
+ }
+
+ if (queryOperator instanceof LastQueryOperator) {
+ throw new LogicalOperatorException("select into: last clauses are not
supported.");
+ }
+
+ if (queryOperator instanceof AggregationQueryOperator
+ && !(queryOperator instanceof GroupByQueryOperator)) {
+ throw new LogicalOperatorException("select into: aggregation queries are
not supported.");
+ }
+
+ if (queryOperator.getSpecialClauseComponent() != null) {
+ SpecialClauseComponent specialClauseComponent =
queryOperator.getSpecialClauseComponent();
+ if (specialClauseComponent.hasSlimit()) {
+ throw new LogicalOperatorException("select into: slimit clauses are
not supported.");
+ }
+ if (specialClauseComponent.getSeriesOffset() > 0) {
+ throw new LogicalOperatorException("select into: soffset clauses are
not supported.");
+ }
+ if (!specialClauseComponent.isAscending()) {
+ throw new LogicalOperatorException(
+ "select into: order by time desc clauses are not supported.");
+ }
+ }
+ }
+
+ public void setQueryOperator(QueryOperator queryOperator) {
+ this.queryOperator = queryOperator;
+ }
+
+ public QueryOperator getQueryOperator() {
+ return queryOperator;
+ }
+
+ public void setIntoPaths(List<PartialPath> intoPaths) {
+ this.intoPaths = intoPaths;
+ }
+}
diff --git
a/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java
b/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java
index 2c87217..a018f1c 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java
@@ -29,6 +29,7 @@ import
org.apache.iotdb.db.qp.physical.crud.InsertMultiTabletPlan;
import org.apache.iotdb.db.qp.physical.crud.InsertRowPlan;
import org.apache.iotdb.db.qp.physical.crud.InsertRowsPlan;
import org.apache.iotdb.db.qp.physical.crud.InsertTabletPlan;
+import org.apache.iotdb.db.qp.physical.crud.SelectIntoPlan;
import org.apache.iotdb.db.qp.physical.crud.SetDeviceTemplatePlan;
import org.apache.iotdb.db.qp.physical.sys.AlterTimeSeriesPlan;
import org.apache.iotdb.db.qp.physical.sys.AuthorPlan;
@@ -120,6 +121,10 @@ public abstract class PhysicalPlan {
return isQuery;
}
+ public boolean isSelectInto() {
+ return false;
+ }
+
public Operator.OperatorType getOperatorType() {
return operatorType;
}
@@ -170,7 +175,7 @@ public abstract class PhysicalPlan {
*
* @param buffer
*/
- public void deserialize(ByteBuffer buffer) throws IllegalPathException {
+ public void deserialize(ByteBuffer buffer) throws IllegalPathException,
IOException {
throw new UnsupportedOperationException(SERIALIZATION_UNIMPLEMENTED);
}
@@ -223,7 +228,9 @@ public abstract class PhysicalPlan {
}
public void setLoginUserName(String loginUserName) {
- this.loginUserName = loginUserName;
+ if (this instanceof AuthorPlan) {
+ this.loginUserName = loginUserName;
+ }
}
public static class Factory {
@@ -400,6 +407,9 @@ public abstract class PhysicalPlan {
case DROP_FUNCTION:
plan = new DropFunctionPlan();
break;
+ case SELECT_INTO:
+ plan = new SelectIntoPlan();
+ break;
default:
throw new IOException("unrecognized log type " + type);
}
@@ -464,7 +474,8 @@ public abstract class PhysicalPlan {
CREATE_SNAPSHOT,
CLEARCACHE,
CREATE_FUNCTION,
- DROP_FUNCTION
+ DROP_FUNCTION,
+ SELECT_INTO
}
public long getIndex() {
diff --git
a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/SelectIntoPlan.java
b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/SelectIntoPlan.java
new file mode 100644
index 0000000..b2c2a06
--- /dev/null
+++
b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/SelectIntoPlan.java
@@ -0,0 +1,113 @@
+/*
+ * 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.qp.physical.crud;
+
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.logical.Operator.OperatorType;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SelectIntoPlan extends PhysicalPlan {
+
+ private QueryPlan queryPlan;
+ private PartialPath fromPath;
+ private List<PartialPath> intoPaths;
+
+ public SelectIntoPlan() {
+ super(false, OperatorType.SELECT_INTO);
+ }
+
+ public SelectIntoPlan(QueryPlan queryPlan, PartialPath fromPath,
List<PartialPath> intoPaths) {
+ super(false, OperatorType.SELECT_INTO);
+ this.queryPlan = queryPlan;
+ this.fromPath = fromPath;
+ this.intoPaths = intoPaths;
+ }
+
+ @Override
+ public boolean isSelectInto() {
+ return true;
+ }
+
+ @Override
+ public void serialize(DataOutputStream outputStream) throws IOException {
+ outputStream.writeByte((byte) PhysicalPlanType.SELECT_INTO.ordinal());
+
+ queryPlan.serialize(outputStream);
+
+ putString(outputStream, fromPath.getFullPath());
+
+ outputStream.write(intoPaths.size());
+ for (PartialPath intoPath : intoPaths) {
+ putString(outputStream, intoPath.getFullPath());
+ }
+ }
+
+ @Override
+ public void serialize(ByteBuffer buffer) {
+ buffer.put((byte) PhysicalPlanType.SELECT_INTO.ordinal());
+
+ queryPlan.serialize(buffer);
+
+ putString(buffer, fromPath.getFullPath());
+
+ buffer.putInt(intoPaths.size());
+ for (PartialPath intoPath : intoPaths) {
+ putString(buffer, intoPath.getFullPath());
+ }
+ }
+
+ @Override
+ public void deserialize(ByteBuffer buffer) throws IllegalPathException,
IOException {
+ queryPlan = (QueryPlan) Factory.create(buffer);
+
+ fromPath = new PartialPath(readString(buffer));
+
+ int intoPathsSize = buffer.getInt();
+ intoPaths = new ArrayList<>(intoPathsSize);
+ for (int i = 0; i < intoPathsSize; ++i) {
+ intoPaths.add(new PartialPath(readString(buffer)));
+ }
+ }
+
+ /** mainly for query auth. */
+ @Override
+ public List<PartialPath> getPaths() {
+ return queryPlan.getPaths();
+ }
+
+ public QueryPlan getQueryPlan() {
+ return queryPlan;
+ }
+
+ public PartialPath getFromPath() {
+ return fromPath;
+ }
+
+ public List<PartialPath> getIntoPaths() {
+ return intoPaths;
+ }
+}
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 5d6989c..bcd6a06 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
@@ -45,6 +45,7 @@ import org.apache.iotdb.db.qp.logical.crud.InsertOperator;
import org.apache.iotdb.db.qp.logical.crud.LastQueryOperator;
import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
import org.apache.iotdb.db.qp.logical.crud.SelectComponent;
+import org.apache.iotdb.db.qp.logical.crud.SelectIntoOperator;
import org.apache.iotdb.db.qp.logical.crud.SpecialClauseComponent;
import org.apache.iotdb.db.qp.logical.crud.UDFQueryOperator;
import org.apache.iotdb.db.qp.logical.crud.WhereComponent;
@@ -157,6 +158,7 @@ import
org.apache.iotdb.db.qp.sql.SqlBaseParser.InsertColumnsSpecContext;
import org.apache.iotdb.db.qp.sql.SqlBaseParser.InsertMultiValueContext;
import org.apache.iotdb.db.qp.sql.SqlBaseParser.InsertStatementContext;
import org.apache.iotdb.db.qp.sql.SqlBaseParser.InsertValuesSpecContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.IntoPathContext;
import org.apache.iotdb.db.qp.sql.SqlBaseParser.KillQueryContext;
import org.apache.iotdb.db.qp.sql.SqlBaseParser.LimitClauseContext;
import org.apache.iotdb.db.qp.sql.SqlBaseParser.LimitStatementContext;
@@ -285,7 +287,9 @@ public class IoTDBSqlVisitor extends
SqlBaseBaseVisitor<Operator> {
"For delete statement, where clause can only contain atomic expressions
like : "
+ "time > XXX, time <= XXX, or two atomic expressions connected by
'AND'";
- private static final Pattern cqLevelNodePattern =
Pattern.compile("\\$\\{\\w+}");
+ // used to match "{x}", where x is a integer.
+ // for create-cq clause and select-into clause.
+ private static final Pattern leveledPathNodePattern =
Pattern.compile("\\$\\{\\w+}");
private ZoneId zoneId;
private QueryOperator queryOp;
@@ -1046,8 +1050,8 @@ public class IoTDBSqlVisitor extends
SqlBaseBaseVisitor<Operator> {
queryOp.setWhereComponent(whereComponent);
}
}
-
- return queryOp;
+ // 4. Check whether it's a select-into clause
+ return ctx.intoClause() == null ? queryOp :
parseAndConstructSelectIntoOperator(ctx);
}
public void parseSelectClause(SelectClauseContext ctx) {
@@ -1101,6 +1105,75 @@ public class IoTDBSqlVisitor extends
SqlBaseBaseVisitor<Operator> {
resultColumnContext.AS() == null ? null :
resultColumnContext.ID().getText());
}
+ private SelectIntoOperator
parseAndConstructSelectIntoOperator(SelectStatementContext ctx) {
+ if (queryOp.getFromComponent().getPrefixPaths().size() != 1) {
+ throw new SQLParserException(
+ "select into: the number of prefix paths in the from clause should
be 1.");
+ }
+
+ int sourcePathsCount =
queryOp.getSelectComponent().getResultColumns().size();
+ if (sourcePathsCount != ctx.intoClause().intoPath().size()) {
+ throw new SQLParserException(
+ "select into: the number of source paths and the number of target
paths should be the same.");
+ }
+
+ SelectIntoOperator selectIntoOperator = new SelectIntoOperator();
+ selectIntoOperator.setQueryOperator(queryOp);
+ List<PartialPath> intoPaths = new ArrayList<>();
+ for (int i = 0; i < sourcePathsCount; ++i) {
+ intoPaths.add(parseIntoPath(ctx.intoClause().intoPath(i)));
+ }
+ selectIntoOperator.setIntoPaths(intoPaths);
+ return selectIntoOperator;
+ }
+
+ private PartialPath parseIntoPath(IntoPathContext intoPathContext) {
+ int levelLimitOfSourcePrefixPath =
+ queryOp.getSpecialClauseComponent() != null
+ ? queryOp.getSpecialClauseComponent().getLevel()
+ : -1;
+ if (levelLimitOfSourcePrefixPath == -1) {
+ levelLimitOfSourcePrefixPath =
+ queryOp.getFromComponent().getPrefixPaths().get(0).getNodeLength() -
1;
+ }
+
+ PartialPath intoPath = null;
+ if (intoPathContext.fullPath() != null) {
+ intoPath = parseFullPath(intoPathContext.fullPath());
+
+ Matcher m = leveledPathNodePattern.matcher(intoPath.getFullPath());
+ while (m.find()) {
+ String param = m.group();
+ int nodeIndex = 0;
+ try {
+ nodeIndex = Integer.parseInt(param.substring(2, param.length() -
1).trim());
+ } catch (NumberFormatException e) {
+ throw new SQLParserException("the x of ${x} should be an integer.");
+ }
+ if (nodeIndex < 1 || levelLimitOfSourcePrefixPath < nodeIndex) {
+ throw new SQLParserException(
+ "the x of ${x} should be greater than 0 and equal to or less
than <level> or the length of queried path prefix.");
+ }
+ }
+ } else if (intoPathContext.nodeNameWithoutStar() != null) {
+ List<NodeNameWithoutStarContext> nodeNameWithoutStars =
intoPathContext.nodeNameWithoutStar();
+ String[] intoPathNodes =
+ new String[1 + levelLimitOfSourcePrefixPath +
nodeNameWithoutStars.size()];
+
+ intoPathNodes[0] = "root";
+ for (int i = 1; i <= levelLimitOfSourcePrefixPath; ++i) {
+ intoPathNodes[i] = "${" + i + "}";
+ }
+ for (int i = 1; i <= nodeNameWithoutStars.size(); ++i) {
+ intoPathNodes[levelLimitOfSourcePrefixPath + i] =
nodeNameWithoutStars.get(i - 1).getText();
+ }
+
+ intoPath = new PartialPath(intoPathNodes);
+ }
+
+ return intoPath;
+ }
+
@Override
public Operator
visitDropContinuousQueryStatement(DropContinuousQueryStatementContext ctx) {
DropContinuousQueryOperator dropContinuousQueryOperator =
@@ -1209,54 +1282,18 @@ public class IoTDBSqlVisitor extends
SqlBaseBaseVisitor<Operator> {
}
if (queryOp.getFromComponent().getPrefixPaths().size() > 1) {
- throw new SQLParserException("CQ: CQ currently does not support multiple
series .");
+ throw new SQLParserException("CQ: CQ currently does not support multiple
series.");
}
parseCqGroupByTimeClause(ctx.cqGroupByTimeClause());
- int fromLen =
queryOp.getFromComponent().getPrefixPaths().get(0).getNodeLength();
- int queryLevel = queryOp.getSpecialClauseComponent().getLevel();
- if (queryLevel >= fromLen) {
+ int groupByQueryLevel = queryOp.getSpecialClauseComponent().getLevel();
+ int fromPrefixLevelLimit =
queryOp.getFromComponent().getPrefixPaths().get(0).getNodeLength();
+ if (groupByQueryLevel >= fromPrefixLevelLimit) {
throw new SQLParserException("CQ: Level should not exceed the
<from_prefix> length.");
}
- PartialPath targetPath = null;
-
- int trueLevel = queryLevel;
- if (trueLevel == -1) {
- trueLevel = fromLen - 1;
- }
-
- if (ctx.fullPath() != null) {
- targetPath = parseFullPath(ctx.fullPath());
- Matcher m = cqLevelNodePattern.matcher(targetPath.getFullPath());
- while (m.find()) {
- String param = m.group();
- int nodeIndex = 0;
- try {
- nodeIndex = Integer.parseInt(param.substring(2, param.length() -
1).trim());
- } catch (NumberFormatException e) {
- throw new SQLParserException("CQ: x of ${x} should be an integer.");
- }
- if (nodeIndex < 1 || nodeIndex > trueLevel) {
- throw new SQLParserException(
- "CQ: x of ${x} should be greater than 0 and equal to or less
than <level> or the length of queried path prefix.");
- }
- }
- } else if (ctx.nodeNameWithoutStar() != null) {
-
- List<String> targetNodes = new ArrayList<>();
-
- targetNodes.add("root");
-
- for (int i = 1; i <= trueLevel; i++) {
- targetNodes.add("${" + i + "}");
- }
- targetNodes.add(ctx.nodeNameWithoutStar().getText());
- targetPath = new PartialPath(targetNodes.toArray(new String[0]));
- }
-
- createContinuousQueryOperator.setTargetPath(targetPath);
+ createContinuousQueryOperator.setTargetPath(parseIntoPath(ctx.intoPath()));
createContinuousQueryOperator.setQueryOperator(queryOp);
}
diff --git
a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalChecker.java
b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalChecker.java
index 23f6a6f..14234d5 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalChecker.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalChecker.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.qp.strategy;
import org.apache.iotdb.db.exception.query.LogicalOperatorException;
import org.apache.iotdb.db.qp.logical.Operator;
import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
+import org.apache.iotdb.db.qp.logical.crud.SelectIntoOperator;
public class LogicalChecker {
@@ -32,5 +33,9 @@ public class LogicalChecker {
if (operator instanceof QueryOperator) {
((QueryOperator) operator).check();
}
+
+ if (operator instanceof SelectIntoOperator) {
+ ((SelectIntoOperator) operator).check();
+ }
}
}
diff --git
a/server/src/main/java/org/apache/iotdb/db/query/control/QueryTimeManager.java
b/server/src/main/java/org/apache/iotdb/db/query/control/QueryTimeManager.java
index 3d78a92..48bb732 100644
---
a/server/src/main/java/org/apache/iotdb/db/query/control/QueryTimeManager.java
+++
b/server/src/main/java/org/apache/iotdb/db/query/control/QueryTimeManager.java
@@ -22,6 +22,8 @@ import org.apache.iotdb.db.concurrent.IoTDBThreadPoolFactory;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.exception.query.QueryTimeoutRuntimeException;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.sys.ShowQueryProcesslistPlan;
import org.apache.iotdb.db.service.IService;
import org.apache.iotdb.db.service.ServiceType;
@@ -76,6 +78,14 @@ public class QueryTimeManager implements IService {
queryScheduledTaskMap.put(queryId, scheduledFuture);
}
+ public void registerQuery(
+ long queryId, long startTime, String sql, long timeout, PhysicalPlan
plan) {
+ if (plan instanceof ShowQueryProcesslistPlan) {
+ return;
+ }
+ registerQuery(queryId, startTime, sql, timeout);
+ }
+
public void killQuery(long queryId) {
if (queryInfoMap.get(queryId) == null) {
return;
@@ -101,6 +111,10 @@ public class QueryTimeManager implements IService {
return successRemoved;
}
+ public AtomicBoolean unRegisterQuery(long queryId, PhysicalPlan plan) {
+ return plan instanceof ShowQueryProcesslistPlan ? null :
unRegisterQuery(queryId);
+ }
+
public static void checkQueryAlive(long queryId) {
QueryInfo queryInfo = getInstance().queryInfoMap.get(queryId);
if (queryInfo != null && queryInfo.isInterrupted()) {
diff --git
a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
index cc78805..79311b6 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
@@ -27,6 +27,7 @@ import org.apache.iotdb.db.conf.IoTDBConstant;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.cost.statistic.Measurement;
import org.apache.iotdb.db.cost.statistic.Operation;
+import org.apache.iotdb.db.engine.selectinto.InsertTabletPlansIterator;
import org.apache.iotdb.db.exception.BatchProcessException;
import org.apache.iotdb.db.exception.IoTDBException;
import org.apache.iotdb.db.exception.QueryInBatchStatementException;
@@ -50,7 +51,6 @@ import org.apache.iotdb.db.qp.physical.crud.AlignByDevicePlan;
import org.apache.iotdb.db.qp.physical.crud.AlignByDevicePlan.MeasurementType;
import org.apache.iotdb.db.qp.physical.crud.CreateTemplatePlan;
import org.apache.iotdb.db.qp.physical.crud.DeletePlan;
-import org.apache.iotdb.db.qp.physical.crud.GroupByTimePlan;
import org.apache.iotdb.db.qp.physical.crud.InsertMultiTabletPlan;
import org.apache.iotdb.db.qp.physical.crud.InsertRowPlan;
import org.apache.iotdb.db.qp.physical.crud.InsertRowsOfOneDevicePlan;
@@ -58,6 +58,7 @@ import org.apache.iotdb.db.qp.physical.crud.InsertRowsPlan;
import org.apache.iotdb.db.qp.physical.crud.InsertTabletPlan;
import org.apache.iotdb.db.qp.physical.crud.LastQueryPlan;
import org.apache.iotdb.db.qp.physical.crud.QueryPlan;
+import org.apache.iotdb.db.qp.physical.crud.SelectIntoPlan;
import org.apache.iotdb.db.qp.physical.crud.SetDeviceTemplatePlan;
import org.apache.iotdb.db.qp.physical.crud.UDFPlan;
import org.apache.iotdb.db.qp.physical.crud.UDTFPlan;
@@ -142,7 +143,6 @@ import java.sql.SQLException;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -172,30 +172,25 @@ public class TSServiceImpl implements TSIService.Iface {
private static final String INFO_QUERY_PROCESS_ERROR = "Error occurred in
query process: ";
private static final String INFO_NOT_ALLOWED_IN_BATCH_ERROR =
"The query statement is not allowed in batch: ";
-
private static final String INFO_INTERRUPT_ERROR =
"Current Thread interrupted when dealing with request {}";
+ public static final TSProtocolVersion CURRENT_RPC_VERSION =
+ TSProtocolVersion.IOTDB_SERVICE_PROTOCOL_V3;
+
private static final int MAX_SIZE =
IoTDBDescriptor.getInstance().getConfig().getQueryCacheSizeInMetric();
private static final int DELETE_SIZE = 20;
- private static final int DEFAULT_FETCH_SIZE = 10000;
- private static final long MS_TO_MONTH = 30 * 86400_000L;
private final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig();
- private final boolean enableMetric = config.isEnableMetricService();
private static final List<SqlArgument> sqlArgumentList = new
ArrayList<>(MAX_SIZE);
- protected Planner processor;
- protected IPlanExecutor executor;
-
- private final SessionManager sessionManager = SessionManager.getInstance();
+ private static final AtomicInteger queryCount = new AtomicInteger(0);
private final QueryTimeManager queryTimeManager =
QueryTimeManager.getInstance();
+ private final SessionManager sessionManager = SessionManager.getInstance();
- public static final TSProtocolVersion CURRENT_RPC_VERSION =
- TSProtocolVersion.IOTDB_SERVICE_PROTOCOL_V3;
-
- private static final AtomicInteger queryCount = new AtomicInteger(0);
+ protected Planner processor;
+ protected IPlanExecutor executor;
public TSServiceImpl() throws QueryProcessException {
processor = new Planner();
@@ -486,7 +481,7 @@ public class TSServiceImpl implements TSIService.Iface {
InsertRowsPlan insertRowsPlan;
int index = 0;
- List executeList = new ArrayList();
+ List<Object> executeList = new ArrayList<>();
OperatorType lastOperatorType = null;
CreateMultiTimeSeriesPlan multiPlan;
for (int i = 0; i < req.getStatements().size(); i++) {
@@ -494,7 +489,7 @@ public class TSServiceImpl implements TSIService.Iface {
try {
PhysicalPlan physicalPlan =
processor.parseSQLToPhysicalPlan(statement,
sessionManager.getZoneId(req.sessionId));
- if (physicalPlan.isQuery()) {
+ if (physicalPlan.isQuery() || physicalPlan.isSelectInto()) {
throw new QueryInBatchStatementException(statement);
}
@@ -548,14 +543,14 @@ public class TSServiceImpl implements TSIService.Iface {
}
} else {
lastOperatorType = physicalPlan.getOperatorType();
- if (executeList.size() > 0) {
+ if (!executeList.isEmpty()) {
if (!executeBatchList(executeList, result)) {
isAllSuccessful = false;
}
executeList.clear();
}
long t2 = System.currentTimeMillis();
- TSExecuteStatementResp resp = executeUpdateStatement(physicalPlan,
req.getSessionId());
+ TSExecuteStatementResp resp = executeNonQueryStatement(physicalPlan,
req.getSessionId());
Measurement.INSTANCE.addOperationLatency(Operation.EXECUTE_ONE_SQL_IN_BATCH,
t2);
result.add(resp.status);
if (resp.getStatus().code !=
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
@@ -599,9 +594,15 @@ public class TSServiceImpl implements TSIService.Iface {
physicalPlan,
req.fetchSize,
req.timeout,
- sessionManager.getUsername(req.getSessionId()),
+ req.getSessionId(),
req.isEnableRedirectQuery())
- : executeUpdateStatement(physicalPlan, req.getSessionId());
+ : executeUpdateStatement(
+ statement,
+ req.statementId,
+ physicalPlan,
+ req.fetchSize,
+ req.timeout,
+ req.getSessionId());
} catch (InterruptedException e) {
LOGGER.error(INFO_INTERRUPT_ERROR, req, e);
Thread.currentThread().interrupt();
@@ -629,7 +630,7 @@ public class TSServiceImpl implements TSIService.Iface {
physicalPlan,
req.fetchSize,
req.timeout,
- sessionManager.getUsername(req.getSessionId()),
+ req.getSessionId(),
req.isEnableRedirectQuery())
: RpcUtils.getTSExecuteStatementResp(
TSStatusCode.EXECUTE_STATEMENT_ERROR, "Statement is not a query
statement.");
@@ -660,7 +661,7 @@ public class TSServiceImpl implements TSIService.Iface {
physicalPlan,
req.fetchSize,
config.getQueryTimeoutThreshold(),
- sessionManager.getUsername(req.sessionId),
+ req.sessionId,
req.isEnableRedirectQuery())
: RpcUtils.getTSExecuteStatementResp(
TSStatusCode.EXECUTE_STATEMENT_ERROR, "Statement is not a query
statement.");
@@ -676,7 +677,7 @@ public class TSServiceImpl implements TSIService.Iface {
}
@Override
- public TSExecuteStatementResp executeLastDataQuery(TSLastDataQueryReq req)
throws TException {
+ public TSExecuteStatementResp executeLastDataQuery(TSLastDataQueryReq req) {
try {
if (!checkLogin(req.getSessionId())) {
return RpcUtils.getTSExecuteStatementResp(getNotLoggedInStatus());
@@ -691,7 +692,7 @@ public class TSServiceImpl implements TSIService.Iface {
physicalPlan,
req.fetchSize,
config.getQueryTimeoutThreshold(),
- sessionManager.getUsername(req.sessionId),
+ req.sessionId,
req.isEnableRedirectQuery())
: RpcUtils.getTSExecuteStatementResp(
TSStatusCode.EXECUTE_STATEMENT_ERROR, "Statement is not a query
statement.");
@@ -717,7 +718,7 @@ public class TSServiceImpl implements TSIService.Iface {
PhysicalPlan plan,
int fetchSize,
long timeout,
- String username,
+ long sessionId,
boolean enableRedirect)
throws QueryProcessException, SQLException, StorageEngineException,
QueryFilterOptimizationException, MetadataException, IOException,
InterruptedException,
@@ -725,16 +726,12 @@ public class TSServiceImpl implements TSIService.Iface {
queryCount.incrementAndGet();
AUDIT_LOGGER.debug(
"Session {} execute Query: {}", sessionManager.getCurrSessionId(),
statement);
- long startTime = System.currentTimeMillis();
- long queryId = -1;
- try {
- // generate the queryId for the operation
- queryId = sessionManager.requestQueryId(statementId, true);
- // register query info to queryTimeManager
- if (!(plan instanceof ShowQueryProcesslistPlan)) {
- queryTimeManager.registerQuery(queryId, startTime, statement, timeout);
- }
+ final long startTime = System.currentTimeMillis();
+ final long queryId = sessionManager.requestQueryId(statementId, true);
+
+ try {
+ queryTimeManager.registerQuery(queryId, startTime, statement, timeout,
plan);
if (plan instanceof QueryPlan && config.isEnablePerformanceTracing()) {
TracingManager tracingManager = TracingManager.getInstance();
if (!(plan instanceof AlignByDevicePlan)) {
@@ -744,9 +741,8 @@ public class TSServiceImpl implements TSIService.Iface {
}
}
- if (plan instanceof AuthorPlan) {
- plan.setLoginUserName(username);
- }
+ String username = sessionManager.getUsername(sessionId);
+ plan.setLoginUserName(username);
TSExecuteStatementResp resp = null;
// execute it before createDataSet since it may change the content of
query plan
@@ -810,14 +806,14 @@ public class TSServiceImpl implements TSIService.Iface {
}
}
}
+
resp.setQueryId(queryId);
if (plan instanceof AlignByDevicePlan &&
config.isEnablePerformanceTracing()) {
TracingManager.getInstance()
.writePathsNum(queryId, ((AlignByDeviceDataSet)
newDataSet).getPathsNum());
}
-
- if (enableMetric) {
+ if (config.isEnableMetricService()) {
long endTime = System.currentTimeMillis();
SqlArgument sqlArgument = new SqlArgument(resp, plan, statement,
startTime, endTime);
synchronized (sqlArgumentList) {
@@ -827,11 +823,8 @@ public class TSServiceImpl implements TSIService.Iface {
}
}
}
+ queryTimeManager.unRegisterQuery(queryId, plan);
- // remove query info in QueryTimeManager
- if (!(plan instanceof ShowQueryProcesslistPlan)) {
- queryTimeManager.unRegisterQuery(queryId);
- }
return resp;
} catch (Exception e) {
sessionManager.releaseQueryResourceNoExceptions(queryId);
@@ -845,21 +838,6 @@ public class TSServiceImpl implements TSIService.Iface {
}
}
- /*
- calculate fetch size for group by time plan
- */
- private int getFetchSizeForGroupByTimePlan(GroupByTimePlan plan) {
- int rows = (int) ((plan.getEndTime() - plan.getStartTime()) /
plan.getInterval());
- // rows gets 0 is caused by: the end time - the start time < the time
interval.
- if (rows == 0 && plan.isIntervalByMonth()) {
- Calendar calendar = Calendar.getInstance();
- calendar.setTimeInMillis(plan.getStartTime());
- calendar.add(Calendar.MONTH, (int) (plan.getInterval() / MS_TO_MONTH));
- rows = calendar.getTimeInMillis() <= plan.getEndTime() ? 1 : 0;
- }
- return rows;
- }
-
private TSExecuteStatementResp getListDataSetHeaders(QueryDataSet dataSet) {
return StaticResps.getNoTimeExecuteResp(
dataSet.getPaths().stream().map(Path::getFullPath).collect(Collectors.toList()),
@@ -998,6 +976,81 @@ public class TSServiceImpl implements TSIService.Iface {
plan.setPaths(null);
}
+ private TSExecuteStatementResp executeSelectIntoStatement(
+ String statement,
+ long statementId,
+ PhysicalPlan physicalPlan,
+ int fetchSize,
+ long timeout,
+ long sessionId)
+ throws IoTDBException, TException, SQLException, IOException,
InterruptedException,
+ QueryFilterOptimizationException {
+ TSStatus status = checkAuthority(physicalPlan, sessionId);
+ if (status != null) {
+ return new TSExecuteStatementResp(status);
+ }
+
+ final long startTime = System.currentTimeMillis();
+ final long queryId = sessionManager.requestQueryId(statementId, true);
+ final SelectIntoPlan selectIntoPlan = (SelectIntoPlan) physicalPlan;
+ final QueryPlan queryPlan = selectIntoPlan.getQueryPlan();
+
+ queryCount.incrementAndGet();
+ AUDIT_LOGGER.debug(
+ "Session {} execute select into: {}",
sessionManager.getCurrSessionId(), statement);
+ if (config.isEnablePerformanceTracing()) {
+ TracingManager.getInstance()
+ .writeQueryInfo(queryId, statement, startTime,
queryPlan.getPaths().size());
+ }
+
+ try {
+ queryTimeManager.registerQuery(queryId, startTime, statement, timeout,
queryPlan);
+
+ InsertTabletPlansIterator insertTabletPlansIterator =
+ new InsertTabletPlansIterator(
+ queryPlan,
+ createQueryDataSet(queryId, queryPlan, fetchSize),
+ selectIntoPlan.getFromPath(),
+ selectIntoPlan.getIntoPaths());
+ while (insertTabletPlansIterator.hasNext()) {
+ TSStatus executionStatus =
+ insertTabletsInternally(insertTabletPlansIterator.next(),
sessionId);
+ if (executionStatus.getCode() !=
TSStatusCode.SUCCESS_STATUS.getStatusCode()
+ && executionStatus.getCode() !=
TSStatusCode.NEED_REDIRECTION.getStatusCode()) {
+ return
RpcUtils.getTSExecuteStatementResp(executionStatus).setQueryId(queryId);
+ }
+ }
+
+ return
RpcUtils.getTSExecuteStatementResp(TSStatusCode.SUCCESS_STATUS).setQueryId(queryId);
+ } catch (Exception e) {
+ sessionManager.releaseQueryResourceNoExceptions(queryId);
+ throw e;
+ } finally {
+ queryTimeManager.unRegisterQuery(queryId, queryPlan);
+ Measurement.INSTANCE.addOperationLatency(Operation.EXECUTE_QUERY,
startTime);
+ long costTime = System.currentTimeMillis() - startTime;
+ if (costTime >= config.getSlowQueryThreshold()) {
+ SLOW_SQL_LOGGER.info("Cost: {} ms, sql is {}", costTime, statement);
+ }
+ }
+ }
+
+ private TSStatus insertTabletsInternally(
+ List<InsertTabletPlan> insertTabletPlans, long sessionId) {
+ InsertMultiTabletPlan insertMultiTabletPlan = new InsertMultiTabletPlan();
+ for (int i = 0; i < insertTabletPlans.size(); i++) {
+ InsertTabletPlan insertTabletPlan = insertTabletPlans.get(i);
+ TSStatus status = checkAuthority(insertTabletPlan, sessionId);
+ if (status != null) {
+ // not authorized
+ insertMultiTabletPlan.getResults().put(i, status);
+ }
+ }
+ insertMultiTabletPlan.setInsertTabletPlanList(insertTabletPlans);
+
+ return executeNonQueryPlan(insertMultiTabletPlan);
+ }
+
@SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity
warning
@Override
public TSFetchResultsResp fetchResults(TSFetchResultsReq req) {
@@ -1120,6 +1173,7 @@ public class TSServiceImpl implements TSIService.Iface {
return new QueryContext(queryId, debug);
}
+ /** update statement can be: 1. select-into statement 2. non-query statement
*/
@Override
public TSExecuteStatementResp executeUpdateStatement(TSExecuteStatementReq
req) {
if (!checkLogin(req.getSessionId())) {
@@ -1127,41 +1181,48 @@ public class TSServiceImpl implements TSIService.Iface {
}
try {
- return executeUpdateStatement(req.getStatement(), req.getSessionId());
+ PhysicalPlan physicalPlan =
+ processor.parseSQLToPhysicalPlan(req.statement,
sessionManager.getZoneId(req.sessionId));
+ return physicalPlan.isQuery()
+ ? RpcUtils.getTSExecuteStatementResp(
+ TSStatusCode.EXECUTE_STATEMENT_ERROR, "Statement is a query
statement.")
+ : executeUpdateStatement(
+ req.statement,
+ req.statementId,
+ physicalPlan,
+ req.fetchSize,
+ req.timeout,
+ req.getSessionId());
+ } catch (InterruptedException e) {
+ LOGGER.error(INFO_INTERRUPT_ERROR, req, e);
+ Thread.currentThread().interrupt();
+ return RpcUtils.getTSExecuteStatementResp(onQueryException(e, "executing
update statement"));
} catch (Exception e) {
return RpcUtils.getTSExecuteStatementResp(onQueryException(e, "executing
update statement"));
}
}
- private TSExecuteStatementResp executeUpdateStatement(PhysicalPlan plan,
long sessionId) {
- TSStatus status = checkAuthority(plan, sessionId);
- if (status != null) {
- return new TSExecuteStatementResp(status);
- }
-
- status = executeNonQueryPlan(plan);
- TSExecuteStatementResp resp = RpcUtils.getTSExecuteStatementResp(status);
- long queryId = sessionManager.requestQueryId(false);
- return resp.setQueryId(queryId);
- }
-
- private boolean executeNonQuery(PhysicalPlan plan)
- throws QueryProcessException, StorageGroupNotSetException,
StorageEngineException {
- if (IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
- throw new QueryProcessException(
- "Current system mode is read-only, does not support non-query
operation");
- }
- return executor.processNonQuery(plan);
+ /** update statement can be: 1. select-into statement 2. non-query statement
*/
+ private TSExecuteStatementResp executeUpdateStatement(
+ String statement,
+ long statementId,
+ PhysicalPlan plan,
+ int fetchSize,
+ long timeout,
+ long sessionId)
+ throws TException, SQLException, IoTDBException, IOException,
InterruptedException,
+ QueryFilterOptimizationException {
+ return plan.isSelectInto()
+ ? executeSelectIntoStatement(statement, statementId, plan, fetchSize,
timeout, sessionId)
+ : executeNonQueryStatement(plan, sessionId);
}
- private TSExecuteStatementResp executeUpdateStatement(String statement, long
sessionId)
- throws QueryProcessException {
- PhysicalPlan physicalPlan =
- processor.parseSQLToPhysicalPlan(statement,
sessionManager.getZoneId(sessionId));
- return physicalPlan.isQuery()
- ? RpcUtils.getTSExecuteStatementResp(
- TSStatusCode.EXECUTE_STATEMENT_ERROR, "Statement is a query
statement.")
- : executeUpdateStatement(physicalPlan, sessionId);
+ private TSExecuteStatementResp executeNonQueryStatement(PhysicalPlan plan,
long sessionId) {
+ TSStatus status = checkAuthority(plan, sessionId);
+ return status != null
+ ? new TSExecuteStatementResp(status)
+ : RpcUtils.getTSExecuteStatementResp(executeNonQueryPlan(plan))
+ .setQueryId(sessionManager.requestQueryId(false));
}
/**
@@ -1567,7 +1628,7 @@ public class TSServiceImpl implements TSIService.Iface {
return getNotLoggedInStatus();
}
- return insertTabletsInternal(req);
+ return insertTabletsInternally(req);
} catch (NullPointerException e) {
LOGGER.error("{}: error occurs when insertTablets",
IoTDBConstant.GLOBAL_DB_NAME, e);
return RpcUtils.getStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR);
@@ -1600,7 +1661,7 @@ public class TSServiceImpl implements TSIService.Iface {
}
/** construct one InsertMultiTabletPlan and process it */
- public TSStatus insertTabletsInternal(TSInsertTabletsReq req) throws
IllegalPathException {
+ public TSStatus insertTabletsInternally(TSInsertTabletsReq req) throws
IllegalPathException {
List<InsertTabletPlan> insertTabletPlanList = new ArrayList<>();
InsertMultiTabletPlan insertMultiTabletPlan = new InsertMultiTabletPlan();
for (int i = 0; i < req.deviceIds.size(); i++) {
@@ -1962,6 +2023,15 @@ public class TSServiceImpl implements TSIService.Iface {
: RpcUtils.getStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR);
}
+ private boolean executeNonQuery(PhysicalPlan plan)
+ throws QueryProcessException, StorageGroupNotSetException,
StorageEngineException {
+ if (IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
+ throw new QueryProcessException(
+ "Current system mode is read-only, does not support non-query
operation");
+ }
+ return executor.processNonQuery(plan);
+ }
+
protected List<TSDataType> getSeriesTypesByPaths(
List<PartialPath> paths, List<String> aggregations) throws
MetadataException {
return SchemaUtils.getSeriesTypesByPaths(paths, aggregations);
diff --git
a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBSelectIntoIT.java
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBSelectIntoIT.java
new file mode 100644
index 0000000..be5a23b
--- /dev/null
+++
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBSelectIntoIT.java
@@ -0,0 +1,564 @@
+/*
+ * 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.integration;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.service.IoTDB;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+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: check null values
+public class IoTDBSelectIntoIT {
+
+ private static final int ROW_LIMIT =
+
IoTDBDescriptor.getInstance().getConfig().getSelectIntoInsertTabletPlanRowLimit();
+
+ private static final String[] INSERTION_SQLS = {
+ "insert into root.sg.d1(time, s2, s3, s4, s5, s6) values (0, 0, 0, 0,
true, '0')",
+ "insert into root.sg.d1(time, s1, s3, s4, s5, s6) values (1, 1, 1, 1,
false, '1')",
+ "insert into root.sg.d1(time, s1, s2, s4, s5, s6) values (2, 2, 2, 2,
true, '2')",
+ "insert into root.sg.d1(time, s1, s2, s3, s5, s6) values (3, 3, 3, 3,
false, '3')",
+ "insert into root.sg.d1(time, s1, s2, s3, s4, s5, s6) values (4, 4, 4, 4,
4, true, '4')",
+ };
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ EnvironmentUtils.envSetUp();
+ Class.forName(Config.JDBC_DRIVER_NAME);
+ createTimeSeries();
+ generateData();
+ }
+
+ private static void createTimeSeries() throws MetadataException {
+ IoTDB.metaManager.setStorageGroup(new PartialPath("root.sg"));
+
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d1.s1"),
+ TSDataType.INT32,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d1.s2"),
+ TSDataType.INT64,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d1.s3"),
+ TSDataType.FLOAT,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d1.s4"),
+ TSDataType.DOUBLE,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d1.s5"),
+ TSDataType.BOOLEAN,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d1.s6"),
+ TSDataType.TEXT,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d1.empty"),
+ TSDataType.TEXT,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+
+ IoTDB.metaManager.createTimeseries(
+ new PartialPath("root.sg.d2.s1"),
+ TSDataType.INT32,
+ TSEncoding.PLAIN,
+ CompressionType.UNCOMPRESSED,
+ null);
+ }
+
+ private static void generateData() {
+ try (Connection connection =
+ DriverManager.getConnection(
+ Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+ Statement statement = connection.createStatement()) {
+ for (String dataGenerationSql : INSERTION_SQLS) {
+ statement.execute(dataGenerationSql);
+ }
+
+ statement.execute("insert into root.sg.d2(time, s1) values (0, 0)");
+
+ final int size = ROW_LIMIT + 1;
+ for (int i = 0; i < size; ++i) {
+ statement.execute(String.format("insert into root.sg.d3(time, s1)
values (%d, %d)", i, i));
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ EnvironmentUtils.cleanEnv();
+ }
+
+ @Test // TODO: check values
+ public void selectIntoSameDevice() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute(
+ "select s1, s2, s3, s4, s5, s6 into s7, s8, s9, s10, s11, s12 from
root.sg.d1");
+
+ try (ResultSet resultSet =
+ statement.executeQuery("select s7, s8, s9, s10, s11, s12 from
root.sg.d1")) {
+ assertEquals(1 + 6, resultSet.getMetaData().getColumnCount());
+
+ for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+ assertTrue(resultSet.next());
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int j = 0; j < 6 + 1; ++j) {
+ stringBuilder.append(resultSet.getString(j + 1)).append(',');
+ }
+ System.out.println(stringBuilder.toString());
+ }
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test // TODO: check values
+ public void selectIntoDifferentDevices() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute(
+ "select s1, s2, s3, s4, s5, s6 into pre_${2}_suf.s1,
pre_${2}_suf.s2, pre_${2}_suf.s3, pre_${2}_suf.s4, pre_${2}_suf.s5,
pre_${2}_suf.s6 from root.sg.d1");
+
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ "select pre_d1_suf.s1, pre_d1_suf.s2, pre_d1_suf.s3,
pre_d1_suf.s4, pre_d1_suf.s5, pre_d1_suf.s6 from root.sg.d1")) {
+ assertEquals(1 + 6, resultSet.getMetaData().getColumnCount());
+
+ for (int i = 0; i < INSERTION_SQLS.length; ++i) {
+ assertTrue(resultSet.next());
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int j = 0; j < 6 + 1; ++j) {
+ stringBuilder.append(resultSet.getString(j + 1)).append(',');
+ }
+ System.out.println(stringBuilder.toString());
+ }
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void selectFromEmptySourcePath() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select empty into target from root.sg.d1");
+
+ try (ResultSet resultSet = statement.executeQuery("select target from
root.sg.d1")) {
+ assertEquals(1, resultSet.getMetaData().getColumnCount());
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void selectIntoFullTargetPath() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into root.${2}.${1}.s1 from root.sg.d1
where time>0");
+
+ try (ResultSet resultSet = statement.executeQuery("select sg.d1.s1,
d1.sg.s1 from root")) {
+ assertEquals(1 + 2, resultSet.getMetaData().getColumnCount());
+
+ for (int i = 0; i < INSERTION_SQLS.length - 1; ++i) {
+ assertTrue(resultSet.next());
+ assertEquals(resultSet.getString(1), String.valueOf(i + 1));
+ assertEquals(resultSet.getString(2), resultSet.getString(3));
+ }
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void selectSameTimeSeries() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1, s1 into s1s2, s1s3 from root.sg.d1");
+
+ try (ResultSet resultSet = statement.executeQuery("select s1s2, s1s3
from root.sg.d1")) {
+ assertEquals(1 + 2, resultSet.getMetaData().getColumnCount());
+
+ for (int i = 1; i < INSERTION_SQLS.length; ++i) {
+ assertTrue(resultSet.next());
+ for (int j = 0; j < 2 + 1; ++j) {
+ assertEquals(resultSet.getString(2), resultSet.getString(3));
+ }
+ }
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void testLargeData() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into large_s1 from root.sg.d3");
+
+ try (ResultSet resultSet = statement.executeQuery("select large_s1 from
root.sg.d3")) {
+ assertEquals(1 + 1, resultSet.getMetaData().getColumnCount());
+
+ final int size = ROW_LIMIT + 1;
+ for (int i = 0; i < size; ++i) {
+ assertTrue(resultSet.next());
+ assertEquals(
+ Double.parseDouble(resultSet.getString(1)),
+ Double.parseDouble(resultSet.getString(2)),
+ 0);
+ }
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void testUDFQuery() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute(
+ "select s1, sin(s1), s1 + s1 into ${2}.s2, ${2}.s3, ${2}.s4 from
root.sg.d1");
+
+ try (ResultSet resultSet = statement.executeQuery("select s2, s3, s4
from root.sg.d1.d1")) {
+ assertEquals(1 + 3, resultSet.getMetaData().getColumnCount());
+
+ for (int i = 1; i < INSERTION_SQLS.length; ++i) {
+ assertTrue(resultSet.next());
+ for (int j = 0; j < 2 + 1; ++j) {
+ double s2 = Double.parseDouble(resultSet.getString(2));
+ double s3 = Double.parseDouble(resultSet.getString(3));
+ double s4 = Double.parseDouble(resultSet.getString(4));
+ assertEquals(i, s2, 0);
+ assertEquals(Math.sin(i), s3, 0);
+ assertEquals((double) i + (double) i, s4, 0);
+ }
+ }
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void testGroupByQuery() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select count(s1) into count_s1 from root.sg.d1 group
by ([1, 5),1ms);");
+
+ try (ResultSet resultSet = statement.executeQuery("select count_s1 from
root.sg.d1")) {
+ assertEquals(1 + 1, resultSet.getMetaData().getColumnCount());
+
+ for (int i = 1; i < INSERTION_SQLS.length; ++i) {
+ assertTrue(resultSet.next());
+ for (int j = 0; j < 1 + 1; ++j) {
+ assertEquals(String.valueOf(i), resultSet.getString(1));
+ assertEquals("1", resultSet.getString(2));
+ }
+ }
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void testGroupByFillQuery() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute(
+ "select last_value(s1) into gbf_s1 from root.sg.d1 group by ([1,
10),1ms) fill (float[PREVIOUS]);");
+
+ try (ResultSet resultSet = statement.executeQuery("select gbf_s1 from
root.sg.d1")) {
+ assertEquals(1 + 1, resultSet.getMetaData().getColumnCount());
+
+ for (int i = 1; i < 10; ++i) {
+ assertTrue(resultSet.next());
+ for (int j = 0; j < 1 + 1; ++j) {
+ assertEquals(String.valueOf(i), resultSet.getString(1));
+ assertEquals(i < 5 ? String.valueOf(i) : "0",
resultSet.getString(2));
+ }
+ }
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void testFillQuery() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute(
+ "select s1 into fill_s1 from root.sg.d1 where time = 10 fill(float
[linear, 1ms, 1ms])");
+
+ try (ResultSet resultSet = statement.executeQuery("select fill_s1 from
root.sg.d1")) {
+ assertEquals(1 + 1, resultSet.getMetaData().getColumnCount());
+
+ assertTrue(resultSet.next());
+ assertEquals("10", resultSet.getString(1));
+ assertEquals("4", resultSet.getString(2));
+
+ assertFalse(resultSet.next());
+ }
+ } catch (SQLException throwable) {
+ fail(throwable.getMessage());
+ }
+ }
+
+ @Test
+ public void testDifferentNumbersOfSourcePathsAndTargetPaths() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1, s2 into target from root.sg.d1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(
+ throwable
+ .getMessage()
+ .contains(
+ "the number of source paths and the number of target paths
should be the same"));
+ }
+
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into target from root.sg.*");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(
+ throwable
+ .getMessage()
+ .contains(
+ "the number of source paths and the number of target paths
should be the same"));
+ }
+
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select * into target from root.sg.d1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(
+ throwable
+ .getMessage()
+ .contains(
+ "the number of source paths and the number of target paths
should be the same"));
+ }
+ }
+
+ @Test
+ public void testMultiPrefixPathsInFromClause() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into target from root.sg.d1, root.sg.d2");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(
+ throwable
+ .getMessage()
+ .contains("the number of prefix paths in the from clause should
be 1"));
+ }
+ }
+
+ @Test
+ public void testLeveledPathNodePatternLimit() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into root.${100}.s1 from root.sg.d1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(
+ throwable
+ .getMessage()
+ .contains(
+ "the x of ${x} should be greater than 0 and equal to or less
than <level> or the length of queried path prefix."));
+ }
+
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into root.${0}.s1 from root.sg.d1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(
+ throwable
+ .getMessage()
+ .contains(
+ "the x of ${x} should be greater than 0 and equal to or less
than <level> or the length of queried path prefix."));
+ }
+
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into root.${wrong}.s1 from root.sg.d1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(throwable.getMessage().contains("the x of ${x} should be an
integer."));
+ }
+ }
+
+ @Test
+ public void testAlignByDevice() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into root.${1}.s1 from root.sg.d1 align by
device");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(throwable.getMessage().contains("align by device clauses are
not supported."));
+ }
+ }
+
+ @Test
+ public void testDisableDevice() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1 into root.${1}.s1 from root.sg.d1 disable
align");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(throwable.getMessage().contains("disable align clauses are
not supported."));
+ }
+ }
+
+ @Test
+ public void testLastQuery() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select last s1 into root.${1}.s1 from root.sg.d1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(throwable.getMessage().contains("last clauses are not
supported."));
+ }
+ }
+
+ @Test
+ public void testSlimit() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1, s2 into ${1}.s1, ${2}.s1 from root.sg.d1
slimit 1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(throwable.getMessage().contains("slimit clauses are not
supported."));
+ }
+ }
+
+ @Test
+ public void testDescending() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1, s2 into ${1}.s1, ${2}.s1 from root.sg.d1
order by time desc");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(throwable.getMessage().contains("desc clauses are not
supported."));
+ }
+ }
+
+ @Test
+ public void testSameTargetPaths() {
+ try (Statement statement =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root")
+ .createStatement()) {
+ statement.execute("select s1, s2 into ${1}.s1, ${1}.s1 from root.sg.d1");
+ fail();
+ } catch (SQLException throwable) {
+ assertTrue(
+ throwable.getMessage().contains("target paths in into clause should
be different."));
+ }
+ }
+}
diff --git
a/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java
b/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java
index 2712308..c6c0437 100644
--- a/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java
@@ -1333,7 +1333,7 @@ public class PhysicalPlanTest {
(CreateContinuousQueryPlan) processor.parseSQLToPhysicalPlan(sql);
fail();
} catch (SQLParserException e) {
- assertEquals("CQ: x of ${x} should be an integer.", e.getMessage());
+ assertTrue(e.getMessage().contains("the x of ${x} should be an
integer."));
}
}
@@ -1347,9 +1347,10 @@ public class PhysicalPlanTest {
(CreateContinuousQueryPlan) processor.parseSQLToPhysicalPlan(sql);
fail();
} catch (SQLParserException e) {
- assertEquals(
- "CQ: x of ${x} should be greater than 0 and equal to or less than
<level> or the length of queried path prefix.",
- e.getMessage());
+ assertTrue(
+ e.getMessage()
+ .contains(
+ "the x of ${x} should be greater than 0 and equal to or less
than <level> or the length of queried path prefix."));
}
}
@@ -1373,19 +1374,6 @@ public class PhysicalPlanTest {
@Test
public void testCreateCQ9() throws QueryProcessException {
- String sql =
- "CREATE CONTINUOUS QUERY cq1 BEGIN SELECT max_value(temperature) INTO
${1}.${0}.${2}.${3}.temperature_max FROM root.ln.*.*.* GROUP BY time(10s),
level = 3 END";
- try {
- CreateContinuousQueryPlan plan =
- (CreateContinuousQueryPlan) processor.parseSQLToPhysicalPlan(sql);
- fail();
- } catch (ParseCancellationException e) {
- assertTrue(e.getMessage().contains("mismatched input '.' expecting
FROM"));
- }
- }
-
- @Test
- public void testCreateCQ10() throws QueryProcessException {
String sql =
"CREATE CQ cq1 RESAMPLE FOR 20s BEGIN SELECT max_value(temperature)
INTO root.${0}.${3}.temperature_max FROM root.ln.*.*.* GROUP BY time(10s),
level = 3 END";
@@ -1394,14 +1382,15 @@ public class PhysicalPlanTest {
(CreateContinuousQueryPlan) processor.parseSQLToPhysicalPlan(sql);
fail();
} catch (SQLParserException e) {
- assertEquals(
- "CQ: x of ${x} should be greater than 0 and equal to or less than
<level> or the length of queried path prefix.",
- e.getMessage());
+ assertTrue(
+ e.getMessage()
+ .contains(
+ "the x of ${x} should be greater than 0 and equal to or less
than <level> or the length of queried path prefix."));
}
}
@Test
- public void testCreateCQ11() throws QueryProcessException {
+ public void testCreateCQ10() throws QueryProcessException {
String sql =
"CREATE CQ cq1 RESAMPLE FOR 20s BEGIN SELECT max_value(temperature),
avg(temperature) INTO root.${0}.${3}.temperature_max FROM root.ln.*.*.* GROUP
BY time(10s), level = 3 END";
@@ -1415,7 +1404,7 @@ public class PhysicalPlanTest {
}
@Test
- public void testCreateCQ12() throws QueryProcessException {
+ public void testCreateCQ11() throws QueryProcessException {
long minEveryInterval =
IoTDBDescriptor.getInstance().getConfig().getContinuousQueryMinimumEveryInterval();
long everyInterval = minEveryInterval / 2;
diff --git a/site/src/main/.vuepress/config.js
b/site/src/main/.vuepress/config.js
index a2cb26c..a8f70eb 100644
--- a/site/src/main/.vuepress/config.js
+++ b/site/src/main/.vuepress/config.js
@@ -712,7 +712,8 @@ var config = {
// The trigger module
has not been implemented yet,
// so the website
should not show users how to use it to avoid misleading.
//
['Advanced-Features/Triggers','Trigger'],
-
['Advanced-Features/Continuous-Query','CQ (Continuous Query)'],
+ ['Advanced-Features/Select-Into','Query Write-back (SELECT
INTO)'],
+ ['Advanced-Features/Continuous-Query','CQ (Continuous Query)'],
]
},
{
@@ -1504,7 +1505,8 @@ var config = {
// The trigger module has not
been implemented yet,
// so the website should not
show users how to use it to avoid misleading.
//
['Advanced-Features/Triggers','触发器'],
-
['Advanced-Features/Continuous-Query','连续查询(CQ)'],
+ ['Advanced-Features/Select-Into','查询写回(SELECT INTO)'],
+ ['Advanced-Features/Continuous-Query','连续查询(CQ)'],
]
},
{
diff --git a/testcontainer/src/test/java/org/apache/iotdb/db/sql/Cases.java
b/testcontainer/src/test/java/org/apache/iotdb/db/sql/Cases.java
index c4c8178..fbbc3fa 100644
--- a/testcontainer/src/test/java/org/apache/iotdb/db/sql/Cases.java
+++ b/testcontainer/src/test/java/org/apache/iotdb/db/sql/Cases.java
@@ -461,4 +461,29 @@ public abstract class Cases {
resultSet.close();
}
}
+
+ @Test
+ public void testSelectInto() throws SQLException {
+ for (int i = 0; i < 10; i++) {
+ writeStatement.execute(
+ String.format("INSERT INTO root.sg.d%s(timestamp,s) VALUES(%s,%s)",
i, i, i));
+ }
+
+ writeStatement.execute(
+ "SELECT d0.s, d1.s, d2.s, d3.s, d4.s into d0.t, d1.t, d2.t, d3.t, d4.t
from root.sg;");
+ for (int i = 5; i < 10; ++i) {
+ writeStatement.execute(String.format("SELECT d%s.s into d%s.t from
root.sg;", i, i));
+ }
+
+ for (Statement readStatement : readStatements) {
+ for (int i = 0; i < 10; ++i) {
+ try (ResultSet resultSet =
+ readStatement.executeQuery(String.format("SELECT s, t FROM
root.sg.d%s", i))) {
+ Assert.assertTrue(resultSet.next());
+ Assert.assertEquals(resultSet.getDouble(2), resultSet.getDouble(3),
0);
+ Assert.assertFalse(resultSet.next());
+ }
+ }
+ }
+ }
}
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/BitMap.java
b/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/BitMap.java
index 91affdb..78fa5b1 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/BitMap.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/BitMap.java
@@ -64,6 +64,11 @@ public class BitMap {
return (bits[position / Byte.SIZE] & BIT_UTIL[position % Byte.SIZE]) != 0;
}
+ /** mark as 1 at all positions. */
+ public void markAll() {
+ Arrays.fill(bits, (byte) 0XFF);
+ }
+
/** mark as 1 at the given bit position. */
public void mark(int position) {
bits[position / Byte.SIZE] |= BIT_UTIL[position % Byte.SIZE];