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 9766836  [IOTDB-1319] Trigger module: sink utility (#3038)
9766836 is described below

commit 9766836b4db82fe56c041c32fcd837017de22114
Author: Steve Yurong Su <[email protected]>
AuthorDate: Thu Apr 22 11:30:28 2021 +0800

    [IOTDB-1319] Trigger module: sink utility (#3038)
---
 docs/UserGuide/Advanced-Features/Triggers.md       | 278 +++++++++++++++++++++
 docs/zh/UserGuide/Advanced-Features/Triggers.md    | 278 +++++++++++++++++++++
 example/pom.xml                                    |   1 +
 example/trigger/pom.xml                            | 124 +++++++++
 .../org/apache/iotdb/trigger/TriggerExample.java   | 127 ++++++++++
 server/pom.xml                                     |   4 +
 .../resources/conf/iotdb-engine.properties         |   6 +-
 .../Evaluator.java => sink/api/Configuration.java} |  14 +-
 .../api/Evaluator.java => sink/api/Event.java}     |  14 +-
 .../api/Evaluator.java => sink/api/Handler.java}   |  16 +-
 .../exception/SinkException.java}                  |  15 +-
 .../db/sink/local/LocalIoTDBConfiguration.java     |  51 ++++
 .../local/LocalIoTDBEvent.java}                    |  23 +-
 .../iotdb/db/sink/local/LocalIoTDBHandler.java     | 104 ++++++++
 .../iotdb/db/sink/mqtt/MQTTConfiguration.java      |  74 ++++++
 .../mqtt/MQTTEvent.java}                           |  48 ++--
 .../org/apache/iotdb/db/sink/mqtt/MQTTHandler.java |  85 +++++++
 .../iotdb/db/utils/windowing/api/Evaluator.java    |   3 +-
 .../windowing/runtime/WindowEvaluationTask.java    |   2 +-
 .../apache/iotdb/db/sink/LocalIoTDBSinkTest.java   | 217 ++++++++++++++++
 .../org/apache/iotdb/db/sink/MQTTSinkTest.java     | 204 +++++++++++++++
 21 files changed, 1621 insertions(+), 67 deletions(-)

diff --git a/docs/UserGuide/Advanced-Features/Triggers.md 
b/docs/UserGuide/Advanced-Features/Triggers.md
index a4f4df8..2c63cbb 100644
--- a/docs/UserGuide/Advanced-Features/Triggers.md
+++ b/docs/UserGuide/Advanced-Features/Triggers.md
@@ -489,6 +489,284 @@ The maximum number of window evaluation tasks that can be 
pending for execution.
 
 
 
