You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2020/04/27 02:51:19 UTC

[incubator-iotdb] 01/01: support upsert in alter timeseries tag/attribute syntax

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

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

commit b5ad99aa75bdc4718183ebff3f257e71c42941df
Author: JackieTien97 <Ja...@foxmail.com>
AuthorDate: Mon Apr 27 10:50:52 2020 +0800

    support upsert in alter timeseries tag/attribute syntax
---
 .../1-DDL Data Definition Language.md              |   6 +-
 .../5-Operation Manual/4-SQL Reference.md          |   8 +
 .../1-DDL Data Definition Language.md              |   5 +
 .../5-Operation Manual/4-SQL Reference.md          |   8 +
 .../org/apache/iotdb/db/qp/strategy/SqlBase.g4     |   5 +
 .../org/apache/iotdb/db/metadata/MManager.java     | 166 +++++++++++++++++----
 .../apache/iotdb/db/qp/executor/PlanExecutor.java  |  89 +++++++++--
 .../db/qp/logical/sys/AlterTimeSeriesOperator.java |  32 +++-
 .../db/qp/physical/sys/AlterTimeSeriesPlan.java    |  40 ++++-
 .../iotdb/db/qp/strategy/LogicalGenerator.java     |  56 +++----
 .../iotdb/db/qp/strategy/PhysicalGenerator.java    |   4 +-
 .../iotdb/db/integration/IoTDBTagAlterIT.java      | 104 ++++++++++++-
 12 files changed, 432 insertions(+), 91 deletions(-)

diff --git a/docs/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md b/docs/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md
index 03e1efa..61bf416 100644
--- a/docs/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md	
+++ b/docs/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md	
@@ -111,7 +111,11 @@ ALTER timeseries root.turbine.d1.s1 ADD TAGS tag3=v3, tag4=v4
 ```
 ALTER timeseries root.turbine.d1.s1 ADD ATTRIBUTES attr3=v3, attr4=v4
 ```
-
+* upsert tags and attributes
+> add new key-value if the key doesn't exist, otherwise, update the old one with new value.
+```
+ALTER timeseries root.turbine.d1.s1 UPSERT TAGS(tag3=v3, tag4=v4) ATTRIBUTES(attr3=v3, attr4=v4)
+```
 
 ## Show Timeseries
 
diff --git a/docs/UserGuide/5-Operation Manual/4-SQL Reference.md b/docs/UserGuide/5-Operation Manual/4-SQL Reference.md
index 75c1e61..6562e63 100644
--- a/docs/UserGuide/5-Operation Manual/4-SQL Reference.md	
+++ b/docs/UserGuide/5-Operation Manual/4-SQL Reference.md	
@@ -113,12 +113,20 @@ alterClause
     | DROP ID (COMMA ID)*
     | ADD TAGS property (COMMA property)*
     | ADD ATTRIBUTES property (COMMA property)*
+    | UPSERT tagClause attributeClause
+    ;
+attributeClause
+    : (ATTRIBUTES LR_BRACKET property (COMMA property)* RR_BRACKET)?
+    ;
+tagClause
+    : (TAGS LR_BRACKET property (COMMA property)* RR_BRACKET)?
     ;
 Eg: ALTER timeseries root.turbine.d1.s1 RENAME tag1 TO newTag1
 Eg: ALTER timeseries root.turbine.d1.s1 SET tag1=newV1, attr1=newV1
 Eg: ALTER timeseries root.turbine.d1.s1 DROP tag1, tag2
 Eg: ALTER timeseries root.turbine.d1.s1 ADD TAGS tag3=v3, tag4=v4
 Eg: ALTER timeseries root.turbine.d1.s1 ADD ATTRIBUTES attr3=v3, attr4=v4
