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

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

commit 30421a9b8a9b31966dafa93e4b02e66894ec0dd9
Author: qiaojialin <[email protected]>
AuthorDate: Tue Mar 3 11:29:08 2020 +0800

    update data query doc
---
 .../SystemDesign/5-DataQuery/2-SeriesReader.md     | 410 ++++++++++-----------
 .../SystemDesign/5-DataQuery/4-AggregationQuery.md |  12 +-
 .../SystemDesign/5-DataQuery/5-GroupByQuery.md     |  17 +-
 .../db/query/reader/series/IAggregateReader.java   |   2 +-
 .../query/reader/series/SeriesAggregateReader.java |   4 +-
 .../reader/series/SeriesRawDataPointReader.java    |   3 +
 .../iotdb/db/query/reader/series/SeriesReader.java |  91 ++---
 .../org/apache/iotdb/db/utils/FileLoaderUtils.java |  40 +-
 8 files changed, 269 insertions(+), 310 deletions(-)

diff --git a/docs/Documentation-CHN/SystemDesign/5-DataQuery/2-SeriesReader.md 
b/docs/Documentation-CHN/SystemDesign/5-DataQuery/2-SeriesReader.md
index eb6c5da..bcc59f3 100644
--- a/docs/Documentation-CHN/SystemDesign/5-DataQuery/2-SeriesReader.md
+++ b/docs/Documentation-CHN/SystemDesign/5-DataQuery/2-SeriesReader.md
@@ -23,59 +23,29 @@
 
 ## 设计原理
 
-IoTDB server 模块共提供 4 种不同形式的针对单个时间序列的读取接口,以支持不同形式的查询。
+IoTDB server 模块共提供 3 种不同形式的针对单个时间序列的读取接口,以支持不同形式的查询。
 
-* 按批遍历形式,返回 BatchData(主要用于不带值过滤的原始数据查询)
+* 原始数据查询接口,返回 BatchData,可带时间过滤条件或值过滤条件,两种过滤不可同时存在。
 * 聚合查询接口 (主要用于聚合查询和降采样查询)
-* 单点遍历形式,返回 TimeValuePair (主要用于带值过滤的查询)
-* 按递增时间戳查询对应值(主要用于带值过滤的查询)
+* 按递增时间戳查询对应值的接口(主要用于带值过滤的查询)
 
 ## 相关接口
 
-以上四种读取单个时间序列数据的方式对应代码里的四个接口
-
-### org.apache.iotdb.tsfile.read.reader.IPointReader
-
-#### 主要方法
-
-```
-// 判断是否还有数据点
-boolean hasNextTimeValuePair() throws IOException;
-
-// 获得当前数据点,并把游标后移
-TimeValuePair nextTimeValuePair() throws IOException;
-
-// 获得当前数据点,不移动游标
-TimeValuePair currentTimeValuePair() throws IOException;
-```
-
-#### 一般使用流程
-
-```
-while (pointReader.hasNextTimeValuePair()) {
-       TimeValuePair timeValuePair = pointReader.currentTimeValuePair();
-       
-       // use current timeValuePair to do some work
-       ...
-       
-       // get next timeValuePair
-       timeValuePair = mergeReader.nextTimeValuePair();
-}
-```
+以上三种读取单个时间序列数据的方式对应代码里的三个接口
 
 ### org.apache.iotdb.tsfile.read.reader.IBatchReader
 
 #### 主要方法
 
 ```
-// 判断是否还有BatchData
+// 判断是否还有 BatchData
 boolean hasNextBatch() throws IOException;
 
-// 获得下一个BatchData,并把游标后移
+// 获得下一个 BatchData,并把游标后移
 BatchData nextBatch() throws IOException;
 ```
 
-#### 一般使用流程
+#### 使用流程
 
 ```
 while (batchReader.hasNextBatch()) {
@@ -86,60 +56,37 @@ while (batchReader.hasNextBatch()) {
 }
 ```
 
-### org.apache.iotdb.db.query.reader.IReaderByTimestamp
+### org.apache.iotdb.db.query.reader.series.IAggregateReader
 
 #### 主要方法
 
-``` 
-// 得到给定时间戳的值,如果不存在返回null(要求传入的 timestamp 是递增的)
-Object getValueInTimestamp(long timestamp) throws IOException;
-```
-
-#### 一般使用流程
-
-该接口在带值过滤的查询中被使用,TimeGenerator生成时间戳后,使用该接口获得该时间戳对应的value
-
 ```
-while (timeGenerator.hasNext()) {
-       long timestamp = timeGenerator.next();
-       Object value = readerByTimestamp.getValueInTimestamp(timestamp);
-}
-```
-
-### org.apache.iotdb.db.query.reader.seriesRelated.IAggregateReader
-
-#### 主要方法
-
-```
-// 判断是否还有Chunk
+// 判断是否还有 Chunk
 boolean hasNextChunk() throws IOException;
 
-// 判断是否能够使用当前Chunk的统计信息
+// 判断是否能够使用当前 Chunk 的统计信息
 boolean canUseCurrentChunkStatistics();
 
-// 获得当前Chunk的统计信息
+// 获得当前 Chunk 的统计信息
 Statistics currentChunkStatistics();
 
-// 跳过当前Chunk
+// 跳过当前 Chunk
 void skipCurrentChunk();
 
-// 判断当前Chunk是否还有下一个Page
+// 判断当前Chunk是否还有下一个 Page
 boolean hasNextPage() throws IOException;
 
-// 判断能否使用当前Page的统计信息
+// 判断能否使用当前 Page 的统计信息
 boolean canUseCurrentPageStatistics() throws IOException;
 
-// 获得当前Page的统计信息
+// 获得当前 Page 的统计信息
 Statistics currentPageStatistics() throws IOException;
 
-// 跳过当前的Page
+// 跳过当前的 Page
 void skipCurrentPage();
 
-// 判断是否有下一个重叠的Page
-boolean hasNextOverlappedPage() throws IOException;
-
-// 获得当前重叠Page的数据
-BatchData nextOverlappedPage() throws IOException;
+// 获得当前 Page 的数据
+BatchData nextPage() throws IOException;
 ```
 
 #### 一般使用流程
