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

jiangtian pushed a commit to branch new_java_interfaces
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/new_java_interfaces by this 
push:
     new bd1efe38 add tag filter eq
bd1efe38 is described below

commit bd1efe38616f01c2eaf13d09bff11721b87b3c9a
Author: Tian Jiang <[email protected]>
AuthorDate: Thu Apr 24 14:22:22 2025 +0800

    add tag filter eq
---
 .../src/main/codegen/dataModel/AllFilter.tdd       |   5 +
 .../codegen/templates/FilterOperatorsTemplate.ftl  | 168 +++++++++++----------
 .../src/main/codegen/templates/FilterTemplate.ftl  |   7 +
 .../tsfile/read/controller/DeviceMetaIterator.java |  12 +-
 .../tsfile/read/controller/IMetadataQuerier.java   |   4 +-
 .../read/controller/MetadataQuerierByFileImpl.java |   6 +-
 .../apache/tsfile/read/filter/basic/Filter.java    |   2 +
 .../tsfile/read/filter/basic/TimeFilter.java       |   5 +
 .../tsfile/read/filter/basic/ValueFilter.java      |   7 +-
 .../factory/TagFilterBuilder.java}                 |  39 +++--
 .../apache/tsfile/read/filter/operator/And.java    |   5 +
 .../apache/tsfile/read/filter/operator/Not.java    |   5 +
 .../org/apache/tsfile/read/filter/operator/Or.java |   5 +
 .../tsfile/read/query/dataset/ResultSet.java       |   7 +-
 .../tsfile/read/query/dataset/TableResultSet.java  |  96 +++++++++++-
 .../tsfile/read/query/dataset/TreeResultSet.java   |   7 +
 .../read/query/executor/TableQueryExecutor.java    |   7 +-
 .../query/executor/task/DeviceTaskIterator.java    |   6 +-
 .../tsfile/read/v4/DeviceTableModelReader.java     |  12 +-
 .../org/apache/tsfile/read/v4/ITsFileReader.java   |   6 +
 .../org/apache/tsfile/utils/ReadWriteIOUtils.java  |  23 +++
 .../apache/tsfile/read/query/ResultSetTest.java    | 132 ++++++++++++++++
 22 files changed, 444 insertions(+), 122 deletions(-)

diff --git a/java/tsfile/src/main/codegen/dataModel/AllFilter.tdd 
b/java/tsfile/src/main/codegen/dataModel/AllFilter.tdd
index cb5815bc..d8dcd64e 100644
--- a/java/tsfile/src/main/codegen/dataModel/AllFilter.tdd
+++ b/java/tsfile/src/main/codegen/dataModel/AllFilter.tdd
@@ -52,6 +52,11 @@
       "dataType": "Binary",
       "javaBoxName": "String",
       "classSerializeName": "STRING"
+    },
+    {
+      "dataType": "String",
+      "javaBoxName": "Tag",
+      "classSerializeName": "TAG"
     }
   ]
 }
\ No newline at end of file
diff --git a/java/tsfile/src/main/codegen/templates/FilterOperatorsTemplate.ftl 
b/java/tsfile/src/main/codegen/templates/FilterOperatorsTemplate.ftl
index 77d394d1..c4f1243b 100644
--- a/java/tsfile/src/main/codegen/templates/FilterOperatorsTemplate.ftl
+++ b/java/tsfile/src/main/codegen/templates/FilterOperatorsTemplate.ftl
@@ -2,6 +2,12 @@
 <#list filters as filter>
   <#assign className = "${filter.javaBoxName}FilterOperators">
   <#assign filterName = "${filter.javaBoxName}Filter">
+    <#if filter.javaBoxName == "Tag">
+        <#assign javaClassName = "String">
+    <#else>
+        <#assign javaClassName = "${filter.javaBoxName}">
+    </#if>
+
   <@pp.changeOutputFile 
