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);
}