@@ -149,266 +96,289 @@ while (aggregateReader.hasNextChunk()) {
   if (aggregateReader.canUseCurrentChunkStatistics()) {
     Statistics chunkStatistics = aggregateReader.currentChunkStatistics();
     
-    // do some aggregate calculation using chunk statistics
+    // 用 chunk 层的统计信息计算
     ...
     
     aggregateReader.skipCurrentChunk();
     continue;
   }
-         
+  
+  // 把当前 chunk 中的 page 消耗完
   while (aggregateReader.hasNextPage()) {
         if (aggregateReader.canUseCurrentPageStatistics()) {
+          // 可以用统计信息
           Statistics pageStatistic = aggregateReader.currentPageStatistics();
           
-          // do some aggregate calculation using page statistics
-      ...
-          
+          // 用 page 层的统计信息计算
+          ...
+         
           aggregateReader.skipCurrentPage();
           continue;
-        }
-        
-        // 遍历所有重叠的page
-        while (aggregateReader.hasNextOverlappedPage()) {
+        } else {
+          // 不能用统计信息,需要用数据计算
           BatchData batchData = aggregateReader.nextOverlappedPage();
           
-          // do some aggregate calculation using batch data
-      ...
+          // 用 batchData 计算
+          ...
         }
   }
 }
 ```
 
-## 具体实现类
-
-上述四个接口都有其对应的实现类,这四个实现类的内部都有一个 SeriesReader 对象,SeriesReader是一个基础的工具类,
-封装了对于一个时间序列读取操作的基本方法。所以,下面先介绍一下SeriesReader的实现,然后再依次介绍四个接口实现类的具体实现。
+### org.apache.iotdb.db.query.reader.IReaderByTimestamp
 
-### org.apache.iotdb.db.query.reader.seriesRelated.SeriesReader
+#### 主要方法
 
-首先介绍一下SeriesReader里的几个重要字段
+``` 
+// 得到给定时间戳的值,如果不存在返回 null(要求传入的 timestamp 是递增的)
+Object getValueInTimestamp(long timestamp) throws IOException;
 
-* private final List<TsFileResource> seqFileResource;
+// 给定一批递增时间戳的值,返回一批结果(减少方法调用次数)
+Object[] getValuesInTimestamps(long[] timestamps) throws IOException;
+```
 
-       顺序文件列表,因为顺序文件本身就保证有序,且时间戳互不重叠,只需使用List进行存储
-  
-* private final PriorityQueue<TsFileResource> unseqFileResource;
-       
-       乱序文件列表,因为乱序文件互相之间不保证顺序性,且可能有重叠,为了保证顺序,使用优先队列进行存储
+#### 一般使用流程
 
-* private final List<ChunkMetaData> seqChunkMetadatas = new LinkedList<>();
+该接口在带值过滤的查询中被使用,TimeGenerator生成时间戳后,使用该接口获得该时间戳对应的value
 
-       顺序文件解开后得到的ChunkMetaData列表,本身有序且互不重叠,所以使用List存储
-  
-* private final PriorityQueue<ChunkMetaData> unseqChunkMetadatas =
-      new 
PriorityQueue<>(Comparator.comparingLong(ChunkMetaData::getStartTime));
-      
-   乱序文件解开后得到的ChunkMetaData列表,互相之间可能有重叠,为了保证顺序,使用优先队列进行存储
+```
+Object value = readerByTimestamp.getValueInTimestamp(timestamp);
 
-* private boolean hasCachedFirstChunkMetadata;
+or
 
-       是否缓存了第一个chunk meta data
-       
-* private ChunkMetaData firstChunkMetaData;
+Object[] values = readerByTimestamp.getValueInTimestamp(timestamps);
+```
 
-       当前第一个chunk meta data的引用
+## 具体实现类
 
-* private PriorityQueue<VersionPair<IPageReader>> cachedPageReaders =
-      new PriorityQueue<>(
-          Comparator.comparingLong(pageReader ->                               
pageReader.data.getStatistics().getStartTime()));
-   
-    所有重叠page的reader,按照每个page的起始时间进行排序
-    
-* private PriorityMergeReader mergeReader = new PriorityMergeReader();
+上述三个接口都有其对应的实现类,由于以上三种查询有共性,我们设计了一个基础的 SeriesReader 
工具类,封装了对于一个时间序列读取操作的基本方法,帮助实现以上三种接口。下面首先介绍 SeriesReader 的设计原理,然后再依次介绍三个接口的具体实现。
 
-        PriorityMergeReader可以接收多个数据源,且根据其优先级输出优先级最高的数据点
+### org.apache.iotdb.db.query.reader.series.SeriesReader
 
-* private boolean hasCachedNextBatch;
+#### 设计思想
 
-        是否缓存了下一个batch
-        
-* private BatchData cachedBatchData;
+背景知识:TsFile 文件(TsFileResource)解开后可以得到 ChunkMetadata,ChunkMetadata 解开后可以得到一堆 
PageReader,PageReader 可以直接返回 BatchData 数据点。
 
-        缓存的下一个batch的引用
-        
-下面介绍一下SeriesReader里的重要方法
+为了支持以上三种接口
 
-#### hasNextChunk()
+数据按照粒度从大到小分成四种:文件,Chunk,Page,相交数据点。在原始数据查询中,最大的数据块返回粒度是一个 page,如果一个 page 和其他 
page 由于乱序写入相互覆盖了,就解开成数据点做合并。聚合查询中优先使用 Chunk 的统计信息,其次是 Page 的统计信息,最后是相交数据点。
 
-这个方法判断该时间序列还有没有下一个chunk。
+设计原则是能用粒度大的就不用粒度小的。
 
-如果`hasCachedFirstChunkMetadata`为`true`,则代表当前已经缓存了第一个`ChunkMetaData`,且该`ChunkMetaData`并未被使用,直接返回`true`;若`hasCachedFirstChunkMetadata`为`false`,我们调用`tryToInitFirstChunk()`,尝试去解开顺序文件和乱序文件,并从中选出开始时间最小的chunk
 meta data赋值给`firstChunkMetaData`,并将`hasCachedFirstChunkMetadata`置为`true`。
+首先介绍一下SeriesReader里的几个重要字段
 
-#### tryToInitFirstChunk()
+```
 
