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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2146a080230 fix: Fix ShowTimeseries estimated offset without limit 
scenario (#17116)
2146a080230 is described below

commit 2146a080230225817e630380b36b7238ff31d9d8
Author: Zhenyu Luo <[email protected]>
AuthorDate: Tue Feb 3 12:00:36 2026 +0800

    fix: Fix ShowTimeseries estimated offset without limit scenario (#17116)
    
    * fix: Fix ShowTimeseries estimated offset without limit scenario
    
    When ShowTimeseries has offset but no limit, the limit calculation
    was incorrectly adding offset to 0, causing incorrect behavior.
    This commit fixes:
    1. LogicalPlanVisitor: When limit is 0, keep it as 0 instead of
       adding offset
    2. SchemaReaderLimitOffsetWrapper: When limit is 0, allow all
       results to pass through (limit == 0 means no limit)
    
    * fix
    
    * fix
    
    * fix
    
    * add it
---
 .../org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java | 244 +++++++++++++++++++++
 .../plan/planner/LogicalPlanVisitor.java           |  15 +-
 .../impl/SchemaReaderLimitOffsetWrapper.java       |   2 +-
 3 files changed, 257 insertions(+), 4 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
index 9cdd1cc3ce9..1d5765deafd 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java
@@ -698,6 +698,250 @@ public class IoTDBSimpleQueryIT {
     }
   }
 
+  @Test
+  public void testShowTimeseriesWithOffsetOnly() throws SQLException {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+
+      // Test Case 1: Single Region scenario (single database, single device)
+      // Create first database and write data to single device to ensure 
single Region
+      statement.execute("CREATE DATABASE root.db1");
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s1) VALUES (1, 
1)");
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s2) VALUES (1, 
2)");
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s3) VALUES (1, 
3)");
+      statement.execute("flush");
+
+      // Verify single Region scenario - offset only
+      List<String> sg1AllPaths = new ArrayList<>();
+      try (ResultSet resultSet = statement.executeQuery("show timeseries 
root.db1.**")) {
+        while (resultSet.next()) {
+          sg1AllPaths.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(3, sg1AllPaths.size());
+
+      int offset1 = 2;
+      List<String> sg1ResultPaths = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show timeseries root.db1.** offset " + 
offset1)) {
+        while (resultSet.next()) {
+          sg1ResultPaths.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(sg1AllPaths.size() - offset1, sg1ResultPaths.size());
+      for (int i = 0; i < sg1ResultPaths.size(); i++) {
+        Assert.assertEquals(sg1AllPaths.get(offset1 + i), 
sg1ResultPaths.get(i));
+      }
+
+      // Test Case 2: Single Region - different path patterns with offset
+      List<String> d0Paths = new ArrayList<>();
+      try (ResultSet resultSet = statement.executeQuery("show timeseries 
root.db1.d0.**")) {
+        while (resultSet.next()) {
+          d0Paths.add(resultSet.getString(1));
+        }
+      }
+
+      int offset2 = 1;
+      List<String> d0ResultPaths = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show timeseries root.db1.d0.** offset " + 
offset2)) {
+        while (resultSet.next()) {
+          d0ResultPaths.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(d0Paths.size() - offset2, d0ResultPaths.size());
+      for (int i = 0; i < d0ResultPaths.size(); i++) {
+        Assert.assertEquals(d0Paths.get(offset2 + i), d0ResultPaths.get(i));
+      }
+
+      // Test Case 3: Single Region - with time condition and offset
+      // Insert data with different timestamps
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s4) VALUES (10, 
10)");
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s5) VALUES (20, 
20)");
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s6) VALUES (30, 
30)");
+      statement.execute("flush");
+
+      // Get all timeseries with time condition
+      List<String> timeFilteredPaths = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show timeseries root.db1.** where time > 
5")) {
+        while (resultSet.next()) {
+          timeFilteredPaths.add(resultSet.getString(1));
+        }
+      }
+
+      // Test offset with time condition in single Region
+      int offset3 = 1;
+      List<String> timeFilteredResultPaths = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show timeseries root.db1.** where time > 5 
offset " + offset3)) {
+        while (resultSet.next()) {
+          timeFilteredResultPaths.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(timeFilteredPaths.size() - offset3, 
timeFilteredResultPaths.size());
+      for (int i = 0; i < timeFilteredResultPaths.size(); i++) {
+        Assert.assertEquals(timeFilteredPaths.get(offset3 + i), 
timeFilteredResultPaths.get(i));
+      }
+
+      // Test Case 4: Multiple Regions scenario (multiple databases)
+      // Create second database to test multi-Region scenario
+      statement.execute("CREATE DATABASE root.db2");
+      statement.execute("INSERT INTO root.db2.d0(timestamp, s1) VALUES (4, 
7)");
+      statement.execute("INSERT INTO root.db2.d0(timestamp, s2) VALUES (4, 
8)");
+      statement.execute("INSERT INTO root.db2.d1(timestamp, s1) VALUES (5, 
9)");
+      statement.execute("flush");
+
+      // Test across multiple databases - offset only
+      List<String> allPaths = new ArrayList<>();
+      try (ResultSet resultSet = statement.executeQuery("show timeseries 
root.db*.**")) {
+        while (resultSet.next()) {
+          allPaths.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(9, allPaths.size());
+
+      int offset4 = 3;
+      List<String> resultPaths = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show timeseries root.db*.** offset " + 
offset4)) {
+        while (resultSet.next()) {
+          resultPaths.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(allPaths.size() - offset4, resultPaths.size());
+      for (int i = 0; i < resultPaths.size(); i++) {
+        Assert.assertEquals(allPaths.get(offset4 + i), resultPaths.get(i));
+      }
+
+      // Test Case 5: Edge case - offset equals or exceeds total count
+      int largeOffset = allPaths.size() + 10;
+      int count = 0;
+      try (ResultSet resultSet = statement.executeQuery("show timeseries 
offset " + largeOffset)) {
+        while (resultSet.next()) {
+          count++;
+        }
+      }
+      Assert.assertEquals("Should return 0 results when offset exceeds total 
count", 0, count);
+    }
+  }
+
+  @Test
+  public void testShowDevicesWithOffsetOnly() throws SQLException {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+
+      statement.execute("CREATE DATABASE root.db1");
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s1) VALUES (1, 
1)");
+      statement.execute("INSERT INTO root.db1.d0(timestamp, s2) VALUES (1, 
2)");
+      statement.execute("INSERT INTO root.db1.d1(timestamp, s3) VALUES (1, 
3)");
+      statement.execute("flush");
+
+      List<String> sg1AllDevices = new ArrayList<>();
+      try (ResultSet resultSet = statement.executeQuery("show devices 
root.db1.**")) {
+        while (resultSet.next()) {
+          sg1AllDevices.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(2, sg1AllDevices.size());
+
+      int offset1 = 1;
+      List<String> sg1ResultDevices = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show devices root.db1.** offset " + 
offset1)) {
+        while (resultSet.next()) {
+          sg1ResultDevices.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(sg1AllDevices.size() - offset1, 
sg1ResultDevices.size());
+      for (int i = 0; i < sg1ResultDevices.size(); i++) {
+        Assert.assertEquals(sg1AllDevices.get(offset1 + i), 
sg1ResultDevices.get(i));
+      }
+
+      List<String> d0Devices = new ArrayList<>();
+      try (ResultSet resultSet = statement.executeQuery("show devices 
root.db1.d0.**")) {
+        while (resultSet.next()) {
+          d0Devices.add(resultSet.getString(1));
+        }
+      }
+
+      int offset2 = 0;
+      List<String> d0ResultDevices = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show devices root.db1.d0.** offset " + 
offset2)) {
+        while (resultSet.next()) {
+          d0ResultDevices.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(d0Devices.size() - offset2, d0ResultDevices.size());
+      for (int i = 0; i < d0ResultDevices.size(); i++) {
+        Assert.assertEquals(d0Devices.get(offset2 + i), 
d0ResultDevices.get(i));
+      }
+
+      statement.execute("INSERT INTO root.db1.d2(timestamp, s4) VALUES (10, 
10)");
+      statement.execute("INSERT INTO root.db1.d3(timestamp, s5) VALUES (20, 
20)");
+      statement.execute("INSERT INTO root.db1.d4(timestamp, s6) VALUES (30, 
30)");
+      statement.execute("flush");
+
+      List<String> timeFilteredDevices = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show devices root.db1.** where time > 5")) {
+        while (resultSet.next()) {
+          timeFilteredDevices.add(resultSet.getString(1));
+        }
+      }
+
+      int offset3 = 1;
+      List<String> timeFilteredResultDevices = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show devices root.db1.** where time > 5 
offset " + offset3)) {
+        while (resultSet.next()) {
+          timeFilteredResultDevices.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(timeFilteredDevices.size() - offset3, 
timeFilteredResultDevices.size());
+      for (int i = 0; i < timeFilteredResultDevices.size(); i++) {
+        Assert.assertEquals(timeFilteredDevices.get(offset3 + i), 
timeFilteredResultDevices.get(i));
+      }
+
+      statement.execute("CREATE DATABASE root.db2");
+      statement.execute("INSERT INTO root.db2.d0(timestamp, s1) VALUES (4, 
7)");
+      statement.execute("INSERT INTO root.db2.d1(timestamp, s2) VALUES (4, 
8)");
+      statement.execute("INSERT INTO root.db2.d2(timestamp, s1) VALUES (5, 
9)");
+      statement.execute("flush");
+
+      List<String> allDevices = new ArrayList<>();
+      try (ResultSet resultSet = statement.executeQuery("show devices 
root.db*.**")) {
+        while (resultSet.next()) {
+          allDevices.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(8, allDevices.size());
+
+      int offset4 = 3;
+      List<String> resultDevices = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery("show devices root.db*.** offset " + 
offset4)) {
+        while (resultSet.next()) {
+          resultDevices.add(resultSet.getString(1));
+        }
+      }
+      Assert.assertEquals(allDevices.size() - offset4, resultDevices.size());
+      for (int i = 0; i < resultDevices.size(); i++) {
+        Assert.assertEquals(allDevices.get(offset4 + i), resultDevices.get(i));
+      }
+
+      int largeOffset = allDevices.size() + 10;
+      int count = 0;
+      try (ResultSet resultSet = statement.executeQuery("show devices offset " 
+ largeOffset)) {
+        while (resultSet.next()) {
+          count++;
+        }
+      }
+      Assert.assertEquals("Should return 0 results when offset exceeds total 
count", 0, count);
+    }
+  }
+
   @Test
   public void testShowDevicesWithLimitOffset() throws SQLException {
     try (Connection connection = EnvFactory.getEnv().getConnection();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
index c03be7e1bcc..ea7be0c2c33 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
@@ -579,7 +579,10 @@ public class LogicalPlanVisitor extends 
StatementVisitor<PlanNode, MPPQueryConte
       limit = 0;
       offset = 0;
     } else if (!canPushDownOffsetLimit) {
-      limit = showTimeSeriesStatement.getLimit() + 
showTimeSeriesStatement.getOffset();
+      limit =
+          showTimeSeriesStatement.getLimit() != 0
+              ? showTimeSeriesStatement.getLimit() + 
showTimeSeriesStatement.getOffset()
+              : 0;
       offset = 0;
     }
     planBuilder =
@@ -637,7 +640,10 @@ public class LogicalPlanVisitor extends 
StatementVisitor<PlanNode, MPPQueryConte
     long limit = showDevicesStatement.getLimit();
     long offset = showDevicesStatement.getOffset();
     if (!canPushDownOffsetLimit) {
-      limit = showDevicesStatement.getLimit() + 
showDevicesStatement.getOffset();
+      limit =
+          showDevicesStatement.getLimit() != 0
+              ? showDevicesStatement.getLimit() + 
showDevicesStatement.getOffset()
+              : 0;
       offset = 0;
     }
 
@@ -1009,7 +1015,10 @@ public class LogicalPlanVisitor extends 
StatementVisitor<PlanNode, MPPQueryConte
     long limit = showLogicalViewStatement.getLimit();
     long offset = showLogicalViewStatement.getOffset();
     if (!canPushDownOffsetLimit) {
-      limit = showLogicalViewStatement.getLimit() + 
showLogicalViewStatement.getOffset();
+      limit =
+          showLogicalViewStatement.getLimit() != 0
+              ? showLogicalViewStatement.getLimit() + 
showLogicalViewStatement.getOffset()
+              : 0;
       offset = 0;
     }
     planBuilder =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/read/resp/reader/impl/SchemaReaderLimitOffsetWrapper.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/read/resp/reader/impl/SchemaReaderLimitOffsetWrapper.java
index 3d9d6abc54e..f35d047753f 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/read/resp/reader/impl/SchemaReaderLimitOffsetWrapper.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/read/resp/reader/impl/SchemaReaderLimitOffsetWrapper.java
@@ -101,7 +101,7 @@ public class SchemaReaderLimitOffsetWrapper<T extends 
ISchemaInfo> implements IS
   public boolean hasNext() {
     try {
       isBlocked().get();
-      return schemaReader.hasNext() && count < limit;
+      return schemaReader.hasNext() && (limit == 0 || count < limit);
     } catch (Exception e) {
       throw new RuntimeException(e);
     }

Reply via email to