+EG: ALTER timeseries root.turbine.d1.s1 UPSERT TAGS(tag2=newV2, tag3=v3) ATTRIBUTES(attr3=v3, attr4=v4)
 ```
 
 * Show All Timeseries Statement
diff --git a/docs/zh/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md b/docs/zh/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md
index b45956f..867f120 100644
--- a/docs/zh/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md	
+++ b/docs/zh/UserGuide/5-Operation Manual/1-DDL Data Definition Language.md	
@@ -109,6 +109,11 @@ ALTER timeseries root.turbine.d1.s1 ADD TAGS tag3=v3, tag4=v4
 ```
 ALTER timeseries root.turbine.d1.s1 ADD ATTRIBUTES attr3=v3, attr4=v4
 ```
+* 更新插入标签和属性
+> 如果该标签或属性原来不存在,则插入,否则,用新值更新原来的旧值
+```
+ALTER timeseries root.turbine.d1.s1 UPSERT TAGS(tag2=newV2, tag3=v3) ATTRIBUTES(attr3=v3, attr4=v4)
+```
 
 ## 查看时间序列
 
diff --git a/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md b/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md
index e58ba19..e9ab162 100644
--- a/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md	
+++ b/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md	
@@ -103,12 +103,20 @@ alterClause
     | DROP ID (COMMA ID)*
     | ADD TAGS property (COMMA property)*
     | ADD ATTRIBUTES property (COMMA property)*
+    | UPSERT tagClause attributeClause
+    ;
+attributeClause
+    : (ATTRIBUTES LR_BRACKET property (COMMA property)* RR_BRACKET)?
+    ;
+tagClause
+    : (TAGS LR_BRACKET property (COMMA property)* RR_BRACKET)?
     ;
 Eg: ALTER timeseries root.turbine.d1.s1 RENAME tag1 TO newTag1
 Eg: ALTER timeseries root.turbine.d1.s1 SET tag1=newV1, attr1=newV1
 Eg: ALTER timeseries root.turbine.d1.s1 DROP tag1, tag2
 Eg: ALTER timeseries root.turbine.d1.s1 ADD TAGS tag3=v3, tag4=v4
 Eg: ALTER timeseries root.turbine.d1.s1 ADD ATTRIBUTES attr3=v3, attr4=v4
+EG: ALTER timeseries root.turbine.d1.s1 UPSERT TAGS(tag2=newV2, tag3=v3) ATTRIBUTES(attr3=v3, attr4=v4)
 ```
 
 * 显示所有时间序列语句
diff --git a/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4 b/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4
index be8edf1..c181d38 100644
--- a/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4
+++ b/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4
@@ -127,6 +127,7 @@ alterClause
     | DROP ID (COMMA ID)*
     | ADD TAGS property (COMMA property)*
     | ADD ATTRIBUTES property (COMMA property)*
+    | UPSERT tagClause attributeClause
     ;
 
 attributeClauses
@@ -608,6 +609,10 @@ ADD
     : A D D
     ;
 
+UPSERT
+    : U P S E R T
+    ;
+
 VALUES
     : V A L U E S
     ;
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
index a4267dd..fd4325f 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
@@ -231,10 +231,16 @@ public class MManager {
           tagMap = tagLogFile.readTag(config.getTagAttributeTotalSize(), offset);
         }
 
-        CreateTimeSeriesPlan plan = new CreateTimeSeriesPlan(new Path(args[1]),
-            TSDataType.deserialize(Short.parseShort(args[2])),
-            TSEncoding.deserialize(Short.parseShort(args[3])),
-            CompressionType.deserialize(Short.parseShort(args[4])), props, tagMap, null, alias);
+        CreateTimeSeriesPlan plan =
+            new CreateTimeSeriesPlan(
+                new Path(args[1]),
+                TSDataType.deserialize(Short.parseShort(args[2])),
+                TSEncoding.deserialize(Short.parseShort(args[3])),
+                CompressionType.deserialize(Short.parseShort(args[4])),
+                props,
+                tagMap,
+                null,
+                alias);
 
         createTimeseries(plan, offset);
         break;
@@ -289,8 +295,14 @@ public class MManager {
       }
 
       // create time series in MTree