-这个方法尝试去初始化当前的chunk meta data。
+/*
+ * 文件层
+ */
+private final List<TsFileResource> seqFileResource;
+       顺序文件列表,因为顺序文件本身就保证有序,且时间戳互不重叠,只需使用List进行存储
+       
+private final PriorityQueue<TsFileResource> unseqFileResource;
+       乱序文件列表,因为乱序文件互相之间不保证顺序性,且可能有重叠,为了保证顺序,使用优先队列进行存储
+       
+/*
+ * chunk 层
+ * 
+ * 三个字段之间数据永远不重复,first 永远是第一个(开始时间最小)
+ */
+private ChunkMetaData firstChunkMetaData;
+       填充 chunk 层时优先填充此字段,保证这个 chunk 具有当前最小开始时间
+       
+private final List<ChunkMetaData> seqChunkMetadatas;
+       顺序文件解开后得到的 ChunkMetaData 存放在此,本身有序且互不重叠,所以使用 List 存储
 
-首先调用`tryToFillChunkMetadatas()`去尝试解开顺序和乱序文件,并填充`seqChunkMetadatas`和`unseqChunkMetadatas`,之后再从这二者里选出开始时间最小的。如果只有顺序的,直接取出`seqChunkMetadatas`的第一个;如果只有乱序的,直接取出`unseqChunkMetadatas`的第一个,如果既有顺序的又有乱序的,比较两者第一个的开始时间大小,取较小者取出。
+private final PriorityQueue<ChunkMetaData> unseqChunkMetadatas;
+       乱序文件解开后得到的 ChunkMetaData 存放在此,互相之间可能有重叠,为了保证顺序,使用优先队列进行存储
+       
+/*
+ * page 层
+ *
+ * 两个字段之间数据永远不重复,first 永远是第一个(开始时间最小)
+ */ 
+private VersionPageReader firstPageReader;
+       开始时间最小的 page reader
+       
+private PriorityQueue<VersionPageReader> cachedPageReaders;
+       当前获得的所有 page reader,按照每个 page 的起始时间进行排序
+       
+/*
+ * 相交数据点层
+ */ 
+private PriorityMergeReader mergeReader;
+       本质上是多个带优先级的 page,按时间戳从低到高输出数据点,时间戳相同时,保留优先级高的
+
+/*
+ * 相交数据点产出结果的缓存
+ */ 
+private boolean hasCachedNextOverlappedPage;
+       是否缓存了下一个batch
+       
+private BatchData cachedBatchData;
+       缓存的下一个batch的引用
+```
+        
+下面介绍一下SeriesReader里的重要方法
 
-#### tryToFillChunkMetadatas()
+#### hasNextChunk()
 
-这个方法尝试解开顺序和乱序文件。
+* 主要功能:判断该时间序列还有没有下一个chunk。
 
-因为可能一个时间序列对应很多个文件,所以一下子解开全部的文件,可能会导致OOM,所以我们推迟解开文件的时间至其被需要时。
+* 约束:在调用这个方法前,需要保证 `SeriesReader` 内已经没有 page 和 数据点 层级的数据了,也就是之前解开的 chunk 都消耗完了。
 
-因为顺序文件之间相互不重叠,所以,对于顺序文件,我们只需要保证`seqChunkMetadatas`不为空即可,每次检测到`seqChunkMetadatas`为空后,调用`loadSatisfiedChunkMetadatas(TsFileResource)`方法去解开`seqFileResource`的第一个顺序文件。
+* 实现:如果 `firstChunkMetaData` 不为空,则代表当前已经缓存了第一个 
`ChunkMetaData`,且未被使用,直接返回`true`;
 
-而乱序文件之间可能会相互重叠,仅仅解开第一个乱序文件是不够的,还需要解开与`unseqChunkMetadatas`中第一个chunk meta 
data时间有重叠的所有乱序文件。
+       尝试去解开第一个顺序文件和第一个乱序文件,填充 chunk 层。并解开与 `firstChunkMetadata` 相重合的所有文件。
 
 #### isChunkOverlapped()
 
-这个方法判断当前的chunk有没有其他与之重叠的chunk存在。
+* 主要功能:判断当前的 chunk 有没有与其他 Chunk 有重叠
 
-如果`mergeReader`里仍然有数据,或者`seqChunkMetadatas`里有与`firstChunkMetaData`时间重叠的,或者`unseqChunkMetadatas`里有与`firstChunkMetaData`时间重叠的,则返回`true`;反之,返回`false`。
+* 约束:在调用这个方法前,需要保证 chunk 层已经缓存了 `firstChunkMetadata`,也就是调用了 hasNextChunk() 并且为 
true。
+
+* 实现:直接把 `firstChunkMetadata` 与 `seqChunkMetadatas` 和 `unseqChunkMetadatas` 
相比较。因为此前已经保证所有和 `firstChunkMetadata` 相交的文件都被解开了。
 
 #### currentChunkStatistics()
 
-返回`firstChunkMetaData`的统计信息。
+返回 `firstChunkMetaData` 的统计信息。
 
 #### skipCurrentChunk()
 
-跳过当前chunk。只需要将`hasCachedFirstChunkMetadata`置为`false`,`firstChunkMetaData`置为`null`即可。
+跳过当前 chunk。只需要将`firstChunkMetaData `置为`null`。
 
 #### hasNextPage()
 
-这个方法判断是否有下一个Page,一般在`firstChunkMetaData`不可直接使用时,继续解成Page。
-
-首先调用`fillOverlappedPageReaders()`去将`firstChunkMetaData`解开为`PageReader`,解开的`PageReader`都放进`cachedPageReaders`里。并将`hasCachedFirstChunkMetadata`置为`false`,`firstChunkMetaData`置为`null`。若`cachedPageReaders`为空则返回`false`,若不为空,返回`true`。
+* 主要功能:判断 SeriesReader 中还有没有已经解开的 page,如果有相交的 page,就构造 `cachedBatchData` 
并缓存,否则缓存 `firstPageReader`。
 
-#### isPageOverlapped()
+* 实现:如果已经缓存了 `cachedBatchData` 就直接返回。如果有相交数据点,就构造 `cachedBatchData`。如果已经缓存了 
`firstPageReader`,就直接返回。
 
-这个方法判断当前的Page有没有其他与之重叠的Page存在。
+       如果当前的 `firstChunkMetadata` 还没有解开,就解开与之重叠的所有 ChunkMetadata,构造 
firstPageReader。
+       
+       判断,如果 `firstPageReader` 和 `cachedPageReaders` 相交,则构造 
`cachedBatchData`,否则直接返回。
 
-如果`mergeReader`里仍然有数据,或者`seqChunkMetadatas`里有与`cachedPageReaders`里第一个`PageReader`时间重叠的,或者`unseqChunkMetadatas`里有与`cachedPageReaders`里第一个`PageReader`时间重叠的,则返回`true`;反之,返回`false`。
+#### isPageOverlapped()
 
-#### nextPage()
+* 主要功能:判断当前的 page 有没有与其他 page 有重叠
 
-须与`isPageOverlapped()`方法搭配使用。
+* 约束:在调用这个方法前,需要保证调用了 hasNextPage() 并且为 true。也就是,有可能缓存了一个相交的 
`cachedBatchData`,或者缓存了不相交的 `firstPageReader`。
 
-当`cachedPageReaders`里第一个Page没有与之重叠的其他Page时,直接获得`cachedPageReaders`的第一个Page里符合过滤条件的所有data。
+* 实现:先判断有没有 `cachedBatchData`,如果没有,就说明当前 page 不相交,则 `mergeReader` 里没数据。再判断 
`firstPageReader` 是否与 `cachedPageReaders` 中的 page 相交。
 
 #### currentPageStatistics()
 
-返回`cachedPageReaders`里第一个Page的统计信息。
+返回 `firstPageReader` 的统计信息。
 
 #### skipCurrentPage()
 
-跳过当前Page。只需要将`cachedPageReaders`里第一个PageReader删掉即可。
+跳过当前Page。只需要将 `firstPageReader` 置为 null。
 
-#### hasNextOverlappedPage()
+#### nextPage()
 
-这个方法判断当前还有没有重叠的Page。
+* 主要功能:返回下一个相交或不想交的 page
 
-如果`hasCachedNextBatch`为`true`,直接返回`true`。
+* 约束:在调用这个方法前,需要保证调用了 hasNextPage() 并且为 true。也就是,有可能缓存了一个相交的 
`cachedBatchData`,或者缓存了不相交的 `firstPageReader`。
 
-否则,先调用`putAllDirectlyOverlappedPageReadersIntoMergeReader()`方法,将所有与`cachedPageReaders`第一个Page有重叠的PageReader放进`mergeReader`里。`mergeReader`里维护了一个`currentLargestEndTime`变量,每次add进新的Reader时被更新,用以记录当前添加进`mergeReader`的最大的结束时间。
+* 实现:如果 `hasCachedNextOverlappedPage` 为 true,说明缓存了一个相交的 page,直接返回 
`cachedBatchData`。否则当前 page 不相交,直接从 firstPageReader 里拿当前 page 的数据。
 
-然后先从`mergeReader`里取出当前最大的结束时间,作为此次所要返回的batch的结束时间,记为`currentPageEndTime`。接着去遍历`mergeReader`,直到当前的时间戳大于`currentPageEndTime`。
+#### hasNextOverlappedPage()
 
-在迭代的过程里,我们也要判断是否有与当前时间戳重叠的file或者chunk或者page(这里之所以还要再做一次判断,是因为,比如当前page是1-30,和他直接相交的page是20-50,还有一个page是40-60,每取一个点判断一次是想把40-60解开),如果有,解开相应的file或者chunk或者page,并将其放入`mergeReader`。完成重叠的判断后,从`mergeReader`中取出相应数据。
+* 主要功能:内部方法,用来判断当前有没有重叠的数据,并且构造相交的 page 并缓存。
 
-完成迭代后将获得数据缓存在`cachedBatchData`中,并将`hasCachedNextBatch`置为`true`。
+* 实现:如果 `hasCachedNextOverlappedPage` 为 `true`,直接返回 `true`。
 
-#### nextOverlappedPage()
+       否则,先调用 `tryToPutAllDirectlyOverlappedPageReadersIntoMergeReader()` 方法,将 
`cachedPageReaders` 中所有与 `firstPageReader` 有重叠的放进 `mergeReader` 里。`mergeReader` 
里维护了一个 `currentLargestEndTime` 变量,每次添加进新的 Reader 时被更新,用以记录当前添加进 `mergeReader` 的 
page 的最大结束时间。   
+       
然后先从`mergeReader`里取出当前最大的结束时间,作为第一批数据的结束时间,记为`currentPageEndTime`。接着去遍历`mergeReader`,直到当前的时间戳大于`currentPageEndTime`。
+       
+       每从 mergeReader 
移出一个点前,我们先要判断是否有与当前时间戳重叠的file或者chunk或者page(这里之所以还要再做一次判断,是因为,比如当前page是1-30,和他直接相交的page是20-50,还有一个page是40-60,每取一个点判断一次是想把40-60解开),如果有,解开相应的file或者chunk或者page,并将其放入`mergeReader`。完成重叠的判断后,从`mergeReader`中取出相应数据。
 
-将缓存的`cachedBatchData`返回,并将`hasCachedNextBatch`置为`false`。
+       完成迭代后将获得数据缓存在 `cachedBatchData` 中,并将 `hasCachedNextOverlappedPage` 置为 
`true`。
 
-### org.apache.iotdb.db.query.reader.seriesRelated.SeriesRawDataPointReader
+#### nextOverlappedPage()
 
-`SeriesRawDataPointReader`实现了`IPointReader`。
+将缓存的`cachedBatchData`返回,并将`hasCachedNextOverlappedPage`置为`false`。
 
-其方法`hasNextTimeValuePair()`的核心判断流程是去组合调用SeriesReader的三个方法
-
-```
-while (seriesReader.hasNextChunk()) {
-  while (seriesReader.hasNextPage()) {
-    if (seriesReader.hasNextOverlappedPage()) {
-      return true;
-    }
-  }
-}
-return false;
-```
-
-### org.apache.iotdb.db.query.reader.seriesRelated.SeriesRawDataBatchReader
+### org.apache.iotdb.db.query.reader.series.SeriesRawDataBatchReader
 
 `SeriesRawDataBatchReader`实现了`IBatchReader`。
 
 其方法`hasNextBatch()`的核心判断流程是
 
 ```