+### Sink Utility
+
+The sink utility provides the ability for triggers to connect to external 
systems.
+
+It provides a programming paradigm. Each sink utility contains a `Handler` for 
processing data sending, a `Configuration` for configuring `Handler`, and an 
`Event` for describing the sending data.
+
+
+
+#### LocalIoTDBSink
+
+`LocalIoTDBSink` is used to insert data points to the local sequence.
+
+Before writing data, it is not required that the time series have been created.
+
+**Note**, in the scenario used for triggers, the listening time series and the 
written target time series should not be in the same storage group.
+
+Example:
+
+```java
+final String device = "root.alerting";
+final String[] measurements = new String[] {"local"};
+final TSDataType[] dataTypes = new TSDataType[] {TSDataType.DOUBLE};
+
+LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+localIoTDBHandler.open(new LocalIoTDBConfiguration(device, measurements, 
dataTypes));
+
+// insert 100 data points
+for (int i = 0; i < 100; ++i) {
+  final long timestamp = i;
+  final double value = i;
+  localIoTDBHandler.onEvent(new LocalIoTDBEvent(timestamp, value));
+}
+```
+
+Note that when you need to insert data points to a time series of type `TEXT`, 
you need to use `org.apache.iotdb.tsfile.utils.Binary`:
+
+```java
+// insert 100 data points
+for (int i = 0; i < 100; ++i) {
+  final long timestamp = i;
+  final String value = "" + i;
+  localIoTDBHandler.onEvent(new LocalIoTDBEvent(timestamp, 
Binary.valueOf(value)));
+}
+```
+
+
+
+#### MQTTSink
+
+In triggers, you can use `MQTTSink` to send data points to other IoTDB 
instances.
+
+Before sending data, it is not required that the time series have been created.
+
+Example:
+
+```java
+final String host = "127.0.0.1";
+final int port = 1883;
+final String username = "root";
+final String password = "root";
+final PartialPath device = new PartialPath("root.alerting");
+final String[] measurements = new String[] {"remote"};
+
+MQTTHandler mqttHandler = new MQTTHandler();
+mqttHandler.open(new MQTTConfiguration(host, port, username, password, device, 
measurements));
+
+final String topic = "test";
+final QoS qos = QoS.EXACTLY_ONCE;
+final boolean retain = false;
+// send 100 data points
+for (int i = 0; i < 100; ++i) {
+  final long timestamp = i;
+  final double value = i;
+  mqttHandler.onEvent(new MQTTEvent(topic, qos, retain, timestamp, value));
+}
+```
+
+
+
+## Maven Project Example
+
+If you use [Maven](http://search.maven.org/), you can refer to our sample 
project **trigger-example**.
+
+You can find it 
[here](https://github.com/apache/iotdb/tree/master/example/trigger).
+
+It shows:
+
+* How to use Maven to manage your trigger project
+* How to listen to data changes based on the user programming interface
+* How to use the windowing utility
+* How to use the sink utility
+
+```java
+package org.apache.iotdb.trigger;
+
+import org.apache.iotdb.db.engine.trigger.api.Trigger;
+import org.apache.iotdb.db.engine.trigger.api.TriggerAttributes;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.sink.mqtt.MQTTConfiguration;
+import org.apache.iotdb.db.sink.mqtt.MQTTEvent;
+import org.apache.iotdb.db.sink.mqtt.MQTTHandler;
+import org.apache.iotdb.db.sink.local.LocalIoTDBConfiguration;
+import org.apache.iotdb.db.sink.local.LocalIoTDBEvent;
+import org.apache.iotdb.db.sink.local.LocalIoTDBHandler;
+import 
org.apache.iotdb.db.utils.windowing.configuration.SlidingSizeWindowConfiguration;
+import 
org.apache.iotdb.db.utils.windowing.handler.SlidingSizeWindowEvaluationHandler;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import org.fusesource.mqtt.client.QoS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TriggerExample implements Trigger {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(TriggerExample.class);
+
+  private static final String TARGET_DEVICE = "root.alerting";
+
+  private final LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+  private final MQTTHandler mqttHandler = new MQTTHandler();
+
+  private SlidingSizeWindowEvaluationHandler windowEvaluationHandler;
+
+  @Override
+  public void onCreate(TriggerAttributes attributes) throws Exception {
+    LOGGER.info("onCreate(TriggerAttributes attributes)");
+
+    double lo = attributes.getDouble("lo");
+    double hi = attributes.getDouble("hi");
+
+    openSinkHandlers();
+
+    windowEvaluationHandler =
+        new SlidingSizeWindowEvaluationHandler(
+            new SlidingSizeWindowConfiguration(TSDataType.DOUBLE, 5, 5),
+            window -> {
+              double avg = 0;
+              for (int i = 0; i < window.size(); ++i) {
+                avg += window.getDouble(i);
+              }
+              avg /= window.size();
+
+              if (avg < lo || hi < avg) {
+                localIoTDBHandler.onEvent(new 
LocalIoTDBEvent(window.getTime(0), avg));
+                mqttHandler.onEvent(
+                    new MQTTEvent("test", QoS.EXACTLY_ONCE, false, 
window.getTime(0), avg));
+              }
+            });
+  }
+
+  @Override
+  public void onDrop() throws Exception {
+    LOGGER.info("onDrop()");
+    closeSinkHandlers();
+  }
+
+  @Override
+  public void onStart() throws Exception {
+    LOGGER.info("onStart()");
+    openSinkHandlers();
+  }
+
+  @Override
+  public void onStop() throws Exception {
+    LOGGER.info("onStop()");
+    closeSinkHandlers();
+  }
+
+  @Override
+  public Double fire(long timestamp, Double value) {
+    windowEvaluationHandler.collect(timestamp, value);
+    return value;
+  }
+
+  @Override
+  public double[] fire(long[] timestamps, double[] values) {
+    for (int i = 0; i < timestamps.length; ++i) {
+      windowEvaluationHandler.collect(timestamps[i], values[i]);
+    }
+    return values;
+  }
+
+  private void openSinkHandlers() throws Exception {
+    localIoTDBHandler.open(
+        new LocalIoTDBConfiguration(
+            TARGET_DEVICE, new String[] {"local"}, new TSDataType[] 
{TSDataType.DOUBLE}));
+    mqttHandler.open(
+        new MQTTConfiguration(
+            "127.0.0.1",
+            1883,
+            "root",
+            "root",
+            new PartialPath(TARGET_DEVICE),
+            new String[] {"remote"}));
+  }
+
+  private void closeSinkHandlers() throws Exception {
+    localIoTDBHandler.close();
+    mqttHandler.close();
+  }
+}
+```
+
+You can try this trigger by following the steps below:
+
+* Enable MQTT service by modifying `iotdb-engine.properties`
+
+  ``` properties
+  # whether to enable the mqtt service.
+  enable_mqtt_service=true
+  ```
+
+* Start the IoTDB server
+
+* Create time series via cli
+
+  ``` sql
+  CREATE TIMESERIES root.sg1.d1.s1 WITH DATATYPE=DOUBLE, ENCODING=PLAIN;
+  ```
+
+* Place the JAR (`trigger-example-0.12.0-SNAPSHOT.jar`) of **trigger-example** 
in the directory `iotdb-server-0.12.0-SNAPSHOT/ext/trigger` (or in a 
subdirectory of `iotdb-server-0.12.0-SNAPSHOT/ext/trigger`)
+
+  > You can specify the root path to load the trigger JAR package by modifying 
the `trigger_root_dir` in the configuration file.
+
+* Use the SQL statement to register the trigger, assuming that the name given 
to the trigger is `window-avg-alerter`
+
+* Use the `CREATE TRIGGER` statement to register the trigger via cli
+
+  ```sql
+  CREATE TRIGGER window-avg-alerter
+  AFTER INSERT
+  ON root.sg1.d1.s1
+  AS "org.apache.iotdb.trigger.TriggerExample"
+  WITH (
+    "lo" = "0",
+    "hi" = "10.0"
+  )
+  ```
+
+* Use cli to insert test data
+
+  ``` sql
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (1, 0);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (2, 2);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (3, 4);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (4, 6);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (5, 8);
+  
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (6, 10);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (7, 12);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (8, 14);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (9, 16);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (10, 18);
+  ```
+
+* Use cli to query data to verify the behavior of the trigger
+
+  ``` sql
+  SELECT * FROM root.alerting;
+  ```
+
+* Under normal circumstances, the following results should be shown
+
+  ``` sql
+  IoTDB> SELECT * FROM root.alerting;
+  +-----------------------------+--------------------+-------------------+
+  |                         Time|root.alerting.remote|root.alerting.local|
+  +-----------------------------+--------------------+-------------------+
+  |1970-01-01T08:00:00.006+08:00|                14.0|               14.0|
+  +-----------------------------+--------------------+-------------------+
+  Total line number = 1
+  It costs 0.006s
+  ```
+
+That's all, please enjoy it :D
+
+
+
 ## Important Notes
 
 * The trigger is implemented based on the reflection mechanism. Triggers can 
be dynamically registered and dropped without restarting the server.
diff --git a/docs/zh/UserGuide/Advanced-Features/Triggers.md 
b/docs/zh/UserGuide/Advanced-Features/Triggers.md
index 3c4ac28..b73ab9e 100644
--- a/docs/zh/UserGuide/Advanced-Features/Triggers.md
+++ b/docs/zh/UserGuide/Advanced-Features/Triggers.md
@@ -506,6 +506,284 @@ SlidingTimeWindowEvaluationHandler handler =
 
 
 
+### Sink工具类
+
+Sink工具类为触发器提供了连接外部系统的能力。
+
+它提供了一套编程范式。每一个Sink工具都包含一个用于处理数据发送的`Handler`、一个用于配置`Handler`的`Configuration`,还有一个用于描述发送数据的`Event`。
+
+
+
+#### LocalIoTDBSink
+
+`LocalIoTDBSink`用于向本地序列写入数据点。
+
+在写入数据前,不要求时间序列已被创建。
+
+**注意**,在触发器场景中,侦听的时间序列和写入的目标时间序列不要在同一个存储组下。
+
+使用示例:
+
+```java
+final String device = "root.alerting";
+final String[] measurements = new String[] {"local"};
+final TSDataType[] dataTypes = new TSDataType[] {TSDataType.DOUBLE};
+
+LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+localIoTDBHandler.open(new LocalIoTDBConfiguration(device, measurements, 
dataTypes));
+
+// insert 100 data points
+for (int i = 0; i < 100; ++i) {
+  final long timestamp = i;
+  final double value = i;
+  localIoTDBHandler.onEvent(new LocalIoTDBEvent(timestamp, value));
+}
+```
+
+注意,当您需要向某个`TEXT`类型的序列写入数据时,您需要借助`org.apache.iotdb.tsfile.utils.Binary`:
+
+```java
+// insert 100 data points
+for (int i = 0; i < 100; ++i) {
+  final long timestamp = i;
+  final String value = "" + i;
+  localIoTDBHandler.onEvent(new LocalIoTDBEvent(timestamp, 
Binary.valueOf(value)));
+}
+```
+
+
+
+#### MQTTSink
+
+触发器可以使用`MQTTSink`向其他的 IoTDB 实例发送数据点。
+
+在发送数据前,不要求时间序列已被创建。
+
+使用示例:
+
+```java
+final String host = "127.0.0.1";
+final int port = 1883;
+final String username = "root";
+final String password = "root";
+final PartialPath device = new PartialPath("root.alerting");
+final String[] measurements = new String[] {"remote"};
+
+MQTTHandler mqttHandler = new MQTTHandler();
+mqttHandler.open(new MQTTConfiguration(host, port, username, password, device, 
measurements));
+
+final String topic = "test";
+final QoS qos = QoS.EXACTLY_ONCE;
+final boolean retain = false;
+// send 100 data points
+for (int i = 0; i < 100; ++i) {
+  final long timestamp = i;
+  final double value = i;
+  mqttHandler.onEvent(new MQTTEvent(topic, qos, retain, timestamp, value));
+}
+```
+
+
+
+## 完整的Maven示例项目
+
+如果您使用[Maven](http://search.maven.org/),可以参考我们编写的示例项目**trigger-example**。
+
+您可以在[这里](https://github.com/apache/iotdb/tree/master/example/trigger)找到它。
+
+它展示了:
+
+* 如何使用Maven管理您的trigger项目
+* 如何基于触发器的用户编程接口实现数据侦听
+* 如何使用窗口工具类
+* 如何使用Sink工具类
+
+```java
+package org.apache.iotdb.trigger;
+
+import org.apache.iotdb.db.engine.trigger.api.Trigger;
+import org.apache.iotdb.db.engine.trigger.api.TriggerAttributes;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.sink.mqtt.MQTTConfiguration;
+import org.apache.iotdb.db.sink.mqtt.MQTTEvent;
+import org.apache.iotdb.db.sink.mqtt.MQTTHandler;
+import org.apache.iotdb.db.sink.local.LocalIoTDBConfiguration;
+import org.apache.iotdb.db.sink.local.LocalIoTDBEvent;
+import org.apache.iotdb.db.sink.local.LocalIoTDBHandler;
+import 
org.apache.iotdb.db.utils.windowing.configuration.SlidingSizeWindowConfiguration;
+import 
org.apache.iotdb.db.utils.windowing.handler.SlidingSizeWindowEvaluationHandler;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import org.fusesource.mqtt.client.QoS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TriggerExample implements Trigger {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(TriggerExample.class);
+
+  private static final String TARGET_DEVICE = "root.alerting";
+
+  private final LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+  private final MQTTHandler mqttHandler = new MQTTHandler();
+
+  private SlidingSizeWindowEvaluationHandler windowEvaluationHandler;
+
+  @Override
+  public void onCreate(TriggerAttributes attributes) throws Exception {
+    LOGGER.info("onCreate(TriggerAttributes attributes)");
+
+    double lo = attributes.getDouble("lo");
+    double hi = attributes.getDouble("hi");
+
+    openSinkHandlers();
+
+    windowEvaluationHandler =
+        new SlidingSizeWindowEvaluationHandler(
+            new SlidingSizeWindowConfiguration(TSDataType.DOUBLE, 5, 5),
+            window -> {
+              double avg = 0;
+              for (int i = 0; i < window.size(); ++i) {
+                avg += window.getDouble(i);
+              }
+              avg /= window.size();
+
+              if (avg < lo || hi < avg) {
+                localIoTDBHandler.onEvent(new 
LocalIoTDBEvent(window.getTime(0), avg));
+                mqttHandler.onEvent(
+                    new MQTTEvent("test", QoS.EXACTLY_ONCE, false, 
window.getTime(0), avg));
+              }
+            });
+  }
+
+  @Override
+  public void onDrop() throws Exception {
+    LOGGER.info("onDrop()");
+    closeSinkHandlers();
+  }
+
+  @Override
+  public void onStart() throws Exception {
+    LOGGER.info("onStart()");
+    openSinkHandlers();
+  }
+
+  @Override
+  public void onStop() throws Exception {
+    LOGGER.info("onStop()");
+    closeSinkHandlers();
+  }
+
+  @Override
+  public Double fire(long timestamp, Double value) {
+    windowEvaluationHandler.collect(timestamp, value);
+    return value;
+  }
+
+  @Override
+  public double[] fire(long[] timestamps, double[] values) {
+    for (int i = 0; i < timestamps.length; ++i) {
+      windowEvaluationHandler.collect(timestamps[i], values[i]);
+    }
+    return values;
+  }
+
+  private void openSinkHandlers() throws Exception {
+    localIoTDBHandler.open(
+        new LocalIoTDBConfiguration(
+            TARGET_DEVICE, new String[] {"local"}, new TSDataType[] 
{TSDataType.DOUBLE}));
+    mqttHandler.open(
+        new MQTTConfiguration(
+            "127.0.0.1",
+            1883,
+            "root",
+            "root",
+            new PartialPath(TARGET_DEVICE),
+            new String[] {"remote"}));
+  }
+
+  private void closeSinkHandlers() throws Exception {
+    localIoTDBHandler.close();
+    mqttHandler.close();
+  }
+}
+```
+
+您可以按照下面的步骤试用这个触发器:
+
+* 在`iotdb-engine.properties`中启用MQTT服务
+
+  ``` properties
+  # whether to enable the mqtt service.
+  enable_mqtt_service=true
+  ```
+
+* 启动 IoTDB 服务器
+
+* 通过 cli 创建时间序列
+
+  ``` sql
+  CREATE TIMESERIES root.sg1.d1.s1 WITH DATATYPE=DOUBLE, ENCODING=PLAIN;
+  ```
+
+* 将 **trigger-example** 中打包好的JAR(`trigger-example-0.12.0-SNAPSHOT.jar`)放置到目录 
`iotdb-server-0.12.0-SNAPSHOT/ext/trigger` 
(也可以是`iotdb-server-0.12.0-SNAPSHOT/ext/trigger`的子目录)下
+
+  > 您可以通过修改配置文件中的`trigger_root_dir`来指定加载触发器JAR包的根路径。
+
+* 使用SQL语句注册该触发器,假定赋予该触发器的名字为`window-avg-alerter`
+
+* 使用`CREATE TRIGGER`语句注册该触发器
+
+  ```sql
+  CREATE TRIGGER window-avg-alerter
+  AFTER INSERT
+  ON root.sg1.d1.s1
+  AS "org.apache.iotdb.trigger.TriggerExample"
+  WITH (
+    "lo" = "0", 
+    "hi" = "10.0"
+  )
+  ```
+
+* 使用 cli 插入测试数据
+
+  ``` sql
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (1, 0);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (2, 2);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (3, 4);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (4, 6);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (5, 8);
+  
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (6, 10);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (7, 12);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (8, 14);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (9, 16);
+  INSERT INTO root.sg1.d1(timestamp, s1) VALUES (10, 18);
+  ```
+
+* 使用 cli 查询数据以验证触发器的行为
+
+  ``` sql
+  SELECT * FROM root.alerting;
+  ```
+
+* 正常情况下,得到如下结果
+
+  ``` sql
+  IoTDB> SELECT * FROM root.alerting;
+  +-----------------------------+--------------------+-------------------+
+  |                         Time|root.alerting.remote|root.alerting.local|
+  +-----------------------------+--------------------+-------------------+
+  |1970-01-01T08:00:00.006+08:00|                14.0|               14.0|
+  +-----------------------------+--------------------+-------------------+
+  Total line number = 1
+  It costs 0.006s
+  ```
+
+以上就是基本的使用方法,希望您能喜欢 :D
+
+
+
 ## 重要注意事项
 
 * 触发器是通过反射技术动态装载的,因此您在装载过程中无需启停服务器。
diff --git a/example/pom.xml b/example/pom.xml
index 8db4d4a..588fc7c 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -44,6 +44,7 @@
         <module>mqtt</module>
         <module>pulsar</module>
         <module>udf</module>
+        <module>trigger</module>
     </modules>
     <build>
         <pluginManagement>
diff --git a/example/trigger/pom.xml b/example/trigger/pom.xml
new file mode 100644
index 0000000..8701de2
--- /dev/null
+++ b/example/trigger/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.iotdb</groupId>
+        <artifactId>iotdb-examples</artifactId>
+        <version>0.12.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>trigger-example</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.iotdb</groupId>
+            <artifactId>iotdb-server</artifactId>
+            <version>0.12.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.3</version>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <version>0.13</version>
+                <configuration>
+                    <consoleOutput>false</consoleOutput>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>license-check</id>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.diffplug.spotless</groupId>
+                <artifactId>spotless-maven-plugin</artifactId>
+                <version>2.4.2</version>
+                <configuration>
+                    <java>
+                        <googleJavaFormat>
+                            <version>1.7</version>
+                            <style>GOOGLE</style>
+                        </googleJavaFormat>
+                        <importOrder>
+                            <order>org.apache.iotdb,,javax,java,\#</order>
+                        </importOrder>
+                        <removeUnusedImports/>
+                    </java>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>spotless-check</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <profiles>
+        <profile>
+            <id>get-jar-with-dependencies</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                        <version>3.1.0</version>
+                        <configuration>
+                            <descriptorRefs>
+                                
<descriptorRef>jar-with-dependencies</descriptorRef>
+                            </descriptorRefs>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>make-assembly</id>
+                                <!-- this is used for inheritance merges -->
+                                <phase>package</phase>
+                                <!-- bind to the packaging phase -->
+                                <goals>
+                                    <goal>single</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git 
a/example/trigger/src/main/java/org/apache/iotdb/trigger/TriggerExample.java 
b/example/trigger/src/main/java/org/apache/iotdb/trigger/TriggerExample.java
new file mode 100644
index 0000000..55d96aa
--- /dev/null
+++ b/example/trigger/src/main/java/org/apache/iotdb/trigger/TriggerExample.java
@@ -0,0 +1,127 @@
+/*
+ * 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.trigger;
+
+import org.apache.iotdb.db.engine.trigger.api.Trigger;
+import org.apache.iotdb.db.engine.trigger.api.TriggerAttributes;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.sink.local.LocalIoTDBConfiguration;
+import org.apache.iotdb.db.sink.local.LocalIoTDBEvent;
+import org.apache.iotdb.db.sink.local.LocalIoTDBHandler;
+import org.apache.iotdb.db.sink.mqtt.MQTTConfiguration;
+import org.apache.iotdb.db.sink.mqtt.MQTTEvent;
+import org.apache.iotdb.db.sink.mqtt.MQTTHandler;
+import 
org.apache.iotdb.db.utils.windowing.configuration.SlidingSizeWindowConfiguration;
+import 
org.apache.iotdb.db.utils.windowing.handler.SlidingSizeWindowEvaluationHandler;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import org.fusesource.mqtt.client.QoS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TriggerExample implements Trigger {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(TriggerExample.class);
+
+  private static final String TARGET_DEVICE = "root.alerting";
+
+  private final LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+  private final MQTTHandler mqttHandler = new MQTTHandler();
+
+  private SlidingSizeWindowEvaluationHandler windowEvaluationHandler;
+
+  @Override
+  public void onCreate(TriggerAttributes attributes) throws Exception {
+    LOGGER.info("onCreate(TriggerAttributes attributes)");
+
+    double lo = attributes.getDouble("lo");
+    double hi = attributes.getDouble("hi");
+
+    openSinkHandlers();
+
+    windowEvaluationHandler =
+        new SlidingSizeWindowEvaluationHandler(
+            new SlidingSizeWindowConfiguration(TSDataType.DOUBLE, 5, 5),
+            window -> {
+              double avg = 0;
+              for (int i = 0; i < window.size(); ++i) {
+                avg += window.getDouble(i);
+              }
+              avg /= window.size();
+
+              if (avg < lo || hi < avg) {
+                localIoTDBHandler.onEvent(new 
LocalIoTDBEvent(window.getTime(0), avg));
+                mqttHandler.onEvent(
+                    new MQTTEvent("test", QoS.EXACTLY_ONCE, false, 
window.getTime(0), avg));
+              }
+            });
+  }
+
+  @Override
+  public void onDrop() throws Exception {
+    LOGGER.info("onDrop()");
+    closeSinkHandlers();
+  }
+
+  @Override
+  public void onStart() throws Exception {
+    LOGGER.info("onStart()");
+    openSinkHandlers();
+  }
+
+  @Override
+  public void onStop() throws Exception {
+    LOGGER.info("onStop()");
+    closeSinkHandlers();
+  }
+
+  @Override
+  public Double fire(long timestamp, Double value) {
+    windowEvaluationHandler.collect(timestamp, value);
+    return value;
+  }
+
+  @Override
+  public double[] fire(long[] timestamps, double[] values) {
+    for (int i = 0; i < timestamps.length; ++i) {
+      windowEvaluationHandler.collect(timestamps[i], values[i]);
+    }
+    return values;
+  }
+
+  private void openSinkHandlers() throws Exception {
+    localIoTDBHandler.open(
+        new LocalIoTDBConfiguration(
+            TARGET_DEVICE, new String[] {"local"}, new TSDataType[] 
{TSDataType.DOUBLE}));
+    mqttHandler.open(
+        new MQTTConfiguration(
+            "127.0.0.1",
+            1883,
+            "root",
+            "root",
+            new PartialPath(TARGET_DEVICE),
+            new String[] {"remote"}));
+  }
+
+  private void closeSinkHandlers() throws Exception {
+    localIoTDBHandler.close();
+    mqttHandler.close();
+  }
+}
diff --git a/server/pom.xml b/server/pom.xml
index fecd1d9..c30b7d0 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -203,6 +203,10 @@
             <artifactId>netty-buffer</artifactId>
             <version>4.1.27.Final</version>
         </dependency>
+        <dependency>
+            <groupId>org.fusesource.mqtt-client</groupId>
+            <artifactId>mqtt-client</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/server/src/assembly/resources/conf/iotdb-engine.properties 
b/server/src/assembly/resources/conf/iotdb-engine.properties
index 3881a0b..f909b39 100644
--- a/server/src/assembly/resources/conf/iotdb-engine.properties
+++ b/server/src/assembly/resources/conf/iotdb-engine.properties
@@ -643,7 +643,7 @@ timestamp_precision=ms
 # The size of log buffer for every trigger management operation plan. If the 
size of a trigger
 # management operation plan is larger than this parameter, the trigger 
management operation plan
 # will be rejected by TriggerManager.
-tlog_buffer_size=1048576
+# tlog_buffer_size=1048576
 
 # Uncomment the following field to configure the trigger root directory.
 # For Window platform
@@ -655,11 +655,11 @@ tlog_buffer_size=1048576
 # trigger_root_dir=ext/trigger
 
 # How many threads can be used for evaluating sliding windows. When <= 0, use 
CPU core number.
-concurrent_window_evaluation_thread=0
+# concurrent_window_evaluation_thread=0
 
 # Max number of window evaluation tasks that can be pending for execution. 
When <= 0, the value is
 # 64 by default.
-max_pending_window_evaluation_tasks = 64
+# max_pending_window_evaluation_tasks = 64
 
 ####################
 ### Index Configuration
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java 
b/server/src/main/java/org/apache/iotdb/db/sink/api/Configuration.java
similarity index 74%
copy from 
server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
copy to server/src/main/java/org/apache/iotdb/db/sink/api/Configuration.java
index 7e2047a..f24994f 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
+++ b/server/src/main/java/org/apache/iotdb/db/sink/api/Configuration.java
@@ -17,16 +17,6 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.utils.windowing.api;
+package org.apache.iotdb.db.sink.api;
 
-import java.util.concurrent.RejectedExecutionException;
-
-@FunctionalInterface
-public interface Evaluator {
-
-  void evaluate(Window window);
-
-  default void onRejection(Window window) {
-    throw new RejectedExecutionException();
-  }
-}
+public interface Configuration {}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java 
b/server/src/main/java/org/apache/iotdb/db/sink/api/Event.java
similarity index 74%
copy from 
server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
copy to server/src/main/java/org/apache/iotdb/db/sink/api/Event.java
index 7e2047a..a4a70e0 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
+++ b/server/src/main/java/org/apache/iotdb/db/sink/api/Event.java
@@ -17,16 +17,6 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.utils.windowing.api;
+package org.apache.iotdb.db.sink.api;
 
-import java.util.concurrent.RejectedExecutionException;
-
-@FunctionalInterface
-public interface Evaluator {
-
-  void evaluate(Window window);
-
-  default void onRejection(Window window) {
-    throw new RejectedExecutionException();
-  }
-}
+public interface Event {}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java 
b/server/src/main/java/org/apache/iotdb/db/sink/api/Handler.java
similarity index 69%
copy from 
server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
copy to server/src/main/java/org/apache/iotdb/db/sink/api/Handler.java
index 7e2047a..1827b0c 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
+++ b/server/src/main/java/org/apache/iotdb/db/sink/api/Handler.java
@@ -17,16 +17,16 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.utils.windowing.api;
+package org.apache.iotdb.db.sink.api;
 
-import java.util.concurrent.RejectedExecutionException;
+public interface Handler<C extends Configuration, E extends Event> {
 
-@FunctionalInterface
-public interface Evaluator {
+  @SuppressWarnings("squid:S112")
+  default void open(C configuration) throws Exception {}
 
-  void evaluate(Window window);
+  @SuppressWarnings("squid:S112")
+  default void close() throws Exception {}
 
-  default void onRejection(Window window) {
-    throw new RejectedExecutionException();
-  }
+  @SuppressWarnings("squid:S112")
+  void onEvent(E event) throws Exception;
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java 
b/server/src/main/java/org/apache/iotdb/db/sink/exception/SinkException.java
similarity index 74%
copy from 
server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
copy to 
server/src/main/java/org/apache/iotdb/db/sink/exception/SinkException.java
index 7e2047a..66ef627 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
+++ b/server/src/main/java/org/apache/iotdb/db/sink/exception/SinkException.java
@@ -17,16 +17,15 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.utils.windowing.api;
+package org.apache.iotdb.db.sink.exception;
 
-import java.util.concurrent.RejectedExecutionException;
+public class SinkException extends Exception {
 
-@FunctionalInterface
-public interface Evaluator {
-
-  void evaluate(Window window);
+  public SinkException(String message) {
+    super(message);
+  }
 
-  default void onRejection(Window window) {
-    throw new RejectedExecutionException();
+  public SinkException(String message, Throwable cause) {
+    super(message, cause);
   }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBConfiguration.java
 
b/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBConfiguration.java
new file mode 100644
index 0000000..576a47e
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBConfiguration.java
@@ -0,0 +1,51 @@
+/*
+ * 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.sink.local;
+
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.sink.api.Configuration;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+public class LocalIoTDBConfiguration implements Configuration {
+
+  private final PartialPath device;
+  private final String[] measurements;
+  private final TSDataType[] dataTypes;
+
+  public LocalIoTDBConfiguration(String device, String[] measurements, 
TSDataType[] dataTypes)
+      throws IllegalPathException {
+    this.device = new PartialPath(device);
+    this.measurements = measurements;
+    this.dataTypes = dataTypes;
+  }
+
+  public PartialPath getDevice() {
+    return device;
+  }
+
+  public String[] getMeasurements() {
+    return measurements;
+  }
+
+  public TSDataType[] getDataTypes() {
+    return dataTypes;
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java 
b/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBEvent.java
similarity index 65%
copy from 
server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
copy to server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBEvent.java
index 7e2047a..62bfd68 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
+++ b/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBEvent.java
@@ -17,16 +17,25 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.utils.windowing.api;
+package org.apache.iotdb.db.sink.local;
 
-import java.util.concurrent.RejectedExecutionException;
+import org.apache.iotdb.db.sink.api.Event;
 
-@FunctionalInterface
-public interface Evaluator {
+public class LocalIoTDBEvent implements Event {
 
-  void evaluate(Window window);
+  private final long timestamp;
+  private final Object[] values;
 
-  default void onRejection(Window window) {
-    throw new RejectedExecutionException();
+  public LocalIoTDBEvent(long timestamp, Object... values) {
+    this.timestamp = timestamp;
+    this.values = values;
+  }
+
+  public long getTimestamp() {
+    return timestamp;
+  }
+
+  public Object[] getValues() {
+    return values;
   }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBHandler.java 
b/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBHandler.java
new file mode 100644
index 0000000..69a6537
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/sink/local/LocalIoTDBHandler.java
@@ -0,0 +1,104 @@
+/*
+ * 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.sink.local;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.metadata.StorageGroupNotSetException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.executor.IPlanExecutor;
+import org.apache.iotdb.db.qp.executor.PlanExecutor;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.crud.InsertRowPlan;
+import org.apache.iotdb.db.service.IoTDB;
+import org.apache.iotdb.db.sink.api.Handler;
+import org.apache.iotdb.db.sink.exception.SinkException;
+import org.apache.iotdb.tsfile.common.conf.TSFileDescriptor;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.util.Collections;
+
+import static 
org.apache.iotdb.db.utils.EncodingInferenceUtils.getDefaultEncoding;
+
+public class LocalIoTDBHandler implements Handler<LocalIoTDBConfiguration, 
LocalIoTDBEvent> {
+
+  private IPlanExecutor executor;
+
+  private PartialPath device;
+  private String[] measurements;
+  private TSDataType[] dataTypes;
+
+  @Override
+  public void open(LocalIoTDBConfiguration configuration) throws Exception {
+    executor = new PlanExecutor();
+
+    device = configuration.getDevice();
+    measurements = configuration.getMeasurements();
+    dataTypes = configuration.getDataTypes();
+
+    createOrCheckTimeseries();
+  }
+
+  private void createOrCheckTimeseries() throws MetadataException, 
SinkException {
+    for (int i = 0; i < measurements.length; ++i) {
+      String measurement = measurements[i];
+      TSDataType dataType = dataTypes[i];
+
+      PartialPath path = new PartialPath(device.getFullPath(), measurement);
+      if (!IoTDB.metaManager.isPathExist(path)) {
+        IoTDB.metaManager.createTimeseries(
+            path,
+            dataType,
+            getDefaultEncoding(dataType),
+            TSFileDescriptor.getInstance().getConfig().getCompressor(),
+            Collections.emptyMap());
+      } else {
+        if (!IoTDB.metaManager.getSeriesSchema(device, 
measurement).getType().equals(dataType)) {
+          throw new SinkException(
+              String.format("The data type of %s you provided was not 
correct.", path));
+        }
+      }
+    }
+  }
+
+  @Override
+  public void onEvent(LocalIoTDBEvent event)
+      throws QueryProcessException, StorageEngineException, 
StorageGroupNotSetException {
+    InsertRowPlan plan = new InsertRowPlan();
+    plan.setNeedInferType(false);
+    plan.setDeviceId(device);
+    plan.setMeasurements(measurements);
+    plan.setDataTypes(dataTypes);
+    plan.setTime(event.getTimestamp());
+    plan.setValues(event.getValues());
+    executeNonQuery(plan);
+  }
+
+  private void executeNonQuery(PhysicalPlan plan)
+      throws QueryProcessException, StorageGroupNotSetException, 
StorageEngineException {
+    if (IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
+      throw new QueryProcessException(
+          "Current system mode is read-only, non-query operation is not 
supported.");
+    }
+    executor.processNonQuery(plan);
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTConfiguration.java 
b/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTConfiguration.java
new file mode 100644
index 0000000..7e89b81
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTConfiguration.java
@@ -0,0 +1,74 @@
+/*
+ * 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.sink.mqtt;
+
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.sink.api.Configuration;
+
+public class MQTTConfiguration implements Configuration {
+
+  private final String host;
+  private final int port;
+
+  private final String username;
+  private final String password;
+
+  private final PartialPath device;
+  private final String[] measurements;
+
+  public MQTTConfiguration(
+      String host,
+      int port,
+      String username,
+      String password,
+      PartialPath device,
+      String[] measurements) {
+    this.host = host;
+    this.port = port;
+    this.username = username;
+    this.password = password;
+    this.device = device;
+    this.measurements = measurements;
+  }
+
+  public String getHost() {
+    return host;
+  }
+
+  public int getPort() {
+    return port;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public String[] getMeasurements() {
+    return measurements;
+  }
+
+  public PartialPath getDevice() {
+    return device;
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/runtime/WindowEvaluationTask.java
 b/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTEvent.java
similarity index 51%
copy from 
server/src/main/java/org/apache/iotdb/db/utils/windowing/runtime/WindowEvaluationTask.java
copy to server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTEvent.java
index 794b794..1c876ec 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/runtime/WindowEvaluationTask.java
+++ b/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTEvent.java
@@ -17,28 +17,46 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.utils.windowing.runtime;
+package org.apache.iotdb.db.sink.mqtt;
 
-import org.apache.iotdb.db.concurrent.WrappedRunnable;
-import org.apache.iotdb.db.utils.windowing.api.Evaluator;
-import org.apache.iotdb.db.utils.windowing.api.Window;
+import org.apache.iotdb.db.sink.api.Event;
 
-public class WindowEvaluationTask extends WrappedRunnable {
+import org.fusesource.mqtt.client.QoS;
 
-  private final Evaluator evaluator;
-  private final Window window;
+public class MQTTEvent implements Event {
 
-  public WindowEvaluationTask(Evaluator evaluator, Window window) {
-    this.evaluator = evaluator;
-    this.window = window;
+  private final String topic;
+  private final QoS qos;
+  private final boolean retain;
+
+  private final long timestamp;
+  private final Object[] values;
+
+  public MQTTEvent(String topic, QoS qos, boolean retain, long timestamp, 
Object... values) {
+    this.topic = topic;
+    this.qos = qos;
+    this.retain = retain;
+    this.timestamp = timestamp;
+    this.values = values;
+  }
+
+  public String getTopic() {
+    return topic;
+  }
+
+  public QoS getQoS() {
+    return qos;
+  }
+
+  public boolean retain() {
+    return retain;
   }
 
-  @Override
-  public void runMayThrow() {
-    evaluator.evaluate(window);
+  public long getTimestamp() {
+    return timestamp;
   }
 
-  public void onRejection() {
-    evaluator.onRejection(window);
+  public Object[] getValues() {
+    return values;
   }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTHandler.java 
b/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTHandler.java
new file mode 100644
index 0000000..342a3e1
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/sink/mqtt/MQTTHandler.java
@@ -0,0 +1,85 @@
+/*
+ * 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.sink.mqtt;
+
+import org.apache.iotdb.db.sink.api.Handler;
+import org.apache.iotdb.db.sink.exception.SinkException;
+import org.apache.iotdb.tsfile.utils.Binary;
+
+import org.fusesource.mqtt.client.BlockingConnection;
+import org.fusesource.mqtt.client.MQTT;
+
+public class MQTTHandler implements Handler<MQTTConfiguration, MQTTEvent> {
+
+  private BlockingConnection connection;
+
+  private String payloadFormatter;
+
+  @Override
+  public void open(MQTTConfiguration configuration) throws Exception {
+    MQTT mqtt = new MQTT();
+    mqtt.setHost(configuration.getHost(), configuration.getPort());
+    mqtt.setUserName(configuration.getUsername());
+    mqtt.setPassword(configuration.getPassword());
+    connection = mqtt.blockingConnection();
+    connection.connect();
+
+    payloadFormatter = generatePayloadFormatter(configuration);
+  }
+
+  private static String generatePayloadFormatter(MQTTConfiguration 
configuration)
+      throws SinkException {
+    return String.format(
+        "{\"device\":\"%s\",\"measurements\":[%s]%s",
+        configuration.getDevice(),
+        arrayToJson(configuration.getMeasurements()),
+        ",\"timestamp\":%d,\"values\":[%s]}");
+  }
+
+  private static String arrayToJson(Object[] array) throws SinkException {
+    if (array.length <= 0) {
+      throw new SinkException("The number of measurements should be 
positive.");
+    }
+
+    StringBuilder sb = new StringBuilder(objectToJson(array[0]));
+    for (int i = 1; i < array.length; ++i) {
+      sb.append(',').append(objectToJson(array[i]));
+    }
+    return sb.toString();
+  }
+
+  private static String objectToJson(Object object) {
+    return (object instanceof String || object instanceof Binary)
+        ? ('\"' + object.toString() + '\"')
+        : object.toString();
+  }
+
+  @Override
+  public void close() throws Exception {
+    connection.disconnect();
+  }
+
+  @Override
+  public void onEvent(MQTTEvent event) throws Exception {
+    String payload =
+        String.format(payloadFormatter, event.getTimestamp(), 
arrayToJson(event.getValues()));
+    connection.publish(event.getTopic(), payload.getBytes(), event.getQoS(), 
event.retain());
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java 
b/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
index 7e2047a..9937b4b 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/utils/windowing/api/Evaluator.java
@@ -24,7 +24,8 @@ import java.util.concurrent.RejectedExecutionException;
 @FunctionalInterface
 public interface Evaluator {
 
-  void evaluate(Window window);
+  @SuppressWarnings("squid:S112")
+  void evaluate(Window window) throws Exception;
 
   default void onRejection(Window window) {
     throw new RejectedExecutionException();
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/runtime/WindowEvaluationTask.java
 
b/server/src/main/java/org/apache/iotdb/db/utils/windowing/runtime/WindowEvaluationTask.java
index 794b794..053296b 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/utils/windowing/runtime/WindowEvaluationTask.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/utils/windowing/runtime/WindowEvaluationTask.java
@@ -34,7 +34,7 @@ public class WindowEvaluationTask extends WrappedRunnable {
   }
 
   @Override
-  public void runMayThrow() {
+  public void runMayThrow() throws Exception {
     evaluator.evaluate(window);
   }
 
diff --git 
a/server/src/test/java/org/apache/iotdb/db/sink/LocalIoTDBSinkTest.java 
b/server/src/test/java/org/apache/iotdb/db/sink/LocalIoTDBSinkTest.java
new file mode 100644
index 0000000..adcd434
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/sink/LocalIoTDBSinkTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.sink;
+
+import org.apache.iotdb.db.sink.local.LocalIoTDBConfiguration;
+import org.apache.iotdb.db.sink.local.LocalIoTDBEvent;
+import org.apache.iotdb.db.sink.local.LocalIoTDBHandler;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.utils.Binary;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class LocalIoTDBSinkTest {
+
+  @Before
+  public void setUp() throws Exception {
+    EnvironmentUtils.envSetUp();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    EnvironmentUtils.cleanEnv();
+  }
+
+  @Test
+  public void onEventUsingSingleSensorHandler() throws Exception {
+    LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+    localIoTDBHandler.open(
+        new LocalIoTDBConfiguration(
+            "root.sg1.d1", new String[] {"s1"}, new TSDataType[] 
{TSDataType.INT32}));
+
+    for (int i = 0; i < 10000; ++i) {
+      localIoTDBHandler.onEvent(new LocalIoTDBEvent(i, i));
+    }
+
+    localIoTDBHandler.close();
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      Assert.assertTrue(statement.execute("select * from root"));
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+
+        checkHeader(
+            resultSetMetaData,
+            "Time,root.sg1.d1.s1,",
+            new int[] {
+              Types.TIMESTAMP, Types.INTEGER,
+            });
+
+        int count = 0;
+        while (resultSet.next()) {
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            assertEquals(count, Double.parseDouble(resultSet.getString(i)), 
0.0);
+          }
+          count++;
+        }
+        Assert.assertEquals(10000, count);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void onEventUsingMultiSensorsHandler() throws Exception {
+    LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+    localIoTDBHandler.open(
+        new LocalIoTDBConfiguration(
+            "root.sg1.d1",
+            new String[] {"s1", "s2", "s3", "s4", "s5", "s6"},
+            new TSDataType[] {
+              TSDataType.INT32,
+              TSDataType.INT64,
+              TSDataType.FLOAT,
+              TSDataType.DOUBLE,
+              TSDataType.BOOLEAN,
+              TSDataType.TEXT
+            }));
+
+    for (int i = 0; i < 10000; ++i) {
+      localIoTDBHandler.onEvent(
+          new LocalIoTDBEvent(
+              i,
+              i,
+              (long) i,
+              (float) i,
+              (double) i,
+              i % 2 == 0,
+              Binary.valueOf(String.valueOf(i))));
+    }
+
+    localIoTDBHandler.close();
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      Assert.assertTrue(statement.execute("select * from root"));
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+
+        checkHeader(
+            resultSetMetaData,
+            "Time,root.sg1.d1.s1,root.sg1.d1.s2,root.sg1.d1.s3,"
+                + "root.sg1.d1.s4,root.sg1.d1.s5,root.sg1.d1.s6,",
+            new int[] {
+              Types.TIMESTAMP,
+              Types.INTEGER,
+              Types.BIGINT,
+              Types.FLOAT,
+              Types.DOUBLE,
+              Types.BOOLEAN,
+              Types.VARCHAR,
+            });
+
+        int count = 0;
+        while (resultSet.next()) {
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            try {
+              assertEquals(count, Double.parseDouble(resultSet.getString(i)), 
0.0);
+            } catch (NumberFormatException e) {
+              assertEquals(count % 2 == 0, 
Boolean.parseBoolean(resultSet.getString(i)));
+            }
+          }
+          count++;
+        }
+        Assert.assertEquals(10000, count);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void checkHeader(
+      ResultSetMetaData resultSetMetaData, String expectedHeaderStrings, int[] 
expectedTypes)
+      throws SQLException {
+    String[] expectedHeaders = expectedHeaderStrings.split(",");
+    Map<String, Integer> expectedHeaderToTypeIndexMap = new HashMap<>();
+    for (int i = 0; i < expectedHeaders.length; ++i) {
+      expectedHeaderToTypeIndexMap.put(expectedHeaders[i], i);
+    }
+
+    for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+      Integer typeIndex = 
expectedHeaderToTypeIndexMap.get(resultSetMetaData.getColumnName(i));
+      Assert.assertNotNull(typeIndex);
+      Assert.assertEquals(expectedTypes[typeIndex], 
resultSetMetaData.getColumnType(i));
+    }
+  }
+
+  @Test(expected = ClassCastException.class)
+  public void onEventWithWrongType1() throws Exception {
+    LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+    localIoTDBHandler.open(
+        new LocalIoTDBConfiguration(
+            "root.sg1.d1", new String[] {"s1"}, new TSDataType[] 
{TSDataType.INT32}));
+
+    localIoTDBHandler.onEvent(new LocalIoTDBEvent(0, 
Binary.valueOf(String.valueOf(0))));
+
+    localIoTDBHandler.close();
+  }
+
+  @Test(expected = ClassCastException.class)
+  public void onEventWithWrongType2() throws Exception {
+    LocalIoTDBHandler localIoTDBHandler = new LocalIoTDBHandler();
+    localIoTDBHandler.open(
+        new LocalIoTDBConfiguration(
+            "root.sg1.d1", new String[] {"s1"}, new TSDataType[] 
{TSDataType.TEXT}));
+
+    localIoTDBHandler.onEvent(new LocalIoTDBEvent(0, String.valueOf(0)));
+
+    localIoTDBHandler.close();
+  }
+}
diff --git a/server/src/test/java/org/apache/iotdb/db/sink/MQTTSinkTest.java 
b/server/src/test/java/org/apache/iotdb/db/sink/MQTTSinkTest.java
new file mode 100644
index 0000000..8e7b92e
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/sink/MQTTSinkTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.sink;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.sink.mqtt.MQTTConfiguration;
+import org.apache.iotdb.db.sink.mqtt.MQTTEvent;
+import org.apache.iotdb.db.sink.mqtt.MQTTHandler;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+
+import org.fusesource.mqtt.client.QoS;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@SuppressWarnings("squid:S2925")
+public class MQTTSinkTest {
+
+  @Before
+  public void setUp() throws Exception {
+    IoTDBDescriptor.getInstance().getConfig().setEnableMQTTService(true);
+    EnvironmentUtils.envSetUp();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    EnvironmentUtils.cleanEnv();
+  }
+
+  @Test
+  public void onEventUsingSingleSensorHandler() throws Exception {
+    MQTTHandler mqttHandler = new MQTTHandler();
+    mqttHandler.open(
+        new MQTTConfiguration(
+            "127.0.0.1",
+            1883,
+            "root",
+            "root",
+            new PartialPath("root.sg1.d1"),
+            new String[] {"s1"}));
+
+    for (int i = 0; i < 10000; ++i) {
+      mqttHandler.onEvent(new MQTTEvent("test", QoS.EXACTLY_ONCE, false, i, 
i));
+    }
+
+    mqttHandler.close();
+
+    Thread.sleep(1000);
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      Assert.assertTrue(statement.execute("select * from root"));
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+
+        checkHeader(
+            resultSetMetaData,
+            "Time,root.sg1.d1.s1,",
+            new int[] {
+              Types.TIMESTAMP, Types.FLOAT,
+            });
+
+        int count = 0;
+        while (resultSet.next()) {
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            assertEquals(count, Double.parseDouble(resultSet.getString(i)), 
0.0);
+          }
+          count++;
+        }
+        Assert.assertEquals(10000, count);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void onEventUsingMultiSensorsHandler() throws Exception {
+    MQTTHandler mqttHandler = new MQTTHandler();
+    mqttHandler.open(
+        new MQTTConfiguration(
+            "127.0.0.1",
+            1883,
+            "root",
+            "root",
+            new PartialPath("root.sg1.d1"),
+            new String[] {"s1", "s2", "s3", "s4", "s5", "s6"}));
+
+    for (int i = 0; i < 10000; ++i) {
+      mqttHandler.onEvent(
+          new MQTTEvent(
+              "test",
+              QoS.EXACTLY_ONCE,
+              false,
+              i,
+              i,
+              (long) i,
+              (float) i,
+              (double) i,
+              i % 2 == 0,
+              String.valueOf(i)));
+    }
+
+    mqttHandler.close();
+
+    Thread.sleep(1000);
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      Assert.assertTrue(statement.execute("select * from root"));
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+
+        checkHeader(
+            resultSetMetaData,
+            "Time,root.sg1.d1.s1,root.sg1.d1.s2,root.sg1.d1.s3,"
+                + "root.sg1.d1.s4,root.sg1.d1.s5,root.sg1.d1.s6,",
+            new int[] {
+              Types.TIMESTAMP,
+              Types.FLOAT,
+              Types.FLOAT,
+              Types.FLOAT,
+              Types.FLOAT,
+              Types.BOOLEAN,
+              Types.FLOAT,
+            });
+
+        int count = 0;
+        while (resultSet.next()) {
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            try {
+              assertEquals(count, Double.parseDouble(resultSet.getString(i)), 
0.0);
+            } catch (NumberFormatException e) {
+              assertEquals(count % 2 == 0, 
Boolean.parseBoolean(resultSet.getString(i)));
+            }
+          }
+          count++;
+        }
+        Assert.assertEquals(10000, count);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void checkHeader(
+      ResultSetMetaData resultSetMetaData, String expectedHeaderStrings, int[] 
expectedTypes)
+      throws SQLException {
+    String[] expectedHeaders = expectedHeaderStrings.split(",");
+    Map<String, Integer> expectedHeaderToTypeIndexMap = new HashMap<>();
+    for (int i = 0; i < expectedHeaders.length; ++i) {
+      expectedHeaderToTypeIndexMap.put(expectedHeaders[i], i);
+    }
+
+    for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+      Integer typeIndex = 
expectedHeaderToTypeIndexMap.get(resultSetMetaData.getColumnName(i));
+      Assert.assertNotNull(typeIndex);
+      Assert.assertEquals(expectedTypes[typeIndex], 
resultSetMetaData.getColumnType(i));
+    }
+  }
+}

Reply via email to