-      LeafMNode leafMNode = mtree.createTimeseries(path, plan.getDataType(), plan.getEncoding(),
-          plan.getCompressor(), plan.getProps(), plan.getAlias());
+      LeafMNode leafMNode =
+          mtree.createTimeseries(
+              path,
+              plan.getDataType(),
+              plan.getEncoding(),
+              plan.getCompressor(),
+              plan.getProps(),
+              plan.getAlias());
       try {
         // check memory
         IoTDBConfigDynamicAdapter.getInstance().addOrDeleteTimeSeries(1);
@@ -303,7 +315,8 @@ public class MManager {
       if (plan.getTags() != null) {
         // tag key, tag value
         for (Entry<String, String> entry : plan.getTags().entrySet()) {
-          tagIndex.computeIfAbsent(entry.getKey(), k -> new HashMap<>())
+          tagIndex
+              .computeIfAbsent(entry.getKey(), k -> new HashMap<>())
               .computeIfAbsent(entry.getValue(), v -> new HashSet<>())
               .add(leafMNode);
         }
@@ -363,8 +376,8 @@ public class MManager {
    *
    * @param prefixPath path to be deleted, could be root or a prefix path or a full path
    * @return 1. The Set contains StorageGroups that contain no more timeseries after this deletion
-   *          files of such StorageGroups should be deleted to reclaim disk space.
-   *         2. The String is the deletion failed Timeseries
+   *     files of such StorageGroups should be deleted to reclaim disk space. 2. The String is the
+   *     deletion failed Timeseries
    */
   public Pair<Set<String>, String> deleteTimeseries(String prefixPath) throws MetadataException {
     lock.writeLock().lock();
@@ -717,10 +730,15 @@ public class MManager {
                 tagLogFile.read(config.getTagAttributeTotalSize(), leaf.getOffset());
             pair.left.putAll(pair.right);
             MeasurementSchema measurementSchema = leaf.getSchema();
-            res.add(new ShowTimeSeriesResult(leaf.getFullPath(), leaf.getAlias(),
-                getStorageGroupName(leaf.getFullPath()), measurementSchema.getType().toString(),
-                measurementSchema.getEncodingType().toString(),
-                measurementSchema.getCompressor().toString(), pair.left));
+            res.add(
+                new ShowTimeSeriesResult(
+                    leaf.getFullPath(),
+                    leaf.getAlias(),
+                    getStorageGroupName(leaf.getFullPath()),
+                    measurementSchema.getType().toString(),
+                    measurementSchema.getEncodingType().toString(),
+                    measurementSchema.getCompressor().toString(),
+                    pair.left));
             if (limit != 0 || offset != 0) {
               count++;
             }
@@ -766,15 +784,29 @@ public class MManager {
         try {
           if (tagFileOffset < 0) {
             // no tags/attributes
-            res.add(new ShowTimeSeriesResult(ansString[0], ansString[1], ansString[2], ansString[3],
-                ansString[4], ansString[5], Collections.emptyMap()));
+            res.add(
+                new ShowTimeSeriesResult(
+                    ansString[0],
+                    ansString[1],
+                    ansString[2],
+                    ansString[3],
+                    ansString[4],
+                    ansString[5],
+                    Collections.emptyMap()));
           } else {
             // has tags/attributes
             Pair<Map<String, String>, Map<String, String>> pair =
                 tagLogFile.read(config.getTagAttributeTotalSize(), tagFileOffset);
             pair.left.putAll(pair.right);
-            res.add(new ShowTimeSeriesResult(ansString[0], ansString[1], ansString[2], ansString[3],
-                ansString[4], ansString[5], pair.left));
+            res.add(
+                new ShowTimeSeriesResult(
+                    ansString[0],
+                    ansString[1],
+                    ansString[2],
+                    ansString[3],
+                    ansString[4],
+                    ansString[5],
+                    pair.left));
           }
         } catch (IOException e) {
           throw new MetadataException(
@@ -855,12 +887,12 @@ public class MManager {
   /**
    * get device node, if the storage group is not set, create it when autoCreateSchema is true
    *
-   * !!!!!!Attention!!!!!
-   * must call the return node's readUnlock() if you call this method.
+   * <p>!!!!!!Attention!!!!! must call the return node's readUnlock() if you call this method.
+   *
    * @param path path
    */
-  public MNode getDeviceNodeWithAutoCreateAndReadLock(String path, boolean autoCreateSchema,
-      int sgLevel) throws MetadataException {
+  public MNode getDeviceNodeWithAutoCreateAndReadLock(
+      String path, boolean autoCreateSchema, int sgLevel) throws MetadataException {
     lock.readLock().lock();
     MNode node = null;
     boolean shouldSetStorageGroup;
@@ -905,13 +937,10 @@ public class MManager {
     }
   }
 
-  /**
-   * !!!!!!Attention!!!!!
-   * must call the return node's readUnlock() if you call this method.
-   */
+  /** !!!!!!Attention!!!!! must call the return node's readUnlock() if you call this method. */
   public MNode getDeviceNodeWithAutoCreateAndReadLock(String path) throws MetadataException {
-    return getDeviceNodeWithAutoCreateAndReadLock(path, config.isAutoCreateSchemaEnabled(),
-        config.getDefaultStorageGroupLevel());
+    return getDeviceNodeWithAutoCreateAndReadLock(
+        path, config.isAutoCreateSchemaEnabled(), config.getDefaultStorageGroupLevel());
   }
 
   /** Get metadata in string */
@@ -947,6 +976,7 @@ public class MManager {
 
   /**
    * change or set the new offset of a timeseries
+   *
    * @param path timeseries
    * @param offset offset in the tag file
    */
@@ -960,7 +990,71 @@ public class MManager {
   }
 
   /**
+   * upsert tags and attributes key-value for the timeseries if the key has existed, just use the
+   * new value to update it.
+   *
+   * @param tagsMap newly added tags map
+   * @param attributesMap newly added attributes map
+   * @param fullPath timeseries
+   */
+  public void upsertTagsAndAttributes(
+      Map<String, String> tagsMap, Map<String, String> attributesMap, String fullPath)
+      throws MetadataException, IOException {
+    lock.writeLock().lock();
+    try {
+      MNode mNode = mtree.getNodeByPath(fullPath);
+      if (!(mNode instanceof LeafMNode)) {
+        throw new PathNotExistException(fullPath);
+      }
+      LeafMNode leafMNode = (LeafMNode) mNode;
+      // no tag or attribute, we need to add a new record in log
+      if (leafMNode.getOffset() < 0) {
+        long offset = tagLogFile.write(tagsMap, attributesMap);
+        logWriter.changeOffset(fullPath, offset);
+        leafMNode.setOffset(offset);
+        return;
+      }
+
+      Pair<Map<String, String>, Map<String, String>> pair =
+          tagLogFile.read(config.getTagAttributeTotalSize(), leafMNode.getOffset());
+
+      for (Entry<String, String> entry : tagsMap.entrySet()) {
+        String key = entry.getKey();
+        String value = entry.getValue();
+        String beforeValue = pair.left.get(key);
+        pair.left.put(key, value);
+        // if the key has existed and the value is not equal to the new one
+        // we should remove before key-value from inverted index map
+        if (beforeValue != null && !beforeValue.equals(value)) {
+          tagIndex.get(key).get(beforeValue).remove(leafMNode);
+          if (tagIndex.get(key).get(beforeValue).isEmpty()) {
+            tagIndex.get(key).remove(beforeValue);
+          }
+        }
+
+        // if the key doesn't exist or the value is not equal to the new one
+        // we should add a new key-value to inverted index map
+        if (beforeValue == null || !beforeValue.equals(value)) {
+          tagIndex
+              .computeIfAbsent(key, k -> new HashMap<>())
+              .computeIfAbsent(value, v -> new HashSet<>())
+              .add(leafMNode);
+        }
+      }
+      pair.left.putAll(tagsMap);
+      pair.right.putAll(attributesMap);
+
+      // persist the change to disk
+      tagLogFile.write(pair.left, pair.right, leafMNode.getOffset());
+
+    } finally {
+      lock.writeLock().unlock();
+    }
+  }
+
+  /**
    * add new attributes key-value for the timeseries
+   *
    * @param attributesMap newly added attributes map
    * @param fullPath timeseries
    */
@@ -1003,6 +1097,7 @@ public class MManager {
 
   /**
    * add new tags key-value for the timeseries
+   *
    * @param tagsMap newly added tags map
    * @param fullPath timeseries
    */
@@ -1054,6 +1149,7 @@ public class MManager {
 
   /**
    * drop tags or attributes of the timeseries
+   *
    * @param keySet tags key or attributes key
    * @param fullPath timeseries path
    */
@@ -1106,6 +1202,7 @@ public class MManager {
 
   /**
    * set/change the values of tags or attributes
+   *
    * @param alterMap the new tags or attributes key-value
    * @param fullPath timeseries
    */
@@ -1167,6 +1264,7 @@ public class MManager {
 
   /**
    * rename the tag or attribute's key of the timeseries
+   *
    * @param oldKey old key of tag or attribute
    * @param newKey new key of tag or attribute
    * @param fullPath timeseries
@@ -1182,8 +1280,7 @@ public class MManager {
       LeafMNode leafMNode = (LeafMNode) mNode;
       if (leafMNode.getOffset() < 0) {
         throw new MetadataException(
-            String.format(
-                "TimeSeries [%s] does not have [%s] tag/attribute.", fullPath, oldKey));
+            String.format("TimeSeries [%s] does not have [%s] tag/attribute.", fullPath, oldKey));
       }
       // tags, attributes
       Pair<Map<String, String>, Map<String, String>> pair =
@@ -1215,8 +1312,7 @@ public class MManager {
         tagLogFile.write(pair.left, pair.right, leafMNode.getOffset());
       } else {
         throw new MetadataException(
-            String.format(
-                "TimeSeries [%s] does not have tag/attribute [%s].", fullPath, oldKey));
+            String.format("TimeSeries [%s] does not have tag/attribute [%s].", fullPath, oldKey));
       }
     } finally {
       lock.writeLock().unlock();
@@ -1257,8 +1353,12 @@ public class MManager {
       MNode node = nodeDeque.removeFirst();
       if (node instanceof LeafMNode) {
         MeasurementSchema nodeSchema = ((LeafMNode) node).getSchema();
-        timeseriesSchemas.add(new MeasurementSchema(node.getFullPath(), nodeSchema.getType(),
-            nodeSchema.getEncodingType(), nodeSchema.getCompressor()));
+        timeseriesSchemas.add(
+            new MeasurementSchema(
+                node.getFullPath(),
+                nodeSchema.getType(),
+                nodeSchema.getEncodingType(),
+                nodeSchema.getCompressor()));
       } else if (!node.getChildren().isEmpty()) {
         nodeDeque.addAll(node.getChildren().values());
       }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
index 5615f38..92f363e 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
@@ -18,6 +18,38 @@
  */
 package org.apache.iotdb.db.qp.executor;
 
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_CHILD_PATHS;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_COLUMN;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_COUNT;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_DEVICES;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_ITEM;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_PARAMETER;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_PRIVILEGE;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_ROLE;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_STORAGE_GROUP;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_TIMESERIES;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_TIMESERIES_ALIAS;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_TIMESERIES_DATATYPE;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_TIMESERIES_ENCODING;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_TTL;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_USER;
+import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_VALUE;
+import static org.apache.iotdb.tsfile.common.constant.TsFileConstant.PATH_SEPARATOR;
+import static org.apache.iotdb.tsfile.common.constant.TsFileConstant.TSFILE_SUFFIX;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import org.apache.iotdb.db.auth.AuthException;
 import org.apache.iotdb.db.auth.authorizer.IAuthorizer;
 import org.apache.iotdb.db.auth.authorizer.LocalFileAuthorizer;
@@ -46,8 +78,33 @@ import org.apache.iotdb.db.metadata.mnode.StorageGroupMNode;
 import org.apache.iotdb.db.qp.logical.sys.AuthorOperator;
 import org.apache.iotdb.db.qp.logical.sys.AuthorOperator.AuthorType;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
-import org.apache.iotdb.db.qp.physical.crud.*;
-import org.apache.iotdb.db.qp.physical.sys.*;
+import org.apache.iotdb.db.qp.physical.crud.AggregationPlan;
+import org.apache.iotdb.db.qp.physical.crud.AlignByDevicePlan;
+import org.apache.iotdb.db.qp.physical.crud.DeletePlan;
+import org.apache.iotdb.db.qp.physical.crud.FillQueryPlan;
+import org.apache.iotdb.db.qp.physical.crud.GroupByFillPlan;
+import org.apache.iotdb.db.qp.physical.crud.GroupByPlan;
+import org.apache.iotdb.db.qp.physical.crud.InsertPlan;
+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.RawDataQueryPlan;
+import org.apache.iotdb.db.qp.physical.crud.UpdatePlan;
+import org.apache.iotdb.db.qp.physical.sys.AlterTimeSeriesPlan;
+import org.apache.iotdb.db.qp.physical.sys.AuthorPlan;
+import org.apache.iotdb.db.qp.physical.sys.CountPlan;
+import org.apache.iotdb.db.qp.physical.sys.CreateTimeSeriesPlan;
+import org.apache.iotdb.db.qp.physical.sys.DataAuthPlan;
+import org.apache.iotdb.db.qp.physical.sys.DeleteStorageGroupPlan;
+import org.apache.iotdb.db.qp.physical.sys.DeleteTimeSeriesPlan;
+import org.apache.iotdb.db.qp.physical.sys.OperateFilePlan;
+import org.apache.iotdb.db.qp.physical.sys.SetStorageGroupPlan;
+import org.apache.iotdb.db.qp.physical.sys.SetTTLPlan;
+import org.apache.iotdb.db.qp.physical.sys.ShowChildPathsPlan;
+import org.apache.iotdb.db.qp.physical.sys.ShowDevicesPlan;
+import org.apache.iotdb.db.qp.physical.sys.ShowPlan;
+import org.apache.iotdb.db.qp.physical.sys.ShowTTLPlan;
+import org.apache.iotdb.db.qp.physical.sys.ShowTimeSeriesPlan;
 import org.apache.iotdb.db.query.context.QueryContext;
 import org.apache.iotdb.db.query.dataset.AlignByDeviceDataSet;
 import org.apache.iotdb.db.query.dataset.ListDataSet;
@@ -80,14 +137,6 @@ import org.apache.iotdb.tsfile.write.writer.RestorableTsFileIOWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.*;
-
-import static org.apache.iotdb.db.conf.IoTDBConstant.*;
-import static org.apache.iotdb.tsfile.common.constant.TsFileConstant.PATH_SEPARATOR;
-import static org.apache.iotdb.tsfile.common.constant.TsFileConstant.TSFILE_SUFFIX;
-
 public class PlanExecutor implements IPlanExecutor {
 
   private static final Logger logger = LoggerFactory.getLogger(PlanExecutor.class);
@@ -672,15 +721,21 @@ public class PlanExecutor implements IPlanExecutor {
             registeredSeries.add(series);
             MeasurementSchema schema = knownSchemas.get(series);
             if (schema == null) {
-              throw new MetadataException(String.format("Can not get the schema of measurement [%s]",
+              throw new MetadataException(
+                  String.format(
+                      "Can not get the schema of measurement [%s]",
                       chunkMetadata.getMeasurementUid()));
             }
             if (!node.hasChild(chunkMetadata.getMeasurementUid())) {
-              mManager.createTimeseries(series.getFullPath(), schema.getType(),
-                      schema.getEncodingType(), schema.getCompressor(), Collections.emptyMap());
+              mManager.createTimeseries(
+                  series.getFullPath(),
+                  schema.getType(),
+                  schema.getEncodingType(),
+                  schema.getCompressor(),
+                  Collections.emptyMap());
             } else if (node.getChild(chunkMetadata.getMeasurementUid()) instanceof InternalMNode) {
               throw new QueryProcessException(
-                      String.format("Current Path is not leaf node. %s", series));
+                  String.format("Current Path is not leaf node. %s", series));
             }
           }
         }
@@ -1009,6 +1064,12 @@ public class PlanExecutor implements IPlanExecutor {
         case ADD_ATTRIBUTES:
           mManager.addAttributes(alterMap, path.getFullPath());
           break;
+        case UPSERT:
+          mManager.upsertTagsAndAttributes(
+              alterTimeSeriesPlan.getTagsMap(),
+              alterTimeSeriesPlan.getAttributesMap(),
+              path.getFullPath());
+          break;
       }
     } catch (MetadataException e) {
       throw new QueryProcessException(e);
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/AlterTimeSeriesOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/AlterTimeSeriesOperator.java
index 41d15ac..a72c76a 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/AlterTimeSeriesOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/AlterTimeSeriesOperator.java
@@ -30,8 +30,17 @@ public class AlterTimeSeriesOperator extends RootOperator {
 
   private AlterType alterType;
 
+  // used when the alterType is RENAME, SET, DROP, ADD_TAGS, ADD_ATTRIBUTES
+  // when the alterType is RENAME, alterMap has only one entry, key is the beforeName, value is the
+  // currentName
+  // when the alterType is DROP, only the keySet of alterMap is useful, it contains all the key
+  // names needed to be removed
   private Map<String, String> alterMap;
 
+  // used when the alterType is UPSERT
+  private Map<String, String> tagsMap;
+  private Map<String, String> attributesMap;
+
   public AlterTimeSeriesOperator(int tokenIntType) {
     super(tokenIntType);
     operatorType = OperatorType.ALTER_TIMESERIES;
@@ -61,7 +70,28 @@ public class AlterTimeSeriesOperator extends RootOperator {
     this.alterMap = alterMap;
   }
 
+  public Map<String, String> getTagsMap() {
+    return tagsMap;
+  }
+
+  public void setTagsMap(Map<String, String> tagsMap) {
+    this.tagsMap = tagsMap;
+  }
+
+  public Map<String, String> getAttributesMap() {
+    return attributesMap;
+  }
+
+  public void setAttributesMap(Map<String, String> attributesMap) {
+    this.attributesMap = attributesMap;
+  }
+
   public enum AlterType {
-    RENAME, SET, DROP, ADD_TAGS, ADD_ATTRIBUTES
+    RENAME,
+    SET,
+    DROP,
+    ADD_TAGS,
+    ADD_ATTRIBUTES,
+    UPSERT
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/AlterTimeSeriesPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/AlterTimeSeriesPlan.java
index 7967c96..34763e3 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/AlterTimeSeriesPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/AlterTimeSeriesPlan.java
@@ -19,28 +19,44 @@
 
 package org.apache.iotdb.db.qp.physical.sys;
 
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import org.apache.iotdb.db.qp.logical.Operator;
 import org.apache.iotdb.db.qp.logical.sys.AlterTimeSeriesOperator;
+import org.apache.iotdb.db.qp.logical.sys.AlterTimeSeriesOperator.AlterType;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
 import org.apache.iotdb.tsfile.read.common.Path;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
 public class AlterTimeSeriesPlan extends PhysicalPlan {
 
-  private Path path;
+  private final Path path;
+
+  private final AlterTimeSeriesOperator.AlterType alterType;
 
-  private AlterTimeSeriesOperator.AlterType alterType;
+  // used when the alterType is RENAME, SET, DROP, ADD_TAGS, ADD_ATTRIBUTES
+  // when the alterType is RENAME, alterMap has only one entry, key is the beforeName, value is the
+  // currentName
+  // when the alterType is DROP, only the keySet of alterMap is useful, it contains all the key
+  // names needed to be removed
+  private final Map<String, String> alterMap;
 
-  private Map<String, String> alterMap;
+  // used when the alterType is UPSERT
+  private final Map<String, String> tagsMap;
+  private final Map<String, String> attributesMap;
 
-  public AlterTimeSeriesPlan(Path path, AlterTimeSeriesOperator.AlterType alterType, Map<String, String> alterMap) {
+  public AlterTimeSeriesPlan(
+      Path path,
+      AlterType alterType,
+      Map<String, String> alterMap,
+      Map<String, String> tagsMap,
+      Map<String, String> attributesMap) {
     super(false, Operator.OperatorType.ALTER_TIMESERIES);
     this.path = path;
     this.alterType = alterType;
     this.alterMap = alterMap;
+    this.tagsMap = tagsMap;
+    this.attributesMap = attributesMap;
   }
 
   public Path getPath() {
@@ -55,6 +71,14 @@ public class AlterTimeSeriesPlan extends PhysicalPlan {
     return alterMap;
   }
 
+  public Map<String, String> getTagsMap() {
+    return tagsMap;
+  }
+
+  public Map<String, String> getAttributesMap() {
+    return attributesMap;
+  }
+
   @Override
   public List<Path> getPaths() {
     return Collections.singletonList(path);
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
index a9b069b..561ffe0 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.db.qp.constant.SQLConstant;
 import org.apache.iotdb.db.qp.logical.RootOperator;
 import org.apache.iotdb.db.qp.logical.crud.*;
 import org.apache.iotdb.db.qp.logical.sys.*;
+import org.apache.iotdb.db.qp.logical.sys.AlterTimeSeriesOperator.AlterType;
 import org.apache.iotdb.db.qp.logical.sys.AuthorOperator.AuthorType;
 import org.apache.iotdb.db.qp.strategy.SqlBaseParser.*;
 import org.apache.iotdb.db.query.fill.IFill;
@@ -208,26 +209,29 @@ public class LogicalGenerator extends SqlBaseBaseListener {
     Map<String, String> alterMap = new HashMap<>();
     // rename
     if (ctx.RENAME() != null) {
-      alterTimeSeriesOperator.setAlterType(AlterTimeSeriesOperator.AlterType.RENAME);
+      alterTimeSeriesOperator.setAlterType(AlterType.RENAME);
       alterMap.put(ctx.beforeName.getText(), ctx.currentName.getText());
     } else if (ctx.SET() != null) {
       // set
-      alterTimeSeriesOperator.setAlterType(AlterTimeSeriesOperator.AlterType.SET);
+      alterTimeSeriesOperator.setAlterType(AlterType.SET);
       setMap(ctx, alterMap);
     } else if (ctx.DROP() != null) {
       // drop
-      alterTimeSeriesOperator.setAlterType(AlterTimeSeriesOperator.AlterType.DROP);
+      alterTimeSeriesOperator.setAlterType(AlterType.DROP);
       for (TerminalNode dropId : ctx.ID()) {
         alterMap.put(dropId.getText(), null);
       }
     } else if (ctx.TAGS() != null) {
       // add tag
-      alterTimeSeriesOperator.setAlterType(AlterTimeSeriesOperator.AlterType.ADD_TAGS);
+      alterTimeSeriesOperator.setAlterType(AlterType.ADD_TAGS);
       setMap(ctx, alterMap);
-    } else {
+    } else if (ctx.ATTRIBUTES() != null){
       // add attribute
-      alterTimeSeriesOperator.setAlterType(AlterTimeSeriesOperator.AlterType.ADD_ATTRIBUTES);
+      alterTimeSeriesOperator.setAlterType(AlterType.ADD_ATTRIBUTES);
       setMap(ctx, alterMap);
+    } else {
+      // upsert
+      alterTimeSeriesOperator.setAlterType(AlterType.UPSERT);
     }
     alterTimeSeriesOperator.setAlterMap(alterMap);
     initializedOperator = alterTimeSeriesOperator;
@@ -930,32 +934,32 @@ public class LogicalGenerator extends SqlBaseBaseListener {
   @Override
   public void enterAttributeClause(AttributeClauseContext ctx) {
     super.enterAttributeClause(ctx);
-    List<PropertyContext> attributesList = ctx.property();
-    String value;
-    Map<String, String> attributes = new HashMap<>(attributesList.size());
-    if (ctx.property(0) != null) {
-      for (PropertyContext property : attributesList) {
-        if(property.propertyValue().STRING_LITERAL() != null) {
-          value = removeStringQuote(property.propertyValue().getText());
-        } else {
-          value = property.propertyValue().getText();
-
-        }
-        attributes.put(property.ID().getText(), value);
-      }
+    Map<String, String> attributes = extractMap(ctx.property(), ctx.property(0));
+    if (createTimeSeriesOperator != null) {
+      createTimeSeriesOperator.setAttributes(attributes);
+    } else if (alterTimeSeriesOperator != null) {
+      alterTimeSeriesOperator.setAttributesMap(attributes);
     }
-    createTimeSeriesOperator.setAttributes(attributes);
   }
 
   @Override
   public void enterTagClause(TagClauseContext ctx) {
     super.enterTagClause(ctx);
-    List<PropertyContext> tagsList = ctx.property();
+    Map<String, String> tags = extractMap(ctx.property(), ctx.property(0));
+    if (createTimeSeriesOperator != null) {
+      createTimeSeriesOperator.setTags(tags);
+    } else if (alterTimeSeriesOperator != null) {
+      alterTimeSeriesOperator.setTagsMap(tags);
+    }
+  }
+
+  private Map<String, String> extractMap(List<PropertyContext> property2,
+      PropertyContext property3) {
     String value;
-    Map<String, String> tags = new HashMap<>(tagsList.size());
-    if (ctx.property(0) != null) {
-      for (PropertyContext property : tagsList) {
-        if(property.propertyValue().STRING_LITERAL() != null) {
+    Map<String, String> tags = new HashMap<>(property2.size());
+    if (property3 != null) {
+      for (PropertyContext property : property2) {
+        if (property.propertyValue().STRING_LITERAL() != null) {
           value = removeStringQuote(property.propertyValue().getText());
         } else {
           value = property.propertyValue().getText();
@@ -963,7 +967,7 @@ public class LogicalGenerator extends SqlBaseBaseListener {
         tags.put(property.ID().getText(), value);
       }
     }
-    createTimeSeriesOperator.setTags(tags);
+    return tags;
   }
 
   @Override
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
index 8f27c54..72e66b4 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
@@ -107,7 +107,9 @@ public class PhysicalGenerator {
         return new AlterTimeSeriesPlan(
             alterTimeSeriesOperator.getPath(),
             alterTimeSeriesOperator.getAlterType(),
-            alterTimeSeriesOperator.getAlterMap());
+            alterTimeSeriesOperator.getAlterMap(),
+            alterTimeSeriesOperator.getTagsMap(),
+            alterTimeSeriesOperator.getAttributesMap());
       case DELETE:
         DeleteDataOperator delete = (DeleteDataOperator) operator;
         paths = delete.getSelectedPaths();
diff --git a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBTagAlterIT.java b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBTagAlterIT.java
index 4ec5888..ff77f49 100644
--- a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBTagAlterIT.java
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBTagAlterIT.java
@@ -18,18 +18,20 @@
  */
 package org.apache.iotdb.db.integration;
 
-import org.apache.iotdb.db.utils.EnvironmentUtils;
-import org.apache.iotdb.jdbc.Config;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.Statement;
-
-import static org.junit.Assert.*;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 public class IoTDBTagAlterIT {
 
@@ -362,4 +364,92 @@ public class IoTDBTagAlterIT {
     }
   }
 
+  @Test
+  public void upsertTest() throws ClassNotFoundException {
+    String[] ret = {"root.turbine.d1.s1,temperature,root.turbine,FLOAT,RLE,SNAPPY,v1,v2,v1,v2"};
+    String[] ret2 = {"root.turbine.d1.s1,temperature,root.turbine,FLOAT,RLE,SNAPPY,v1,v2,v1,newV2,v3"};
+    String[] ret3 = {"root.turbine.d1.s1,temperature,root.turbine,FLOAT,RLE,SNAPPY,newA1,v2,v3,newV1,newV2,newV3"};
+
+
+    String sql = "create timeseries root.turbine.d1.s1(temperature) with datatype=FLOAT, encoding=RLE, compression=SNAPPY " +
+        "tags(tag1=v1, tag2=v2) attributes(attr1=v1, attr2=v2)";
+    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()) {
+      statement.execute(sql);
+      boolean hasResult = statement.execute("show timeseries");
+      assertTrue(hasResult);
+      ResultSet resultSet = statement.getResultSet();
+      int count = 0;
+      while (resultSet.next()) {
+        String ans = resultSet.getString("timeseries")
+            + "," + resultSet.getString("alias")
+            + "," + resultSet.getString("storage group")
+            + "," + resultSet.getString("dataType")
+            + "," + resultSet.getString("encoding")
+            + "," + resultSet.getString("compression")
+            + "," + resultSet.getString("attr1")
+            + "," + resultSet.getString("attr2")
+            + "," + resultSet.getString("tag1")
+            + "," + resultSet.getString("tag2");
+        assertEquals(ret[count], ans);
+        count++;
+      }
+      assertEquals(ret.length, count);
+
+      statement.execute("ALTER timeseries root.turbine.d1.s1 UPSERT TAGS(tag3=v3, tag2=newV2)");
+      hasResult = statement.execute("show timeseries where tag3=v3");
+      assertTrue(hasResult);
+      resultSet = statement.getResultSet();
+      count = 0;
+      while (resultSet.next()) {
+        String ans = resultSet.getString("timeseries")
+            + "," + resultSet.getString("alias")
+            + "," + resultSet.getString("storage group")
+            + "," + resultSet.getString("dataType")
+            + "," + resultSet.getString("encoding")
+            + "," + resultSet.getString("compression")
+            + "," + resultSet.getString("attr1")
+            + "," + resultSet.getString("attr2")
+            + "," + resultSet.getString("tag1")
+            + "," + resultSet.getString("tag2")
+            + "," + resultSet.getString("tag3");
+        assertEquals(ret2[count], ans);
+        count++;
+      }
+      assertEquals(ret2.length, count);
+
+      statement.execute("ALTER timeseries root.turbine.d1.s1 UPSERT TAGS(tag1=newV1, tag3=newV3) ATTRIBUTES(attr1=newA1, attr3=v3)");
+      hasResult = statement.execute("show timeseries where tag3=newV3");
+      assertTrue(hasResult);
+      resultSet = statement.getResultSet();
+      count = 0;
+      while (resultSet.next()) {
+        String ans = resultSet.getString("timeseries")
+            + "," + resultSet.getString("alias")
+            + "," + resultSet.getString("storage group")
+            + "," + resultSet.getString("dataType")
+            + "," + resultSet.getString("encoding")
+            + "," + resultSet.getString("compression")
+            + "," + resultSet.getString("attr1")
+            + "," + resultSet.getString("attr2")
+            + "," + resultSet.getString("attr3")
+            + "," + resultSet.getString("tag1")
+            + "," + resultSet.getString("tag2")
+            + "," + resultSet.getString("tag3");
+        assertEquals(ret3[count], ans);
+        count++;
+      }
+      assertEquals(ret3.length, count);
+
+      statement.execute("show timeseries where tag3=v3");
+      resultSet = statement.getResultSet();
+      assertFalse(resultSet.next());
+
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
 }