-// 判断有没有下一个chunk
+// 有缓存了 batch,直接返回
+if (hasCachedBatchData) {
+  return true;
+}
+
+/*
+ * 如果 SeriesReader 里还有 page,返回 page
+ */
+if (readPageData()) {
+  hasCachedBatchData = true;
+  return true;
+}
+
+/*
+ * 如果有 chunk,并且有 page,返回 page
+ */
 while (seriesReader.hasNextChunk()) {
-  // 判断有没有下一个page
-  while (seriesReader.hasNextPage()) {
-    // 如果当前page没有重叠
-    if (!seriesReader.isPageOverlapped()) {
-      // 直接调用nextPage()方法返回一个batch
-      batchData = seriesReader.nextPage();
-      hasCachedBatchData = true;
-      return true;
-    }
-    // 如果当前还有下一个重叠的Page
-    if (seriesReader.hasNextOverlappedPage()) {
-      // 调用nextOverlappedPage()方法返回一个batch
-      batchData = seriesReader.nextOverlappedPage();
-      hasCachedBatchData = true;
-      return true;
-    }
+  if (readPageData()) {
+    hasCachedBatchData = true;
+    return true;
   }
 }
-return false;
+return hasCachedBatchData;
 ```
 
-### org.apache.iotdb.db.query.reader.seriesRelated.SeriesReaderByTimestamp
+### org.apache.iotdb.db.query.reader.series.SeriesReaderByTimestamp
+
+`SeriesReaderByTimestamp` 实现了 `IReaderByTimestamp`。
 
-`SeriesReaderByTimestamp`实现了`IReaderByTimestamp`。
+设计思想:当给一个时间戳要查询值时,这个时间戳可以转化为一个 time >= x 的过滤条件。不断更新这个过滤条件,并且跳过不满足的文件,chunk 和 
page。
 
-其方法getValueInTimestamp()的核心流程是找到下一个满足条件的batch 
data,然后调`BatchData`的`getValueInTimestamp(long)`方法获得相应值,而找下一个batch data的流程是
+实现方式:
 
 ```
-// 判断是否有下一个chunk
+/*
+ * 优先判断下一个 page 有没有当前所查时间,能跳过就跳过
+ */
+if (readPageData(timestamp)) {
+  return true;
+}
+
+/*
+ * 判断下一个 chunk 有没有当前所查时间,能跳过就跳过
+ */
 while (seriesReader.hasNextChunk()) {
-  // 如果当前chunk不满足过滤条件,直接跳过当前chunk,进入下一个循环
-  if (!satisfyTimeFilter(seriesReader.currentChunkStatistics())) {
+  Statistics statistics = seriesReader.currentChunkStatistics();
+  if (!satisfyTimeFilter(statistics)) {
     seriesReader.skipCurrentChunk();
     continue;
   }
-  // 判断是否有下一个page
-  while (seriesReader.hasNextPage()) {
-    // 如果当前page不满足过滤条件,直接跳过当前page,进入下一个循环
-    if (!satisfyTimeFilter(seriesReader.currentPageStatistics())) {
-      seriesReader.skipCurrentPage();
-      continue;
-    }
-    // 如果当前的page没有重叠
-    if (!seriesReader.isPageOverlapped()) {
-      // 直接调用nextPage()方法获得下一个batch
-      batchData = seriesReader.nextPage();
-    } 
-    // 如果当前的page有重叠
-    else {
-      // 调用nextOverlappedPage()方法获得下一个batch
-      batchData = seriesReader.nextOverlappedPage();
-    }
-    // 如果该batch满足要求,则返回true,否则继续搜索下一个batch
-    if (batchData.getTimeByIndex(batchData.length() - 1) >= timestamp) {
-      return true;
-    }
+  /*
+   * chunk 不能跳过,继续到 chunk 里检查 page
+   */
+  if (readPageData(timestamp)) {
+    return true;
   }
 }
 return false;
 ```
 
-### org.apache.iotdb.db.query.reader.seriesRelated.SeriesAggregateReader
+### org.apache.iotdb.db.query.reader.series.SeriesAggregateReader
 
-`SeriesAggregateReader`实现了`IAggregateReader`
+`SeriesAggregateReader` 实现了 `IAggregateReader`
 
 
`IAggregateReader`的大部分接口方法都在`SeriesReader`有对应实现,除了`canUseCurrentChunkStatistics()`和`canUseCurrentPageStatistics()`两个方法。
 
 #### canUseCurrentChunkStatistics()
 
+设计思想:可以用统计信息的条件是当前 chunk 不重叠,并且满足过滤条件。
+
 
先调用`SeriesReader`的`currentChunkStatistics()`方法,获得当前chunk的统计信息,再调用`SeriesReader`的`isChunkOverlapped()`方法判断当前chunk是否重叠,如果当前chunk不重叠,且其统计信息满足过滤条件,则返回`true`,否则返回`false`。
 
 #### canUseCurrentPageStatistics()
 
-先调用`SeriesReader`的`currentPageStatistics()`方法,获得当前page的统计信息,再调用`SeriesReader`的`isPageOverlapped()`方法判断当前page是否重叠,如果当前page不重叠,且其统计信息满足过滤条件,则返回`true`,否则返回`false`。
+设计思想:可以用统计信息的条件是当前 page 不重叠,并且满足过滤条件。
+
+先调用`SeriesReader`的 `currentPageStatistics()` 
方法,获得当前page的统计信息,再调用`SeriesReader` 的 `isPageOverlapped()` 方法判断当前 page 
是否重叠,如果当前page不重叠,且其统计信息满足过滤条件,则返回`true`,否则返回`false`。
\ No newline at end of file
diff --git 
a/docs/Documentation-CHN/SystemDesign/5-DataQuery/4-AggregationQuery.md 
b/docs/Documentation-CHN/SystemDesign/5-DataQuery/4-AggregationQuery.md
index 31f3c4a..56b2589 100644
--- a/docs/Documentation-CHN/SystemDesign/5-DataQuery/4-AggregationQuery.md
+++ b/docs/Documentation-CHN/SystemDesign/5-DataQuery/4-AggregationQuery.md
@@ -63,15 +63,11 @@ while (aggregateReader.hasNextChunk()) {
           
           aggregateReader.skipCurrentPage();
           continue;
-        }
-        
-        // 遍历所有重叠的page
-        while (aggregateReader.hasNextOverlappedPage()) {
-          BatchData batchData = aggregateReader.nextOverlappedPage();
-          
-          // do some aggregate calculation using batch data
+        } else {
+               BatchData batchData = aggregateReader.nextPage();
+               // do some aggregate calculation using batch data
       ...
-        }
+        }       
   }
 }
 ```
diff --git a/docs/Documentation-CHN/SystemDesign/5-DataQuery/5-GroupByQuery.md 
b/docs/Documentation-CHN/SystemDesign/5-DataQuery/5-GroupByQuery.md
index 6fa7d22..911ee7e 100644
--- a/docs/Documentation-CHN/SystemDesign/5-DataQuery/5-GroupByQuery.md
+++ b/docs/Documentation-CHN/SystemDesign/5-DataQuery/5-GroupByQuery.md
@@ -89,6 +89,7 @@ if (aggregateResultList.length == 0) {
 ```
 while (aggregateReader.hasNextChunk()) {
   if (aggregateReader.canUseCurrentChunkStatistics()) {
+    // 可以用 chunk 统计信息
     Statistics chunkStatistics = aggregateReader.currentChunkStatistics();
     
     // do some aggregate calculation using chunk statistics
@@ -97,23 +98,19 @@ while (aggregateReader.hasNextChunk()) {
     aggregateReader.skipCurrentChunk();
     continue;
   }
-         
+  
+  // chunk 统计信息不能用,消耗所有 page
   while (aggregateReader.hasNextPage()) {
         if (aggregateReader.canUseCurrentPageStatistics()) {
+          // 可以用 page 统计信息
           Statistics pageStatistic = aggregateReader.currentPageStatistics();
-          
-          // do some aggregate calculation using page statistics
       ...
           
           aggregateReader.skipCurrentPage();
           continue;
-        }
-        
-        // 遍历所有重叠的page
-        while (aggregateReader.hasNextOverlappedPage()) {
-          BatchData batchData = aggregateReader.nextOverlappedPage();
-          
-          // do some aggregate calculation using batch data
+        } else {
+               // page 统计信息不能用,用数据计算
+               BatchData batchData = aggregateReader.nextPage();
       ...
         }
   }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/IAggregateReader.java
 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/IAggregateReader.java
index 23e5dde..4893970 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/IAggregateReader.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/IAggregateReader.java
@@ -27,7 +27,7 @@ public interface IAggregateReader {
 
   boolean hasNextChunk() throws IOException;
 
-  boolean canUseCurrentChunkStatistics();
+  boolean canUseCurrentChunkStatistics() throws IOException;
 
   Statistics currentChunkStatistics();
 
diff --git 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesAggregateReader.java
 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesAggregateReader.java
index 453d6af..7a6e40b 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesAggregateReader.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesAggregateReader.java
@@ -46,7 +46,7 @@ public class SeriesAggregateReader implements 
IAggregateReader {
   }
 
   @Override
-  public boolean canUseCurrentChunkStatistics() {
+  public boolean canUseCurrentChunkStatistics() throws IOException {
     Statistics chunkStatistics = currentChunkStatistics();
     return !seriesReader.isChunkOverlapped() && 
containedByTimeFilter(chunkStatistics);
   }
@@ -77,7 +77,7 @@ public class SeriesAggregateReader implements 
IAggregateReader {
   }
 
   @Override
-  public Statistics currentPageStatistics() throws IOException {
+  public Statistics currentPageStatistics() {
     return seriesReader.currentPageStatistics();
   }
 
diff --git 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesRawDataPointReader.java
 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesRawDataPointReader.java
index dd3296c..7885b94 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesRawDataPointReader.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesRawDataPointReader.java
@@ -24,6 +24,9 @@ import org.apache.iotdb.tsfile.read.reader.IPointReader;
 
 import java.io.IOException;
 
+/**
+ * only for test now
+ */
 public class SeriesRawDataPointReader implements IPointReader {
 
   private final SeriesRawDataBatchReader batchReader;
diff --git 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesReader.java
 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesReader.java
index 2c0765a..1d7825d 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesReader.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/query/reader/series/SeriesReader.java
@@ -18,25 +18,20 @@
  */
 package org.apache.iotdb.db.query.reader.series;
 
-import org.apache.iotdb.db.engine.cache.DeviceMetaDataCache;
-import org.apache.iotdb.db.engine.modification.Modification;
 import org.apache.iotdb.db.engine.querycontext.QueryDataSource;
-import org.apache.iotdb.db.engine.querycontext.ReadOnlyMemChunk;
 import org.apache.iotdb.db.engine.storagegroup.TsFileResource;
 import org.apache.iotdb.db.query.context.QueryContext;
-import org.apache.iotdb.db.query.control.FileReaderManager;
 import org.apache.iotdb.db.query.filter.TsFileFilter;
-import org.apache.iotdb.db.query.reader.chunk.DiskChunkLoader;
 import org.apache.iotdb.db.query.reader.chunk.MemChunkLoader;
 import org.apache.iotdb.db.query.reader.chunk.MemChunkReader;
 import org.apache.iotdb.db.query.reader.universal.PriorityMergeReader;
+import org.apache.iotdb.db.utils.FileLoaderUtils;
 import org.apache.iotdb.db.utils.QueryUtils;
 import org.apache.iotdb.db.utils.TestOnly;
 import org.apache.iotdb.tsfile.file.metadata.ChunkMetaData;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
 import org.apache.iotdb.tsfile.read.TimeValuePair;
-import org.apache.iotdb.tsfile.read.TsFileSequenceReader;
 import org.apache.iotdb.tsfile.read.common.BatchData;
 import org.apache.iotdb.tsfile.read.common.Chunk;
 import org.apache.iotdb.tsfile.read.common.Path;
@@ -143,7 +138,11 @@ class SeriesReader {
   }
 
 
-  boolean isChunkOverlapped() {
+  boolean isChunkOverlapped() throws IOException {
+    if (firstChunkMetaData == null) {
+      throw new IOException("no first chunk");
+    }
+
     Statistics chunkStatistics = firstChunkMetaData.getStatistics();
     return !seqChunkMetadatas.isEmpty()
         && chunkStatistics.getEndTime() >= 
seqChunkMetadatas.get(0).getStartTime()
@@ -276,6 +275,17 @@ class SeriesReader {
         firstPageStatistics.getEndTime() >= 
cachedPageReaders.peek().getStartTime();
   }
 
+  Statistics currentPageStatistics() {
+    if (firstPageReader == null) {
+      return null;
+    }
+    return firstPageReader.getStatistics();
+  }
+
+  void skipCurrentPage() {
+    firstPageReader = null;
+  }
+
   /**
    * This method should only be used when the method isPageOverlapped() return 
true.
    */
@@ -303,17 +313,6 @@ class SeriesReader {
     }
   }
 
-  Statistics currentPageStatistics() {
-    if (firstPageReader == null) {
-      return null;
-    }
-    return firstPageReader.getStatistics();
-  }
-
-  void skipCurrentPage() {
-    firstPageReader = null;
-  }
-
   /**
    * read overlapped data till currentLargestEndTime in mergeReader,
    * if current batch does not contain data, read till next 
currentLargestEndTime again
@@ -445,46 +444,6 @@ class SeriesReader {
     return chunkReader;
   }
 
-  private List<ChunkMetaData> loadSatisfiedChunkMetadatas(TsFileResource 
resource)
-      throws IOException {
-    List<ChunkMetaData> currentChunkMetaDataList;
-    if (resource == null) {
-      return new ArrayList<>();
-    }
-    if (resource.isClosed()) {
-      currentChunkMetaDataList = 
DeviceMetaDataCache.getInstance().get(resource, seriesPath);
-    } else {
-      currentChunkMetaDataList = resource.getChunkMetaDataList();
-    }
-    List<Modification> pathModifications =
-        context.getPathModifications(resource.getModFile(), 
seriesPath.getFullPath());
-
-    if (!pathModifications.isEmpty()) {
-      QueryUtils.modifyChunkMetaData(currentChunkMetaDataList, 
pathModifications);
-    }
-
-    for (ChunkMetaData chunkMetaData : currentChunkMetaDataList) {
-      TsFileSequenceReader tsFileSequenceReader = 
FileReaderManager.getInstance()
-          .get(resource, resource.isClosed());
-      chunkMetaData.setChunkLoader(new DiskChunkLoader(tsFileSequenceReader));
-    }
-    List<ReadOnlyMemChunk> memChunks = resource.getReadOnlyMemChunk();
-    if (memChunks != null) {
-      for (ReadOnlyMemChunk readOnlyMemChunk : memChunks) {
-        if (!memChunks.isEmpty()) {
-          currentChunkMetaDataList.add(readOnlyMemChunk.getChunkMetaData());
-        }
-      }
-    }
-
-    /*
-     * remove empty and not satisfied ChunkMetaData
-     */
-    currentChunkMetaDataList.removeIf(chunkMetaData -> (timeFilter != null && 
!timeFilter
-        .satisfyStartEndTime(chunkMetaData.getStartTime(), 
chunkMetaData.getEndTime()))
-        || chunkMetaData.getStartTime() > chunkMetaData.getEndTime());
-    return currentChunkMetaDataList;
-  }
 
   private PriorityQueue<TsFileResource> sortUnSeqFileResources(
       List<TsFileResource> tsFileResources) {
@@ -516,14 +475,18 @@ class SeriesReader {
      * Fill sequence chunkMetadatas until it is not empty
      */
     while (seqChunkMetadatas.isEmpty() && !seqFileResource.isEmpty()) {
-      
seqChunkMetadatas.addAll(loadSatisfiedChunkMetadatas(seqFileResource.remove(0)));
+      seqChunkMetadatas.addAll(FileLoaderUtils
+          .loadChunkMetadataFromTsFileResource(seqFileResource.remove(0), 
seriesPath, context,
+              timeFilter));
     }
 
     /*
      * Fill unsequence chunkMetadatas until it is not empty
      */
     while (unseqChunkMetadatas.isEmpty() && !unseqFileResource.isEmpty()) {
-      
unseqChunkMetadatas.addAll(loadSatisfiedChunkMetadatas(unseqFileResource.poll()));
+      unseqChunkMetadatas.addAll(FileLoaderUtils
+          .loadChunkMetadataFromTsFileResource(unseqFileResource.poll(), 
seriesPath, context,
+              timeFilter));
     }
 
     /*
@@ -555,11 +518,15 @@ class SeriesReader {
   private void unpackAllOverlappedTsFilesToChunkMetadatas(long endTime) throws 
IOException {
     while (!unseqFileResource.isEmpty() && endTime >=
         
unseqFileResource.peek().getStartTimeMap().get(seriesPath.getDevice())) {
-      
unseqChunkMetadatas.addAll(loadSatisfiedChunkMetadatas(unseqFileResource.poll()));
+      unseqChunkMetadatas.addAll(FileLoaderUtils
+          .loadChunkMetadataFromTsFileResource(unseqFileResource.poll(), 
seriesPath, context,
+              timeFilter));
     }
     while (!seqFileResource.isEmpty() && endTime >=
         seqFileResource.get(0).getStartTimeMap().get(seriesPath.getDevice())) {
-      
seqChunkMetadatas.addAll(loadSatisfiedChunkMetadatas(seqFileResource.remove(0)));
+      seqChunkMetadatas.addAll(FileLoaderUtils
+          .loadChunkMetadataFromTsFileResource(seqFileResource.remove(0), 
seriesPath, context,
+              timeFilter));
     }
   }
 
diff --git 
a/server/src/main/java/org/apache/iotdb/db/utils/FileLoaderUtils.java 
b/server/src/main/java/org/apache/iotdb/db/utils/FileLoaderUtils.java
index 671e5d4..7b93c94 100644
--- a/server/src/main/java/org/apache/iotdb/db/utils/FileLoaderUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/utils/FileLoaderUtils.java
@@ -35,6 +35,7 @@ import 
org.apache.iotdb.tsfile.file.metadata.TsDeviceMetadataIndex;
 import org.apache.iotdb.tsfile.file.metadata.TsFileMetaData;
 import org.apache.iotdb.tsfile.read.TsFileSequenceReader;
 import org.apache.iotdb.tsfile.read.common.Path;
+import org.apache.iotdb.tsfile.read.filter.basic.Filter;
 
 public class FileLoaderUtils {
 
@@ -73,25 +74,44 @@ public class FileLoaderUtils {
     }
   }
 
+  /**
+   * load all ChunkMetadatas belong to the seriesPath and satisfy filter
+   */
+  public static List<ChunkMetaData> loadChunkMetadataFromTsFileResource(
+      TsFileResource resource, Path seriesPath, QueryContext context, Filter 
timeFilter) throws IOException {
+    List<ChunkMetaData> chunkMetaDataList = 
loadChunkMetadataFromTsFileResource(resource, seriesPath, context);
+
+    /*
+     * remove not satisfied ChunkMetaData
+     */
+    chunkMetaDataList.removeIf(chunkMetaData -> (timeFilter != null && 
!timeFilter
+        .satisfyStartEndTime(chunkMetaData.getStartTime(), 
chunkMetaData.getEndTime()))
+        || chunkMetaData.getStartTime() > chunkMetaData.getEndTime());
+    return chunkMetaDataList;
+  }
+
+  /**
+   * load all ChunkMetadatas belong to the seriesPath
+   */
   public static List<ChunkMetaData> loadChunkMetadataFromTsFileResource(
       TsFileResource resource, Path seriesPath, QueryContext context) throws 
IOException {
-    List<ChunkMetaData> currentChunkMetaDataList;
+    List<ChunkMetaData> chunkMetaDataList;
     if (resource == null) {
       return new ArrayList<>();
     }
     if (resource.isClosed()) {
-      currentChunkMetaDataList = 
DeviceMetaDataCache.getInstance().get(resource, seriesPath);
+      chunkMetaDataList = DeviceMetaDataCache.getInstance().get(resource, 
seriesPath);
     } else {
-      currentChunkMetaDataList = resource.getChunkMetaDataList();
+      chunkMetaDataList = resource.getChunkMetaDataList();
     }
     List<Modification> pathModifications =
         context.getPathModifications(resource.getModFile(), 
seriesPath.getFullPath());
 
     if (!pathModifications.isEmpty()) {
-      QueryUtils.modifyChunkMetaData(currentChunkMetaDataList, 
pathModifications);
+      QueryUtils.modifyChunkMetaData(chunkMetaDataList, pathModifications);
     }
 
-    for (ChunkMetaData data : currentChunkMetaDataList) {
+    for (ChunkMetaData data : chunkMetaDataList) {
       TsFileSequenceReader tsFileSequenceReader =
           FileReaderManager.getInstance().get(resource, resource.isClosed());
       data.setChunkLoader(new DiskChunkLoader(tsFileSequenceReader));
@@ -100,10 +120,16 @@ public class FileLoaderUtils {
     if (memChunks != null) {
       for (ReadOnlyMemChunk readOnlyMemChunk : memChunks) {
         if (!memChunks.isEmpty()) {
-          currentChunkMetaDataList.add(readOnlyMemChunk.getChunkMetaData());
+          chunkMetaDataList.add(readOnlyMemChunk.getChunkMetaData());
         }
       }
     }
-    return currentChunkMetaDataList;
+
+    /*
+     * remove empty or invalid chunk metadata
+     */
+    chunkMetaDataList.removeIf(chunkMetaData -> (
+        chunkMetaData.getStartTime() > chunkMetaData.getEndTime()));
+    return chunkMetaDataList;
   }
 }

Reply via email to