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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 816dcb641 fix(tencent): page the message query by consumed rows, not 
collected records (#6518)
816dcb641 is described below

commit 816dcb6416f2d7fb7f56db61dd746dd77d7655ff
Author: Apulupie <[email protected]>
AuthorDate: Thu Oct 15 18:40:02 2026 +0800

    fix(tencent): page the message query by consumed rows, not collected 
records (#6518)
    
    `queryMessagesDetailed` derived the next page offset from the records it 
had collected
    (`request.setOffset((long) result.size())`) instead of the rows the API had 
already
    returned. The SDK maps a JSON null array element to a null element and the 
loop skips it,
    so every skipped entry made the next offset one row too small: the API 
re-returned the
    tail of the page, the result contained duplicate msgIds, and 
`isLastPage(..., result.size())`
    made the completion check drift by the same amount. The Aliyun sibling 
counts raw
    provider rows in its paging bookkeeping, and this file already guards null 
array elements
    in three places.
    
    A `fetchedRows` counter now grows by `returned` on every page and feeds 
both the next
    offset and the last-page check; `result.size()` keeps serving only the 
result budget. The
    duplicated short-page comment above `isLastPage` is removed while editing 
those lines.
---
 .../provider/tencent/TencentInstanceProvider.java  | 17 ++++++----
 .../tencent/TencentInstanceProviderTest.java       | 36 ++++++++++++++++++++++
 2 files changed, 47 insertions(+), 6 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
index cd363ace1..c3d0d7632 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
@@ -642,6 +642,10 @@ public class TencentInstanceProvider implements 
InstanceProvider {
         // is not server-paginated (pagination=false), so page through the 
whole result set here.
         String taskRequestId = UUID.randomUUID().toString();
         List<MessageRecordVO> result = new ArrayList<>();
+        // Rows the API has already returned, which is what the next offset 
must be derived from:
+        // a page can carry entries the provider skips (the SDK maps a JSON 
null array element to a
+        // null element), so the number of collected records is not the number 
of consumed rows.
+        long fetchedRows = 0L;
         boolean mayBeTruncated = false;
         for (int page = 0; page < MAX_PAGES && result.size() < 
MESSAGE_QUERY_HARD_LIMIT; page++) {
             DescribeMessageListRequest request = new 
DescribeMessageListRequest();
@@ -658,7 +662,7 @@ public class TencentInstanceProvider implements 
InstanceProvider {
             if (StringUtils.hasText(tag)) {
                 request.setTag(tag);
             }
-            request.setOffset((long) result.size());
+            request.setOffset(fetchedRows);
             request.setLimit((long) MESSAGE_LIMIT);
             DescribeMessageListResponse response = 
clientFactory.call(context.credentialId(), context.regionId(),
                     client -> client.DescribeMessageList(request));
@@ -682,8 +686,9 @@ public class TencentInstanceProvider implements 
InstanceProvider {
                 mayBeTruncated = true;
                 break;
             }
+            fetchedRows += returned;
             // Missing totals retain the existing short-page termination 
fallback.
-            if (isLastPage(returned, total, result.size())) {
+            if (isLastPage(returned, total, fetchedRows)) {
                 break;
             }
             if (result.size() >= MESSAGE_QUERY_HARD_LIMIT) {
@@ -899,12 +904,12 @@ public class TencentInstanceProvider implements 
InstanceProvider {
         }
     }
 
-    private static boolean isLastPage(int returned, long total, int collected) 
{
+    private static boolean isLastPage(int returned, long total, long 
fetchedRows) {
         // The last page is one that returned no rows or fewer rows than 
requested. When the API
-        // reports a TotalCount, also stop once all expected results have been 
collected.
+        // reports a TotalCount, also stop once all expected rows have been 
fetched.
         boolean shortPage = returned == 0 || returned < MESSAGE_LIMIT;
-        boolean allCollected = total > 0 && collected >= total;
-        return shortPage || allCollected;
+        boolean allFetched = total > 0 && fetchedRows >= total;
+        return shortPage || allFetched;
     }
 
     private static long parseTraceTime(String value) {
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
index 02598f59f..5ef54fb12 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
@@ -1210,6 +1210,42 @@ class TencentInstanceProviderTest {
                 .DescribeMessageList(any());
     }
 
+    @Test
+    void 
queryMessagesShouldAdvanceByConsumedRowsWhenAPageSkipsAnAbsentEntryTest() 
throws Exception {
+        // The SDK maps a JSON null array element to a null element and the 
provider skips it, but
+        // the next page must still start after every row the API already 
returned.
+        when(client.DescribeMessageList(any())).thenAnswer(invocation -> {
+            DescribeMessageListRequest request = invocation.getArgument(0);
+            int start = request.getOffset().intValue();
+            int size = Math.min(request.getLimit().intValue(), Math.max(150 - 
start, 0));
+            MessageItem[] items = new MessageItem[size];
+            for (int i = 0; i < size; i++) {
+                MessageItem item = new MessageItem();
+                item.setMsgId("MSG-" + (start + i + 1));
+                item.setProduceTime("2024-09-12 14:06:55,591");
+                items[i] = item;
+            }
+            if (start == 0) {
+                items[50] = null;
+            }
+            DescribeMessageListResponse response = new 
DescribeMessageListResponse();
+            response.setData(items);
+            response.setTotalCount(150L);
+            return response;
+        });
+
+        List<MessageRecordVO> messages = 
provider.queryMessages(STUDIO_INSTANCE_ID, "orders", null,
+                null, null, 1600000000000L, 1600001000000L);
+
+        ArgumentCaptor<DescribeMessageListRequest> captor = 
ArgumentCaptor.forClass(DescribeMessageListRequest.class);
+        verify(client, 
org.mockito.Mockito.times(2)).DescribeMessageList(captor.capture());
+        assertThat(captor.getAllValues().get(0).getOffset()).isZero();
+        assertThat(captor.getAllValues().get(1).getOffset())
+                .isEqualTo((long) TencentInstanceProvider.MESSAGE_LIMIT);
+        
assertThat(messages).extracting(MessageRecordVO::getMsgId).doesNotHaveDuplicates();
+        assertThat(messages).hasSize(149);
+    }
+
     @Test
     void queryMessagesShouldReportCompleteWhenTencentReturnsAShortPage() 
throws Exception {
         MessageItem one = new MessageItem();

Reply via email to