name="/org/apache/tsfile/read/filter/operator/${className}.java" />
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -71,7 +77,7 @@ public final class ${className} {
 
     protected ValueColumnCompareFilter(int measurementIndex, 
${filter.dataType} constant) {
       super(measurementIndex);
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       this.constant = Objects.requireNonNull(constant, 
CONSTANT_CANNOT_BE_NULL_MSG);
       <#else>
       this.constant = constant;
@@ -81,7 +87,7 @@ public final class ${className} {
     @SuppressWarnings("unchecked")
     protected ValueColumnCompareFilter(ByteBuffer buffer) {
       super(buffer);
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       this.constant = 
Objects.requireNonNull(ReadWriteIOUtils.read${filter.dataType?cap_first}(buffer),
 CONSTANT_CANNOT_BE_NULL_MSG);
       <#else>
       this.constant = 
ReadWriteIOUtils.read${filter.dataType?cap_first}(buffer);
@@ -106,7 +112,7 @@ public final class ${className} {
         return false;
       }
       ValueColumnCompareFilter that = (ValueColumnCompareFilter) o;
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       return Objects.equals(constant,that.constant);
       <#else>
       return constant == that.constant;
@@ -142,7 +148,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(${filter.dataType} value) {
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       return constant.equals(value);
       <#else>
       return constant == value;
@@ -152,8 +158,8 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
-        <#if filter.javaBoxName == "String">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
+        <#if filter.javaBoxName == "String" || filter.javaBoxName == "Tag">
       if(statistics.isEmpty()){
         return false;
       }
@@ -167,15 +173,15 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return constant < (${filter.javaBoxName}) statistics.getMinValue()
-          || constant > (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant < (${javaClassName}) statistics.getMinValue()
+          || constant > (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -190,8 +196,8 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return constant == (${filter.javaBoxName}) statistics.getMinValue()
-          && constant == (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant == (${javaClassName}) statistics.getMinValue()
+          && constant == (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
@@ -223,7 +229,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(${filter.dataType} value) {
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       return !constant.equals(value);
       <#else>
       return constant != value;
@@ -233,7 +239,7 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -249,15 +255,15 @@ public final class ${className} {
         return false;
       }
       // drop if this is a column where min = max = value
-      return constant == (${filter.javaBoxName}) statistics.getMinValue()
-          && constant == (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant == (${javaClassName}) statistics.getMinValue()
+          && constant == (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -271,8 +277,8 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return constant < (${filter.javaBoxName}) statistics.getMinValue()
-          || constant > (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant < (${javaClassName}) statistics.getMinValue()
+          || constant > (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
@@ -307,7 +313,7 @@ public final class ${className} {
     public boolean valueSatisfy(${filter.dataType} value) {
       <#if filter.dataType == "boolean">
       return Boolean.compare(constant,value) > 0;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       return constant.compareTo(value) > 0;
       <#else>
       return constant > value;
@@ -317,7 +323,7 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -332,14 +338,14 @@ public final class ${className} {
         return false;
       }
       // drop if value <= min
-      return constant <= (${filter.javaBoxName}) statistics.getMinValue();
+      return constant <= (${javaClassName}) statistics.getMinValue();
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -352,7 +358,7 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return constant > (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant > (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
@@ -387,7 +393,7 @@ public final class ${className} {
     public boolean valueSatisfy(${filter.dataType} value) {
       <#if filter.dataType == "boolean">
       return Boolean.compare(constant,value) >= 0;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       return constant.compareTo(value) >= 0;
       <#else>
       return constant >= value;
@@ -397,7 +403,7 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -412,14 +418,14 @@ public final class ${className} {
         return false;
       }
       // drop if value < min
-      return constant < (${filter.javaBoxName}) statistics.getMinValue();
+      return constant < (${javaClassName}) statistics.getMinValue();
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -432,7 +438,7 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return constant >= (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant >= (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
@@ -467,7 +473,7 @@ public final class ${className} {
     public boolean valueSatisfy(${filter.dataType} value) {
       <#if filter.dataType == "boolean">
       return Boolean.compare(constant,value) < 0;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       return constant.compareTo(value) < 0;
       <#else>
       return constant < value;
@@ -477,7 +483,7 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -492,14 +498,14 @@ public final class ${className} {
         return false;
       }
       // drop if value >= max
-      return constant >= (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant >= (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -512,7 +518,7 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return constant < (${filter.javaBoxName}) statistics.getMinValue();
+      return constant < (${javaClassName}) statistics.getMinValue();
       </#if>
     }
 
@@ -547,7 +553,7 @@ public final class ${className} {
     public boolean valueSatisfy(${filter.dataType} value) {
       <#if filter.dataType == "boolean">
       return Boolean.compare(constant,value) <= 0;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       return constant.compareTo(value) <= 0;
       <#else>
       return constant <= value;
@@ -557,7 +563,7 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -572,14 +578,14 @@ public final class ${className} {
         return false;
       }
       // drop if value > max
-      return constant > (${filter.javaBoxName}) statistics.getMaxValue();
+      return constant > (${javaClassName}) statistics.getMaxValue();
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -592,7 +598,7 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return constant <= (${filter.javaBoxName}) statistics.getMinValue();
+      return constant <= (${javaClassName}) statistics.getMinValue();
       </#if>
     }
 
@@ -615,7 +621,7 @@ public final class ${className} {
 
     protected ValueColumnRangeFilter(int measurementIndex, ${filter.dataType} 
min, ${filter.dataType} max) {
       super(measurementIndex);
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       this.min = Objects.requireNonNull(min,"min cannot be null");
       this.max = Objects.requireNonNull(max,"max cannot be null");
       <#else>
@@ -627,7 +633,7 @@ public final class ${className} {
     @SuppressWarnings("unchecked")
     protected ValueColumnRangeFilter(ByteBuffer buffer) {
       super(buffer);
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       this.min = 
Objects.requireNonNull(ReadWriteIOUtils.read${filter.dataType?cap_first}(buffer),"min
 cannot be null");
       this.max = 
Objects.requireNonNull(ReadWriteIOUtils.read${filter.dataType?cap_first}(buffer),"max
 cannot be null");
       <#else>
@@ -655,7 +661,7 @@ public final class ${className} {
         return false;
       }
       ValueColumnRangeFilter that = (ValueColumnRangeFilter) o;
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       return min.equals(that.min) && max.equals(that.max);
       <#else>
       return min == that.min && max == that.max;
@@ -696,7 +702,7 @@ public final class ${className} {
       <#if filter.dataType == "boolean">
       return Boolean.compare(min,value) <= 0
           && Boolean.compare(max,value) >= 0;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       return min.compareTo(value) <= 0
           && max.compareTo(value) >= 0;
       <#else>
@@ -707,7 +713,7 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -722,15 +728,15 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return (${filter.javaBoxName}) statistics.getMaxValue() < min
-          || (${filter.javaBoxName}) statistics.getMinValue() > max;
+      return (${javaClassName}) statistics.getMaxValue() < min
+          || (${javaClassName}) statistics.getMinValue() > max;
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -744,8 +750,8 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return (${filter.javaBoxName}) statistics.getMinValue() >= min
-          && (${filter.javaBoxName}) statistics.getMaxValue() <= max;
+      return (${javaClassName}) statistics.getMinValue() >= min
+          && (${javaClassName}) statistics.getMaxValue() <= max;
       </#if>
     }
 
@@ -781,7 +787,7 @@ public final class ${className} {
       <#if filter.dataType == "boolean">
       return Boolean.compare(min,value) > 0
           || Boolean.compare(max,value) < 0;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       return min.compareTo(value) > 0 || max.compareTo(value) < 0;
       <#else>
       return min > value || max < value;
@@ -791,7 +797,7 @@ public final class ${className} {
     @Override
     @SuppressWarnings("unchecked")
     public boolean canSkip(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -806,15 +812,15 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return (${filter.javaBoxName}) statistics.getMinValue() >= min
-          && (${filter.javaBoxName}) statistics.getMaxValue() <= max;
+      return (${javaClassName}) statistics.getMinValue() >= min
+          && (${javaClassName}) statistics.getMaxValue() <= max;
       </#if>
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public boolean allSatisfy(Statistics<? extends Serializable> statistics) {
-      <#if filter.dataType == "boolean" || filter.dataType == "Binary">
+      <#if filter.dataType == "boolean" || filter.dataType == "Binary" || 
filter.dataType == "String">
         <#if filter.javaBoxName == "String">
       if(statistics.isEmpty()){
         return false;
@@ -828,8 +834,8 @@ public final class ${className} {
       if(statistics.isEmpty()){
         return false;
       }
-      return (${filter.javaBoxName}) statistics.getMinValue() > max
-          || (${filter.javaBoxName}) statistics.getMaxValue() < min;
+      return (${javaClassName}) statistics.getMinValue() > max
+          || (${javaClassName}) statistics.getMaxValue() < min;
       </#if>
     }
 
@@ -850,7 +856,7 @@ public final class ${className} {
     <#if filter.javaBoxName == "String">
     protected final Set<${filter.dataType}> candidates;
     <#else>
-    protected final Set<${filter.javaBoxName}> candidates;
+    protected final Set<${javaClassName}> candidates;
     </#if>
 
     protected final ${filter.dataType} candidatesMin;
@@ -859,7 +865,7 @@ public final class ${className} {
     <#if filter.javaBoxName == "String">
     protected ValueColumnSetFilter(int measurementIndex, 
Set<${filter.dataType}> candidates) {
     <#else>
-    protected ValueColumnSetFilter(int measurementIndex, 
Set<${filter.javaBoxName}> candidates) {
+    protected ValueColumnSetFilter(int measurementIndex, Set<${javaClassName}> 
candidates) {
     </#if>
       super(measurementIndex);
       this.candidates = candidates;
@@ -867,18 +873,18 @@ public final class ${className} {
       <#if filter.javaBoxName == "String">
       Set<${filter.dataType}> filteredSet = 
candidates.stream().filter(Objects::nonNull).collect(Collectors.toSet());
       <#else>
-      Set<${filter.javaBoxName}> filteredSet = 
candidates.stream().filter(Objects::nonNull).collect(Collectors.toSet());
+      Set<${javaClassName}> filteredSet = 
candidates.stream().filter(Objects::nonNull).collect(Collectors.toSet());
       </#if>
       <#if filter.dataType == "boolean">
       // BooleanStatistics is not available
       this.candidatesMin = false;
       this.candidatesMax = false;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       this.candidatesMin = !filteredSet.isEmpty() ? 
Collections.min(filteredSet) : null;
       this.candidatesMax = !filteredSet.isEmpty() ? 
Collections.max(filteredSet) : null;
       <#else>
-      this.candidatesMin = !filteredSet.isEmpty() ? 
Collections.min(filteredSet) : ${filter.javaBoxName}.MIN_VALUE;
-      this.candidatesMax = !filteredSet.isEmpty() ? 
Collections.max(filteredSet) : ${filter.javaBoxName}.MAX_VALUE;
+      this.candidatesMin = !filteredSet.isEmpty() ? 
Collections.min(filteredSet) : ${javaClassName}.MIN_VALUE;
+      this.candidatesMax = !filteredSet.isEmpty() ? 
Collections.max(filteredSet) : ${javaClassName}.MAX_VALUE;
       </#if>
     }
 
@@ -888,18 +894,18 @@ public final class ${className} {
       <#if filter.javaBoxName == "String">
       this.candidates = ReadWriteIOUtils.read${filter.dataType}Set(buffer);
       <#else>
-      this.candidates = ReadWriteIOUtils.read${filter.javaBoxName}Set(buffer);
+      this.candidates = ReadWriteIOUtils.read${javaClassName}Set(buffer);
       </#if>
       <#if filter.dataType == "boolean">
       // BooleanStatistics is not available
       this.candidatesMin = false;
       this.candidatesMax = false;
-      <#elseif filter.dataType == "Binary">
+      <#elseif filter.dataType == "Binary" || filter.dataType == "String">
       this.candidatesMin = !candidates.isEmpty() ? Collections.min(candidates) 
: null;
       this.candidatesMax = !candidates.isEmpty() ? Collections.max(candidates) 
: null;
       <#else>
-      this.candidatesMin = !candidates.isEmpty() ? Collections.min(candidates) 
: ${filter.javaBoxName}.MAX_VALUE;
-      this.candidatesMax = !candidates.isEmpty() ? Collections.max(candidates) 
: ${filter.javaBoxName}.MAX_VALUE;
+      this.candidatesMin = !candidates.isEmpty() ? Collections.min(candidates) 
: ${javaClassName}.MAX_VALUE;
+      this.candidatesMax = !candidates.isEmpty() ? Collections.max(candidates) 
: ${javaClassName}.MAX_VALUE;
       </#if>
       if(hasNull){
         this.candidates.add(null);
@@ -913,7 +919,7 @@ public final class ${className} {
       <#if filter.javaBoxName == "String">
       ReadWriteIOUtils.write${filter.dataType}Set(candidates, outputStream);
       <#else>
-      ReadWriteIOUtils.write${filter.javaBoxName}Set(candidates, outputStream);
+      ReadWriteIOUtils.write${javaClassName}Set(candidates, outputStream);
       </#if>
     }
 
@@ -951,7 +957,7 @@ public final class ${className} {
       super(measurementIndex, candidates);
     }
     <#else>
-    public ValueIn(int measurementIndex, Set<${filter.javaBoxName}> 
candidates) {
+    public ValueIn(int measurementIndex, Set<${javaClassName}> candidates) {
       super(measurementIndex, candidates);
     }
     </#if>
@@ -962,7 +968,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(Object value){
-      return candidates.contains((${filter.javaBoxName}) value);
+      return candidates.contains((${javaClassName}) value);
     }
 
     @Override
@@ -990,12 +996,12 @@ public final class ${className} {
 
       if (statistics.isPresent()) {
         Statistics<? extends Serializable> stat = statistics.get();
-        <#if filter.dataType == "Binary" && filter.javaBoxName == "String">
+        <#if (filter.dataType == "Binary" && filter.javaBoxName == "String") 
|| (filter.dataType == "String" && filter.javaBoxName == "Tag")>
         ${filter.dataType} valuesMin = (${filter.dataType}) stat.getMinValue();
         ${filter.dataType} valuesMax = (${filter.dataType}) stat.getMaxValue();
         <#else>
-        ${filter.javaBoxName} valuesMin = (${filter.javaBoxName}) 
stat.getMinValue();
-        ${filter.javaBoxName} valuesMax = (${filter.javaBoxName}) 
stat.getMaxValue();
+        ${javaClassName} valuesMin = (${javaClassName}) stat.getMinValue();
+        ${javaClassName} valuesMax = (${javaClassName}) stat.getMaxValue();
         </#if>
         // All values are same
         if (valuesMin.equals(valuesMax)) {
@@ -1003,7 +1009,7 @@ public final class ${className} {
         } else {
           if (!candidates.isEmpty()) {
             // All values are less than min, or greater than max
-            <#if filter.dataType == "Binary" && filter.javaBoxName == "String">
+            <#if (filter.dataType == "Binary" && filter.javaBoxName == 
"String") || (filter.dataType == "String" && filter.javaBoxName == "Tag")>
             return candidatesMin.compareTo(valuesMax) > 0
                 || candidatesMax.compareTo(valuesMin) < 0;
             <#else>
@@ -1044,12 +1050,12 @@ public final class ${className} {
       // All values are same
       if (statistics.isPresent()) {
         Statistics<? extends Serializable> stat = statistics.get();
-        <#if filter.dataType == "Binary" && filter.javaBoxName == "String">
+        <#if (filter.dataType == "Binary" && filter.javaBoxName == "String") 
|| (filter.dataType == "String" && filter.javaBoxName == "Tag")>
         ${filter.dataType} valuesMin = (${filter.dataType}) stat.getMinValue();
         ${filter.dataType} valuesMax = (${filter.dataType}) stat.getMaxValue();
         <#else>
-        ${filter.javaBoxName} valuesMin = (${filter.javaBoxName}) 
stat.getMinValue();
-        ${filter.javaBoxName} valuesMax = (${filter.javaBoxName}) 
stat.getMaxValue();
+        ${javaClassName} valuesMin = (${javaClassName}) stat.getMinValue();
+        ${javaClassName} valuesMax = (${javaClassName}) stat.getMaxValue();
         </#if>
         // All values are same
         if (valuesMin.equals(valuesMax)) {
@@ -1095,7 +1101,7 @@ public final class ${className} {
       super(measurementIndex, candidates);
     }
     <#else>
-    public ValueNotIn(int measurementIndex, Set<${filter.javaBoxName}> 
candidates) {
+    public ValueNotIn(int measurementIndex, Set<${javaClassName}> candidates) {
       super(measurementIndex, candidates);
     }
     </#if>
@@ -1106,7 +1112,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(Object value){
-      return !candidates.contains((${filter.javaBoxName}) value);
+      return !candidates.contains((${javaClassName}) value);
     }
 
     @Override
@@ -1203,7 +1209,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(${filter.dataType} value) {
-    <#if filter.dataType == "Binary">
+    <#if filter.dataType == "Binary" || filter.dataType == "String">
       return pattern.matcher(new MatcherInput(value.toString(), new 
AccessCount())).find();
     <#else>
       return pattern.matcher(new MatcherInput(String.valueOf(value), new 
AccessCount())).find();
@@ -1248,7 +1254,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(${filter.dataType} value) {
-    <#if filter.dataType == "Binary">
+    <#if filter.dataType == "Binary" || filter.dataType == "String">
       return !pattern.matcher(new MatcherInput(value.toString(), new 
AccessCount())).find();
     <#else>
       return !pattern.matcher(new MatcherInput(String.valueOf(value), new 
AccessCount())).find();
@@ -1353,7 +1359,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(${filter.dataType} value) {
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       return pattern.getMatcher().match(value.toString().getBytes());
       <#else>
       return pattern.getMatcher().match(String.valueOf(value).getBytes());
@@ -1398,7 +1404,7 @@ public final class ${className} {
 
     @Override
     public boolean valueSatisfy(${filter.dataType} value) {
-      <#if filter.dataType == "Binary">
+      <#if filter.dataType == "Binary" || filter.dataType == "String">
       return !pattern.getMatcher().match(value.toString().getBytes());
       <#else>
       return !pattern.getMatcher().match(String.valueOf(value).getBytes());
diff --git a/java/tsfile/src/main/codegen/templates/FilterTemplate.ftl 
b/java/tsfile/src/main/codegen/templates/FilterTemplate.ftl
index e1fd9fef..d0574842 100644
--- a/java/tsfile/src/main/codegen/templates/FilterTemplate.ftl
+++ b/java/tsfile/src/main/codegen/templates/FilterTemplate.ftl
@@ -66,6 +66,7 @@ public abstract class ${className} extends ValueFilter {
 
   protected abstract boolean valueSatisfy(${filter.dataType} value);
 
+ <#if filter.javaBoxName != "Tag">
   @Override
   public boolean[] satisfyTsBlock(boolean[] selection, TsBlock tsBlock) {
     Column valueColumn = tsBlock.getValueColumns()[measurementIndex];
@@ -83,6 +84,12 @@ public abstract class ${className} extends ValueFilter {
     }
     return satisfyInfo;
   }
+ <#else >
+   @Override
+   public boolean[] satisfyTsBlock(boolean[] selection, TsBlock tsBlock) {
+     throw new IllegalArgumentException("TagFilter cannot be applied to 
TsBlock");
+   }
+ </#if>
 
   @Override
   public void serialize(DataOutputStream outputStream) throws IOException {
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/controller/DeviceMetaIterator.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/controller/DeviceMetaIterator.java
index 4445bf9b..74c99efa 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/controller/DeviceMetaIterator.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/controller/DeviceMetaIterator.java
@@ -24,7 +24,7 @@ import org.apache.tsfile.file.metadata.IDeviceID;
 import org.apache.tsfile.file.metadata.MetadataIndexNode;
 import org.apache.tsfile.file.metadata.enums.MetadataIndexNodeType;
 import org.apache.tsfile.read.TsFileSequenceReader;
-import org.apache.tsfile.read.expression.ExpressionTree;
+import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.utils.Pair;
 
 import org.slf4j.Logger;
@@ -43,15 +43,15 @@ public class DeviceMetaIterator implements 
Iterator<Pair<IDeviceID, MetadataInde
   private final TsFileSequenceReader tsFileSequenceReader;
   private final Queue<MetadataIndexNode> metadataIndexNodes = new 
ArrayDeque<>();
   private final Queue<Pair<IDeviceID, MetadataIndexNode>> resultCache = new 
ArrayDeque<>();
-  private final ExpressionTree idFilter;
+  private final Filter tagFilter;
 
   public DeviceMetaIterator(
       TsFileSequenceReader tsFileSequenceReader,
       MetadataIndexNode metadataIndexNode,
-      ExpressionTree idFilter) {
+      Filter tagFilter) {
     this.tsFileSequenceReader = tsFileSequenceReader;
     this.metadataIndexNodes.add(metadataIndexNode);
-    this.idFilter = idFilter;
+    this.tagFilter = tagFilter;
   }
 
   @Override
@@ -74,7 +74,8 @@ public class DeviceMetaIterator implements 
Iterator<Pair<IDeviceID, MetadataInde
     for (int i = 0; i < leafChildren.size(); i++) {
       IMetadataIndexEntry child = leafChildren.get(i);
       final IDeviceID deviceID = ((DeviceMetadataIndexEntry) 
child).getDeviceID();
-      if (idFilter != null && !idFilter.satisfy(deviceID)) {
+      // time is only a placeholder here
+      if (tagFilter != null && !tagFilter.satisfyRow(0, 
deviceID.getSegments())) {
         continue;
       }
 
@@ -114,6 +115,7 @@ public class DeviceMetaIterator implements 
Iterator<Pair<IDeviceID, MetadataInde
           if (!resultCache.isEmpty()) {
             return;
           }
+          break;
         case INTERNAL_DEVICE:
           loadInternalNode(currentNode);
           break;
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/controller/IMetadataQuerier.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/controller/IMetadataQuerier.java
index 3503e49e..3f74178d 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/controller/IMetadataQuerier.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/controller/IMetadataQuerier.java
@@ -28,7 +28,7 @@ import org.apache.tsfile.file.metadata.TableSchema;
 import org.apache.tsfile.file.metadata.TsFileMetadata;
 import org.apache.tsfile.read.common.Path;
 import org.apache.tsfile.read.common.TimeRange;
-import org.apache.tsfile.read.expression.ExpressionTree;
+import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.utils.Pair;
 
 import java.io.IOException;
@@ -87,5 +87,5 @@ public interface IMetadataQuerier {
   void clear();
 
   Iterator<Pair<IDeviceID, MetadataIndexNode>> deviceIterator(
-      MetadataIndexNode root, ExpressionTree idFilter);
+      MetadataIndexNode root, Filter idFilter);
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/controller/MetadataQuerierByFileImpl.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/controller/MetadataQuerierByFileImpl.java
index ae7fb9ee..68e19530 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/controller/MetadataQuerierByFileImpl.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/controller/MetadataQuerierByFileImpl.java
@@ -34,7 +34,7 @@ import org.apache.tsfile.read.TsFileSequenceReader;
 import org.apache.tsfile.read.TsFileSequenceReader.LocateStatus;
 import org.apache.tsfile.read.common.Path;
 import org.apache.tsfile.read.common.TimeRange;
-import org.apache.tsfile.read.expression.ExpressionTree;
+import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.utils.Pair;
 
 import java.io.IOException;
@@ -306,7 +306,7 @@ public class MetadataQuerierByFileImpl implements 
IMetadataQuerier {
 
   @Override
   public Iterator<Pair<IDeviceID, MetadataIndexNode>> deviceIterator(
-      MetadataIndexNode root, ExpressionTree idFilter) {
-    return new DeviceMetaIterator(tsFileReader, root, idFilter);
+      MetadataIndexNode root, Filter tagFilter) {
+    return new DeviceMetaIterator(tsFileReader, root, tagFilter);
   }
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java
index cc48210d..a5aac76c 100755
--- a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java
@@ -77,6 +77,8 @@ public abstract class Filter {
 
   public abstract boolean satisfyBinary(long time, Binary value);
 
+  public abstract boolean satisfyString(long time, String value);
+
   /**
    * To examine whether the row(with time and values) is satisfied with the 
filter.
    *
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/TimeFilter.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/TimeFilter.java
index 18d444d7..a23ee31a 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/TimeFilter.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/TimeFilter.java
@@ -74,6 +74,11 @@ public abstract class TimeFilter extends Filter {
     return timeSatisfy(time);
   }
 
+  @Override
+  public boolean satisfyString(long time, String value) {
+    return timeSatisfy(time);
+  }
+
   @Override
   public boolean satisfyRow(long time, Object[] values) {
     // only use time to filter
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/ValueFilter.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/ValueFilter.java
index e1ae98bb..d715352f 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/ValueFilter.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/ValueFilter.java
@@ -84,6 +84,11 @@ public abstract class ValueFilter extends Filter {
     throw new UnSupportedDataTypeException(getClass().getName());
   }
 
+  @Override
+  public boolean satisfyString(long time, String value) {
+    throw new UnSupportedDataTypeException(getClass().getName());
+  }
+
   @Override
   public boolean satisfyBinary(long time, Binary value) {
     throw new UnSupportedDataTypeException(getClass().getName());
@@ -91,7 +96,7 @@ public abstract class ValueFilter extends Filter {
 
   @Override
   public boolean satisfyRow(long time, Object[] values) {
-    return satisfy(time, values[measurementIndex]);
+    return satisfy(time, measurementIndex < values.length ? 
values[measurementIndex] : null);
   }
 
   @Override
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/v4/ITsFileReader.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/factory/TagFilterBuilder.java
similarity index 53%
copy from java/tsfile/src/main/java/org/apache/tsfile/read/v4/ITsFileReader.java
copy to 
java/tsfile/src/main/java/org/apache/tsfile/read/filter/factory/TagFilterBuilder.java
index 885e8452..a3159509 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/read/v4/ITsFileReader.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/factory/TagFilterBuilder.java
@@ -17,31 +17,30 @@
  * under the License.
  */
 
-package org.apache.tsfile.read.v4;
+package org.apache.tsfile.read.filter.factory;
 
 import org.apache.tsfile.annotations.TsFileApi;
-import org.apache.tsfile.exception.read.ReadProcessException;
-import org.apache.tsfile.exception.write.NoMeasurementException;
-import org.apache.tsfile.exception.write.NoTableException;
 import org.apache.tsfile.file.metadata.TableSchema;
-import org.apache.tsfile.read.query.dataset.ResultSet;
+import org.apache.tsfile.read.filter.basic.Filter;
+import org.apache.tsfile.read.filter.operator.TagFilterOperators.ValueEq;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
 
-import java.io.IOException;
-import java.util.List;
-import java.util.Optional;
+public class TagFilterBuilder {
+  private TableSchema tableSchema;
 
-public interface ITsFileReader extends AutoCloseable {
+  public TagFilterBuilder(TableSchema tableSchema) {
+    this.tableSchema = tableSchema;
+  }
 
   @TsFileApi
-  ResultSet query(String tableName, List<String> columnNames, long startTime, 
long endTime)
-      throws ReadProcessException, IOException, NoTableException, 
NoMeasurementException;
-
-  @TsFileApi
-  Optional<TableSchema> getTableSchemas(String tableName) throws IOException;
-
-  @TsFileApi
-  List<TableSchema> getAllTableSchema() throws IOException;
-
-  @TsFileApi
-  void close();
+  public Filter eq(String columnName, Object value) {
+    int idColumnOrder = tableSchema.findIdColumnOrder(columnName);
+    if (idColumnOrder == -1) {
+      throw new IllegalArgumentException("Column '" + columnName + "' is not a 
tag column");
+    }
+    IMeasurementSchema columnSchema = tableSchema.findColumnSchema(columnName);
+
+    // +1 for table name
+    return new ValueEq(idColumnOrder + 1, (String) value);
+  }
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/And.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/And.java
index de0f3381..8df4e509 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/And.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/And.java
@@ -77,6 +77,11 @@ public class And extends BinaryLogicalFilter {
     return left.satisfyBinary(time, value) && right.satisfyBinary(time, value);
   }
 
+  @Override
+  public boolean satisfyString(long time, String value) {
+    return left.satisfyString(time, value) && right.satisfyString(time, value);
+  }
+
   @Override
   public boolean satisfyRow(long time, Object[] values) {
     return left.satisfyRow(time, values) && right.satisfyRow(time, values);
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Not.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Not.java
index 00918915..e02a3697 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Not.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Not.java
@@ -90,6 +90,11 @@ public class Not extends Filter {
     return !filter.satisfyRow(time, values);
   }
 
+  @Override
+  public boolean satisfyString(long time, String value) {
+    return !filter.satisfyString(time, value);
+  }
+
   @Override
   public boolean satisfyBooleanRow(long time, boolean[] values) {
     return !filter.satisfyBooleanRow(time, values);
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Or.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Or.java
index f0f1c2f1..97a78758 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Or.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/Or.java
@@ -77,6 +77,11 @@ public class Or extends BinaryLogicalFilter {
     return left.satisfyBinary(time, value) || right.satisfyBinary(time, value);
   }
 
+  @Override
+  public boolean satisfyString(long time, String value) {
+    return left.satisfyString(time, value) || right.satisfyString(time, value);
+  }
+
   @Override
   public boolean satisfyRow(long time, Object[] values) {
     return left.satisfyRow(time, values) || right.satisfyRow(time, values);
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java
index 0f2d72bf..2e5d0706 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java
@@ -20,9 +20,11 @@
 package org.apache.tsfile.read.query.dataset;
 
 import org.apache.tsfile.annotations.TsFileApi;
+import org.apache.tsfile.write.record.TSRecord;
 
 import java.io.IOException;
 import java.time.LocalDate;
+import java.util.Iterator;
 
 public interface ResultSet extends AutoCloseable {
 
@@ -87,5 +89,8 @@ public interface ResultSet extends AutoCloseable {
   boolean isNull(int columnIndex);
 
   @TsFileApi
-  public abstract void close();
+  void close();
+
+  @TsFileApi
+  Iterator<TSRecord> recordIterator();
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
index 5e050774..8a585df8 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
@@ -26,27 +26,36 @@ import org.apache.tsfile.read.common.RowRecord;
 import org.apache.tsfile.read.common.block.TsBlock;
 import org.apache.tsfile.read.reader.IPointReader;
 import org.apache.tsfile.read.reader.block.TsBlockReader;
+import org.apache.tsfile.write.record.TSRecord;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.List;
+import java.util.NoSuchElementException;
 
 public class TableResultSet extends AbstractResultSet {
+
   private static final Logger LOG = 
LoggerFactory.getLogger(TableResultSet.class);
 
   private TsBlockReader tsBlockReader;
   private IPointReader tsBlockPointReader;
   private List<String> columnNameList;
   private List<TSDataType> dataTypeList;
+  private String tableName;
 
   public TableResultSet(
-      TsBlockReader tsBlockReader, List<String> columnNameList, 
List<TSDataType> dataTypeList) {
+      TsBlockReader tsBlockReader,
+      List<String> columnNameList,
+      List<TSDataType> dataTypeList,
+      String tableName) {
     super(columnNameList, dataTypeList);
     this.tsBlockReader = tsBlockReader;
     this.columnNameList = columnNameList;
     this.dataTypeList = dataTypeList;
+    this.tableName = tableName;
   }
 
   @Override
@@ -85,4 +94,89 @@ public class TableResultSet extends AbstractResultSet {
       LOG.error("Failed to close tsBlockReader");
     }
   }
+
+  @Override
+  public Iterator<TSRecord> recordIterator() {
+    return new RecordIterator();
+  }
+
+  private class RecordIterator implements Iterator<TSRecord> {
+
+    private TSRecord cachedRecord = null;
+    private boolean exhausted = false;
+
+    @Override
+    public boolean hasNext() {
+      if (cachedRecord != null) {
+        return true;
+      }
+      if (exhausted) {
+        return false;
+      }
+
+      try {
+        return cacheNextRecord();
+      } catch (IOException e) {
+        throw new NoSuchElementException(e.toString());
+      }
+    }
+
+    private boolean cacheNextRecord() throws IOException {
+      boolean next = TableResultSet.this.next();
+      if (!next) {
+        exhausted = true;
+        return false;
+      }
+
+      cachedRecord = new TSRecord(tableName, getLong("Time"));
+      for (int i = 0; i < columnNameList.size(); i++) {
+        Field field = currentRow.getFields().get(i);
+        if (field != null) {
+          switch (field.getDataType()) {
+            case INT32:
+            case DATE:
+              cachedRecord.addPoint(columnNameList.get(i), field.getIntV());
+              break;
+            case INT64:
+            case TIMESTAMP:
+              cachedRecord.addPoint(columnNameList.get(i), field.getLongV());
+              break;
+            case FLOAT:
+              cachedRecord.addPoint(columnNameList.get(i), field.getFloatV());
+              break;
+            case DOUBLE:
+              cachedRecord.addPoint(columnNameList.get(i), field.getDoubleV());
+              break;
+            case STRING:
+            case TEXT:
+              cachedRecord.addPoint(columnNameList.get(i), 
field.getStringValue());
+              break;
+            case BLOB:
+              cachedRecord.addPoint(columnNameList.get(i), 
field.getBinaryV().getValues());
+              break;
+            case BOOLEAN:
+              cachedRecord.addPoint(columnNameList.get(i), field.getBoolV());
+              break;
+            case VECTOR:
+            case UNKNOWN:
+            default:
+              break;
+          }
+        } else {
+          cachedRecord.dataPointList.add(null);
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public TSRecord next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      TSRecord ret = cachedRecord;
+      cachedRecord = null;
+      return ret;
+    }
+  }
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
index 0f3aade6..1f12cc62 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
@@ -21,8 +21,10 @@ package org.apache.tsfile.read.query.dataset;
 
 import org.apache.tsfile.annotations.TsFileApi;
 import org.apache.tsfile.read.common.Path;
+import org.apache.tsfile.write.record.TSRecord;
 
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.stream.Collectors;
 
 public class TreeResultSet extends AbstractResultSet {
@@ -51,4 +53,9 @@ public class TreeResultSet extends AbstractResultSet {
   public void close() {
     // nothing to be done
   }
+
+  @Override
+  public Iterator<TSRecord> recordIterator() {
+    return null;
+  }
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/TableQueryExecutor.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/TableQueryExecutor.java
index a192a360..c50d1bf1 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/TableQueryExecutor.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/TableQueryExecutor.java
@@ -29,6 +29,7 @@ import org.apache.tsfile.file.metadata.TsFileMetadata;
 import org.apache.tsfile.read.controller.IChunkLoader;
 import org.apache.tsfile.read.controller.IMetadataQuerier;
 import org.apache.tsfile.read.expression.ExpressionTree;
+import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.read.query.executor.task.DeviceTaskIterator;
 import org.apache.tsfile.read.reader.block.DeviceOrderedTsBlockReader;
 import org.apache.tsfile.read.reader.block.TsBlockReader;
@@ -62,7 +63,7 @@ public class TableQueryExecutor {
    * @param tableName table to query
    * @param columns columns to query (ID or MEASUREMENT)
    * @param timeFilter time predicate
-   * @param idFilter id predicate
+   * @param tagFilter id predicate
    * @param measurementFilter measurement predicate
    * @return an iterator of TsBlocks
    * @throws ReadProcessException if the read process fails
@@ -71,7 +72,7 @@ public class TableQueryExecutor {
       String tableName,
       List<String> columns,
       ExpressionTree timeFilter,
-      ExpressionTree idFilter,
+      Filter tagFilter,
       ExpressionTree measurementFilter)
       throws ReadProcessException {
     TsFileMetadata fileMetadata = metadataQuerier.getWholeFileMetadata();
@@ -90,7 +91,7 @@ public class TableQueryExecutor {
 
     DeviceTaskIterator deviceTaskIterator =
         new DeviceTaskIterator(
-            columns, tableRoot, columnMapping, metadataQuerier, idFilter, 
tableSchema);
+            columns, tableRoot, columnMapping, metadataQuerier, tagFilter, 
tableSchema);
     switch (tableQueryOrdering) {
       case DEVICE:
         return new DeviceOrderedTsBlockReader(
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/task/DeviceTaskIterator.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/task/DeviceTaskIterator.java
index 8ee44650..399cd32c 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/task/DeviceTaskIterator.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/executor/task/DeviceTaskIterator.java
@@ -23,7 +23,7 @@ import org.apache.tsfile.file.metadata.IDeviceID;
 import org.apache.tsfile.file.metadata.MetadataIndexNode;
 import org.apache.tsfile.file.metadata.TableSchema;
 import org.apache.tsfile.read.controller.IMetadataQuerier;
-import org.apache.tsfile.read.expression.ExpressionTree;
+import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.read.query.executor.TableQueryExecutor.ColumnMapping;
 import org.apache.tsfile.utils.Pair;
 
@@ -41,11 +41,11 @@ public class DeviceTaskIterator implements 
Iterator<DeviceQueryTask> {
       MetadataIndexNode indexRoot,
       ColumnMapping columnMapping,
       IMetadataQuerier metadataQuerier,
-      ExpressionTree idFilter,
+      Filter tagFilter,
       TableSchema tableSchema) {
     this.columnNames = columnNames;
     this.columnMapping = columnMapping;
-    this.deviceMetaIterator = metadataQuerier.deviceIterator(indexRoot, 
idFilter);
+    this.deviceMetaIterator = metadataQuerier.deviceIterator(indexRoot, 
tagFilter);
     this.tableSchema = tableSchema;
   }
 
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/v4/DeviceTableModelReader.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/v4/DeviceTableModelReader.java
index 894353fa..927ac895 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/v4/DeviceTableModelReader.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/v4/DeviceTableModelReader.java
@@ -31,6 +31,7 @@ import org.apache.tsfile.read.controller.IChunkLoader;
 import org.apache.tsfile.read.controller.IMetadataQuerier;
 import org.apache.tsfile.read.controller.MetadataQuerierByFileImpl;
 import org.apache.tsfile.read.expression.ExpressionTree;
+import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.read.query.dataset.ResultSet;
 import org.apache.tsfile.read.query.dataset.TableResultSet;
 import org.apache.tsfile.read.query.executor.TableQueryExecutor;
@@ -79,6 +80,13 @@ public class DeviceTableModelReader implements ITsFileReader 
{
   @TsFileApi
   public ResultSet query(String tableName, List<String> columnNames, long 
startTime, long endTime)
       throws IOException, NoTableException, NoMeasurementException, 
ReadProcessException {
+    return query(tableName, columnNames, startTime, endTime, null);
+  }
+
+  @Override
+  public ResultSet query(
+      String tableName, List<String> columnNames, long startTime, long 
endTime, Filter tagFilter)
+      throws ReadProcessException, IOException, NoTableException, 
NoMeasurementException {
     String lowerCaseTableName = tableName.toLowerCase();
     TableSchema tableSchema = 
fileReader.getTableSchemaMap().get(lowerCaseTableName);
     if (tableSchema == null) {
@@ -100,9 +108,9 @@ public class DeviceTableModelReader implements 
ITsFileReader {
             lowerCaseTableName,
             lowerCaseColumnNames,
             new ExpressionTree.TimeBetweenAnd(startTime, endTime),
-            null,
+            tagFilter,
             null);
-    return new TableResultSet(tsBlockReader, columnNames, dataTypeList);
+    return new TableResultSet(tsBlockReader, columnNames, dataTypeList, 
tableName);
   }
 
   @Override
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/v4/ITsFileReader.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/v4/ITsFileReader.java
index 885e8452..995f73f6 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/read/v4/ITsFileReader.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/read/v4/ITsFileReader.java
@@ -24,6 +24,7 @@ import org.apache.tsfile.exception.read.ReadProcessException;
 import org.apache.tsfile.exception.write.NoMeasurementException;
 import org.apache.tsfile.exception.write.NoTableException;
 import org.apache.tsfile.file.metadata.TableSchema;
+import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.read.query.dataset.ResultSet;
 
 import java.io.IOException;
@@ -36,6 +37,11 @@ public interface ITsFileReader extends AutoCloseable {
   ResultSet query(String tableName, List<String> columnNames, long startTime, 
long endTime)
       throws ReadProcessException, IOException, NoTableException, 
NoMeasurementException;
 
+  @TsFileApi
+  ResultSet query(
+      String tableName, List<String> columnNames, long startTime, long 
endTime, Filter tagFilter)
+      throws ReadProcessException, IOException, NoTableException, 
NoMeasurementException;
+
   @TsFileApi
   Optional<TableSchema> getTableSchemas(String tableName) throws IOException;
 
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/utils/ReadWriteIOUtils.java 
b/java/tsfile/src/main/java/org/apache/tsfile/utils/ReadWriteIOUtils.java
index 6e38e513..e6b72e40 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/utils/ReadWriteIOUtils.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/utils/ReadWriteIOUtils.java
@@ -997,6 +997,18 @@ public class ReadWriteIOUtils {
     return set;
   }
 
+  public static Set<String> readStringSet(ByteBuffer buffer) {
+    int size = readInt(buffer);
+    if (size <= 0) {
+      return Collections.emptySet();
+    }
+    Set<String> set = new HashSet<>();
+    for (int i = 0; i < size; i++) {
+      set.add(readString(buffer));
+    }
+    return set;
+  }
+
   // read object set with self define length
   public static <T> Set<T> readObjectSet(ByteBuffer buffer) {
     int size = readInt(buffer);
@@ -1082,6 +1094,16 @@ public class ReadWriteIOUtils {
     }
   }
 
+  public static void writeStringSet(Set<String> set, DataOutputStream 
outputStream)
+      throws IOException {
+    write(set.contains(null) ? set.size() - 1 : set.size(), outputStream);
+    for (String e : set) {
+      if (e != null) {
+        write(e, outputStream);
+      }
+    }
+  }
+
   public static CompressionType readCompressionType(InputStream inputStream) 
throws IOException {
     byte n = readByte(inputStream);
     return CompressionType.deserialize(n);
@@ -1145,6 +1167,7 @@ public class ReadWriteIOUtils {
     BINARY,
     BOOLEAN,
     STRING,
+    TAG,
     NULL
   }
 
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java 
b/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java
index a9f8766b..090d7f80 100644
--- a/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java
+++ b/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java
@@ -22,10 +22,12 @@ package org.apache.tsfile.read.query;
 import org.apache.tsfile.enums.ColumnCategory;
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.TableSchema;
+import org.apache.tsfile.read.filter.factory.TagFilterBuilder;
 import org.apache.tsfile.read.query.dataset.ResultSet;
 import org.apache.tsfile.read.query.dataset.ResultSetMetadata;
 import org.apache.tsfile.read.v4.DeviceTableModelReader;
 import org.apache.tsfile.utils.TsFileGeneratorForTest;
+import org.apache.tsfile.write.record.TSRecord;
 import org.apache.tsfile.write.record.Tablet;
 import org.apache.tsfile.write.schema.MeasurementSchema;
 import org.apache.tsfile.write.v4.ITsFileWriter;
@@ -40,6 +42,7 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.util.Arrays;
+import java.util.Iterator;
 
 public class ResultSetTest {
 
@@ -139,6 +142,135 @@ public class ResultSetTest {
     }
   }
 
+  @Test
+  public void testQueryTableWithTagFilter() throws Exception {
+    TableSchema tableSchema =
+        new TableSchema(
+            "t1",
+            Arrays.asList(
+                new MeasurementSchema("id1", TSDataType.STRING),
+                new MeasurementSchema("id2", TSDataType.STRING),
+                new MeasurementSchema("s1", TSDataType.BOOLEAN),
+                new MeasurementSchema("s2", TSDataType.BOOLEAN)),
+            Arrays.asList(
+                ColumnCategory.TAG,
+                ColumnCategory.TAG,
+                ColumnCategory.FIELD,
+                ColumnCategory.FIELD));
+    Tablet tablet =
+        new Tablet(
+            Arrays.asList("id1", "id2", "s1", "s2"),
+            Arrays.asList(
+                TSDataType.STRING, TSDataType.STRING, TSDataType.BOOLEAN, 
TSDataType.BOOLEAN),
+            1024);
+    tablet.addTimestamp(0, 0);
+    tablet.addValue("id1", 0, "id_field1");
+    tablet.addValue("id2", 0, "id_field2");
+    tablet.addValue("s1", 0, true);
+    tablet.addValue("s2", 0, false);
+
+    tablet.addTimestamp(1, 1);
+    tablet.addValue("id1", 1, "id_field1_2");
+    tablet.addValue("s2", 1, true);
+
+    tablet.addTimestamp(2, 2);
+
+    try (ITsFileWriter writer =
+        new 
TsFileWriterBuilder().file(tsfile).tableSchema(tableSchema).build()) {
+      writer.write(tablet);
+    }
+
+    // eq
+    TagFilterBuilder filterBuilder = new TagFilterBuilder(tableSchema);
+    try (DeviceTableModelReader tsFileReader = new 
DeviceTableModelReader(tsfile);
+        ResultSet resultSet =
+            tsFileReader.query(
+                "T1",
+                Arrays.asList("ID1", "ID2", "S2", "S1"),
+                0,
+                2,
+                filterBuilder.eq("ID1", "id_field1"))) {
+
+      Assert.assertTrue(resultSet.next());
+      Assert.assertEquals(0, resultSet.getLong(1));
+      Assert.assertEquals("id_field1", resultSet.getString(2));
+      Assert.assertEquals("id_field2", resultSet.getString(3));
+      Assert.assertFalse(resultSet.getBoolean(4));
+      Assert.assertTrue(resultSet.getBoolean(5));
+    }
+  }
+
+  @Test
+  public void testQueryTableByIterator() throws Exception {
+    TableSchema tableSchema =
+        new TableSchema(
+            "t1",
+            Arrays.asList(
+                new MeasurementSchema("id1", TSDataType.STRING),
+                new MeasurementSchema("id2", TSDataType.STRING),
+                new MeasurementSchema("s1", TSDataType.BOOLEAN),
+                new MeasurementSchema("s2", TSDataType.BOOLEAN)),
+            Arrays.asList(
+                ColumnCategory.TAG,
+                ColumnCategory.TAG,
+                ColumnCategory.FIELD,
+                ColumnCategory.FIELD));
+    Tablet tablet =
+        new Tablet(
+            Arrays.asList("id1", "id2", "s1", "s2"),
+            Arrays.asList(
+                TSDataType.STRING, TSDataType.STRING, TSDataType.BOOLEAN, 
TSDataType.BOOLEAN),
+            1024);
+    tablet.addTimestamp(0, 0);
+    tablet.addValue("id1", 0, "id_field1");
+    tablet.addValue("id2", 0, "id_field2");
+    tablet.addValue("s1", 0, true);
+    tablet.addValue("s2", 0, false);
+
+    tablet.addTimestamp(1, 1);
+    tablet.addValue("id1", 1, "id_field1_2");
+    tablet.addValue("s2", 1, true);
+
+    tablet.addTimestamp(2, 2);
+
+    try (ITsFileWriter writer =
+        new 
TsFileWriterBuilder().file(tsfile).tableSchema(tableSchema).build()) {
+      writer.write(tablet);
+    }
+
+    try (DeviceTableModelReader tsFileReader = new 
DeviceTableModelReader(tsfile);
+        ResultSet resultSet =
+            tsFileReader.query("T1", Arrays.asList("ID1", "ID2", "S2", "S1"), 
0, 2); ) {
+      Iterator<TSRecord> tsRecordIterator = resultSet.recordIterator();
+
+      Assert.assertTrue(tsRecordIterator.hasNext());
+      TSRecord tsRecord = tsRecordIterator.next();
+      Assert.assertEquals(2, tsRecord.time);
+      Assert.assertNull(tsRecord.dataPointList.get(0));
+      Assert.assertNull(tsRecord.dataPointList.get(1));
+      Assert.assertNull(tsRecord.dataPointList.get(2));
+      Assert.assertNull(tsRecord.dataPointList.get(3));
+
+      Assert.assertTrue(tsRecordIterator.hasNext());
+      tsRecord = tsRecordIterator.next();
+      Assert.assertEquals(0, tsRecord.time);
+      Assert.assertEquals("id_field1", 
tsRecord.dataPointList.get(0).getValue().toString());
+      Assert.assertEquals("id_field2", 
tsRecord.dataPointList.get(1).getValue().toString());
+      Assert.assertFalse((Boolean) tsRecord.dataPointList.get(2).getValue());
+      Assert.assertTrue((Boolean) tsRecord.dataPointList.get(3).getValue());
+
+      Assert.assertTrue(tsRecordIterator.hasNext());
+      tsRecord = tsRecordIterator.next();
+      Assert.assertEquals(1, tsRecord.time);
+      Assert.assertEquals("id_field1_2", 
tsRecord.dataPointList.get(0).getValue().toString());
+      Assert.assertNull(tsRecord.dataPointList.get(1));
+      Assert.assertTrue((Boolean) tsRecord.dataPointList.get(2).getValue());
+      Assert.assertNull(tsRecord.dataPointList.get(3));
+
+      Assert.assertFalse(tsRecordIterator.hasNext());
+    }
+  }
+
   @Test
   public void testQueryWithMaxValue() throws Exception {
     TableSchema tableSchema =

Reply via email to