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 e6c2a7fe1 fix(metadata): scope persisted topic/group identity by 
instance (#2366)
e6c2a7fe1 is described below

commit e6c2a7fe1a944fccdeeac64ba6bb60edaf3c5676
Author: aias00 <[email protected]>
AuthorDate: Wed Aug 19 14:56:27 2026 +0800

    fix(metadata): scope persisted topic/group identity by instance (#2366)
    
    Constraint: Existing topic/group records were keyed by cluster plus name, 
so same-name resources under different Studio instances could overwrite or 
delete each other.
    Rejected: Keep nullable instance_id inside the new unique key | MySQL 
unique indexes still allow duplicate NULL scopes, so legacy rows would remain 
ambiguous.
    Directive: Persist and query topic/group metadata with the (cluster_id, 
instance_id, name) identity; use the empty-string legacy scope only for old 
unassigned rows.
    Confidence: high
    Scope-risk: moderate
    Tested: 
JAVA_HOME=/Users/aias/Library/Java/JavaVirtualMachines/openjdk-21.0.2/Contents/Home
 mvn 
-Dtest=RocketMQAdminClientImplTest,RocketMQMetadataProviderTest,ApacheInstanceProviderTest,MybatisPlusInstanceRepositoryTest
 test
    
    Signed-off-by: liuhy <[email protected]>
---
 .../studio/instance/InstanceRepository.java        |  4 +--
 .../instance/MybatisPlusInstanceRepository.java    |  4 +--
 .../provider/apache/ApacheInstanceProvider.java    | 17 ++-------
 .../studio/provider/apache/MetadataProvider.java   |  4 +++
 .../provider/apache/RocketMQAdminClientImpl.java   | 41 +++++++++++++---------
 .../provider/apache/RocketMQMetadataProvider.java  | 14 ++++++--
 server/src/main/resources/db/schema.sql            |  8 ++---
 .../MybatisPlusInstanceRepositoryTest.java         |  4 +--
 .../apache/ApacheInstanceProviderTest.java         | 13 +++++--
 .../apache/RocketMQAdminClientImplTest.java        | 13 ++++---
 .../apache/RocketMQMetadataProviderTest.java       | 34 ++++++++++++++++++
 11 files changed, 107 insertions(+), 49 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java
index a4e8ce716..89173ce3a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceRepository.java
@@ -60,7 +60,7 @@ public interface InstanceRepository {
 
     boolean existsByCredentialId(Long credentialId);
 
-    long countTopicsByInstance(Long instanceId);
+    long countTopicsByInstance(String instanceId);
 
-    long countGroupsByInstance(Long instanceId);
+    long countGroupsByInstance(String instanceId);
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
index 0d49534ae..422e8643a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
@@ -152,13 +152,13 @@ public class MybatisPlusInstanceRepository implements 
InstanceRepository {
     }
 
     @Override
-    public long countTopicsByInstance(Long instanceId) {
+    public long countTopicsByInstance(String instanceId) {
         return topicMapper.selectCount(
                 new QueryWrapper<RmqTopic>().eq("instance_id", instanceId));
     }
 
     @Override
-    public long countGroupsByInstance(Long instanceId) {
+    public long countGroupsByInstance(String instanceId) {
         return groupMapper.selectCount(
                 new QueryWrapper<RmqGroup>().eq("instance_id", instanceId));
     }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
index 6c4147ecb..27b1fc909 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
@@ -34,7 +34,6 @@ import org.springframework.stereotype.Component;
 import lombok.RequiredArgsConstructor;
 
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -68,24 +67,21 @@ public class ApacheInstanceProvider implements 
InstanceProvider {
 
     @Override
     public int countTopics(String instanceId) {
-        // The instance_id foreign key is numeric; resolve the external 
identifier first.
         return instanceRepository.findByIdentifier(instanceId)
-                .map(instance -> (int) 
instanceRepository.countTopicsByInstance(instance.getId()))
+                .map(instance -> (int) 
instanceRepository.countTopicsByInstance(instance.getName()))
                 .orElse(0);
     }
 
     @Override
     public int countGroups(String instanceId) {
         return instanceRepository.findByIdentifier(instanceId)
-                .map(instance -> (int) 
instanceRepository.countGroupsByInstance(instance.getId()))
+                .map(instance -> (int) 
instanceRepository.countGroupsByInstance(instance.getName()))
                 .orElse(0);
     }
 
     @Override
     public List<TopicVO> listTopics(String instanceId, String type, String 
search) {
-        return metadataProvider.listTopics(null, type, search).stream()
-                .filter(topic -> matchesInstance(topic.getInstanceId(), 
instanceId))
-                .toList();
+        return metadataProvider.listTopics(instanceId, null, type, search);
     }
 
     @Override
@@ -159,11 +155,4 @@ public class ApacheInstanceProvider implements 
InstanceProvider {
     public TraceRecordVO getMessageTrace(String instanceId, String msgId, 
String topic) {
         return messageProvider.getMessageTrace(instanceId, msgId, topic);
     }
-
-    private boolean matchesInstance(String topicInstanceId, String instanceId) 
{
-        if (instanceId == null || instanceId.isBlank()) {
-            return true;
-        }
-        return Objects.equals(topicInstanceId, instanceId);
-    }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
index 181267c8f..7e16d005e 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
@@ -31,6 +31,10 @@ import java.util.List;
 public interface MetadataProvider {
     List<TopicVO> listTopics(String clusterId, String type, String search);
 
+    default List<TopicVO> listTopics(String instanceId, String clusterId, 
String type, String search) {
+        return listTopics(clusterId, type, search);
+    }
+
     default PageResult<TopicVO> listTopicsPage(String clusterId, String type, 
String search,
             int page, int pageSize) {
         List<TopicVO> topics = listTopics(clusterId, type, search);
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
index 2eefa9c7a..a16e8b849 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
@@ -70,6 +70,7 @@ public class RocketMQAdminClientImpl implements AdminClient {
 
     private static final String MESSAGE_SENDER_GROUP_PREFIX = 
"studio-msg-sender";
     private static final int MAX_MESSAGE_SIZE = 4 * 1024 * 1024; // 4 MB 
default broker limit
+    private static final String LEGACY_METADATA_SCOPE = "";
 
     private final MqAdminExtFactory adminFactory;
     private final RocketMQProperties properties;
@@ -142,11 +143,12 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
         return executeForInstance(topic.getInstanceId(), admin -> {
             try {
                 String clusterName = getClusterName(admin);
-                // Match on the (cluster_id, name) key: the same topic name 
can exist in several
-                // clusters, and a name-only lookup would blow up with 
TooManyResultsException.
+                // Match on the instance-scoped identity: the same topic name 
can exist in several
+                // clusters or Studio instances, and a broader lookup would 
corrupt metadata.
                 RmqTopic existing = topicMapper.selectOne(
                         new LambdaQueryWrapper<RmqTopic>()
                                 .eq(RmqTopic::getClusterId, clusterName)
+                                .eq(RmqTopic::getInstanceId, 
metadataScope(topic.getInstanceId()))
                                 .eq(RmqTopic::getName, topicName));
                 TopicPerm effectivePerm = topic.getPerm() != null
                         ? topic.getPerm()
@@ -169,20 +171,20 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
 
                 // Persist to DB. Re-creating a topic that already has a 
record (for example when
                 // rebuilding a broker route from the console) must update it 
instead of failing on
-                // the unique (cluster_id, name) key.
+                // the instance-scoped unique key.
                 RmqTopic entity = topicMapper.selectOne(new 
LambdaQueryWrapper<RmqTopic>()
                         .eq(RmqTopic::getClusterId, clusterName)
+                        .eq(RmqTopic::getInstanceId, 
metadataScope(topic.getInstanceId()))
                         .eq(RmqTopic::getName, topicName));
                 boolean isNew = entity == null;
                 if (isNew) {
                     entity = new RmqTopic();
                     entity.setName(topicName);
                     entity.setClusterId(clusterName);
+                    entity.setInstanceId(metadataScope(topic.getInstanceId()));
                     entity.setGmtCreate(LocalDateTime.now());
                 }
-                if (topic.getInstanceId() != null) {
-                    entity.setInstanceId(topic.getInstanceId());
-                }
+                entity.setInstanceId(metadataScope(topic.getInstanceId()));
                 if (topic.getType() != null) {
                     entity.setTopicType(topic.getType().name());
                 } else if (isNew) {
@@ -225,12 +227,13 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
 
         return executeForInstance(topic.getInstanceId(), admin -> {
             try {
-                // Match on the (cluster_id, name) key to avoid ambiguity when 
several clusters share
-                // the same topic name (a name-only lookup would throw 
TooManyResultsException).
+                // Match on the instance-scoped identity so same-name 
resources under different
+                // Studio instances do not overwrite each other.
                 String clusterName = getClusterName(admin);
                 RmqTopic existing = topicMapper.selectOne(
                         new LambdaQueryWrapper<RmqTopic>()
                                 .eq(RmqTopic::getClusterId, clusterName)
+                                .eq(RmqTopic::getInstanceId, 
metadataScope(topic.getInstanceId()))
                                 .eq(RmqTopic::getName, topicName));
                 // Preserve the existing queue counts when the update request 
does not change them,
                 // matching the perm semantics below; defaulting to 8 would 
silently resize the
@@ -329,6 +332,7 @@ public class RocketMQAdminClientImpl implements AdminClient 
{
                 // Topic names may be shared by several clusters managed by 
this Studio instance.
                 topicMapper.delete(new LambdaQueryWrapper<RmqTopic>()
                         .eq(RmqTopic::getClusterId, clusterName)
+                        .eq(RmqTopic::getInstanceId, metadataScope(instanceId))
                         .eq(RmqTopic::getName, name));
 
                 recordAudit("DELETE_TOPIC", name, "", "SUCCESS");
@@ -437,21 +441,21 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
                 admin.createAndUpdateSubscriptionGroupConfig(addr, config);
             }
 
-            // Persist to DB, upserting so re-creating an existing group does 
not violate the
-            // unique (cluster_id, name) key.
+            // Persist to DB, upserting so re-creating an existing group stays 
scoped to the
+            // selected Studio instance.
             RmqGroup entity = groupMapper.selectOne(new 
LambdaQueryWrapper<RmqGroup>()
                     .eq(RmqGroup::getClusterId, groupClusterName)
+                    .eq(RmqGroup::getInstanceId, 
metadataScope(group.getInstanceId()))
                     .eq(RmqGroup::getName, groupName));
             boolean isNewGroup = entity == null;
             if (isNewGroup) {
                 entity = new RmqGroup();
                 entity.setName(groupName);
                 entity.setClusterId(groupClusterName);
+                entity.setInstanceId(metadataScope(group.getInstanceId()));
                 entity.setGmtCreate(LocalDateTime.now());
             }
-            if (group.getInstanceId() != null) {
-                entity.setInstanceId(group.getInstanceId());
-            }
+            entity.setInstanceId(metadataScope(group.getInstanceId()));
             entity.setConsumeType(group.getConsumeType() != null ? 
group.getConsumeType().name() : "CLUSTERING");
             entity.setMessageModel(group.getSubscriptionMode() != null ? 
group.getSubscriptionMode().name() : "Push");
             entity.setMaxRetry(config.getRetryMaxTimes());
@@ -481,18 +485,18 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
     public void deleteConsumerGroup(String instanceId, String name) {
         if (StringUtils.hasText(instanceId)) {
             runtimeAdminClientResolver.execute(instanceId, admin -> {
-                doDeleteConsumerGroup(admin, name);
+                doDeleteConsumerGroup(instanceId, admin, name);
                 return null;
             });
             return;
         }
         adminFactory.execute(namesrvAddr(), null, admin -> {
-            doDeleteConsumerGroup(admin, name);
+            doDeleteConsumerGroup(null, admin, name);
             return null;
         });
     }
 
-    private void doDeleteConsumerGroup(MQAdminExt admin, String name) {
+    private void doDeleteConsumerGroup(String instanceId, MQAdminExt admin, 
String name) {
         try {
             String clusterName = getClusterName(admin);
             Set<String> brokerAddrs = getMasterBrokerAddrsForCluster(admin, 
clusterName);
@@ -504,6 +508,7 @@ public class RocketMQAdminClientImpl implements AdminClient 
{
             // Consumer group names may be shared by several clusters managed 
by this Studio instance.
             groupMapper.delete(new LambdaQueryWrapper<RmqGroup>()
                     .eq(RmqGroup::getClusterId, clusterName)
+                    .eq(RmqGroup::getInstanceId, metadataScope(instanceId))
                     .eq(RmqGroup::getName, name));
 
             recordAudit("DELETE_GROUP", name, "", "SUCCESS");
@@ -613,6 +618,10 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
         return adminFactory.execute(namesrvAddr(), null, action);
     }
 
+    private String metadataScope(String instanceId) {
+        return StringUtils.hasText(instanceId) ? instanceId.trim() : 
LEGACY_METADATA_SCOPE;
+    }
+
     private Set<String> getAllMasterBrokerAddrs(MQAdminExt admin) throws 
Exception {
         Set<String> addrs = new HashSet<>();
         ClusterInfo clusterInfo = admin.examineBrokerClusterInfo();
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
index df46661ed..48bfdc935 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
@@ -116,7 +116,13 @@ public class RocketMQMetadataProvider implements 
MetadataProvider {
 
     @Override
     public List<TopicVO> listTopics(String clusterId, String type, String 
search) {
+        return listTopics(null, clusterId, type, search);
+    }
+
+    @Override
+    public List<TopicVO> listTopics(String instanceId, String clusterId, 
String type, String search) {
         LambdaQueryWrapper<RmqTopic> query = new LambdaQueryWrapper<RmqTopic>()
+                .eq(instanceId != null, RmqTopic::getInstanceId, 
normalizeMetadataScope(instanceId))
                 .eq(StringUtils.hasText(clusterId), RmqTopic::getClusterId, 
clusterId)
                 .eq(StringUtils.hasText(type), RmqTopic::getTopicType, type)
                 .like(StringUtils.hasText(search), RmqTopic::getName, search)
@@ -136,7 +142,7 @@ public class RocketMQMetadataProvider implements 
MetadataProvider {
     public PageResult<TopicVO> listTopicsPage(String instanceId, String 
clusterId, String type,
             String search, int page, int pageSize) {
         LambdaQueryWrapper<RmqTopic> query = new LambdaQueryWrapper<RmqTopic>()
-                .eq(StringUtils.hasText(instanceId), RmqTopic::getInstanceId, 
instanceId)
+                .eq(instanceId != null, RmqTopic::getInstanceId, 
normalizeMetadataScope(instanceId))
                 .eq(StringUtils.hasText(clusterId), RmqTopic::getClusterId, 
clusterId)
                 .eq(StringUtils.hasText(type), RmqTopic::getTopicType, type)
                 .like(StringUtils.hasText(search), RmqTopic::getName, search)
@@ -195,7 +201,7 @@ public class RocketMQMetadataProvider implements 
MetadataProvider {
     @Override
     public List<ConsumerGroupVO> listConsumerGroups(String instanceId, String 
clusterId, String search) {
         LambdaQueryWrapper<RmqGroup> query = new LambdaQueryWrapper<RmqGroup>()
-                .eq(StringUtils.hasText(instanceId), RmqGroup::getInstanceId, 
instanceId)
+                .eq(instanceId != null, RmqGroup::getInstanceId, 
normalizeMetadataScope(instanceId))
                 .eq(StringUtils.hasText(clusterId), RmqGroup::getClusterId, 
clusterId)
                 .like(StringUtils.hasText(search), RmqGroup::getName, search)
                 .orderByAsc(RmqGroup::getName);
@@ -225,6 +231,10 @@ public class RocketMQMetadataProvider implements 
MetadataProvider {
         return result;
     }
 
+    private String normalizeMetadataScope(String instanceId) {
+        return StringUtils.hasText(instanceId) ? instanceId.trim() : "";
+    }
+
     private ConsumeType parseConsumeType(String messageModel) {
         if (!StringUtils.hasText(messageModel)) {
             return ConsumeType.CLUSTERING;
diff --git a/server/src/main/resources/db/schema.sql 
b/server/src/main/resources/db/schema.sql
index 9132df72a..f20f859d6 100644
--- a/server/src/main/resources/db/schema.sql
+++ b/server/src/main/resources/db/schema.sql
@@ -80,7 +80,7 @@ CREATE TABLE IF NOT EXISTS rmq_instance_topic (
   `gmt_create`   datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP COMMENT '修改时间',
   cluster_id VARCHAR(64) NOT NULL,
-  instance_id VARCHAR(128) COMMENT '归属实例 ID(rmq_instance.name,全局唯一)',
+  instance_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '归属实例 
ID(rmq_instance.name;空字符串表示历史未归属记录)',
   name VARCHAR(255) NOT NULL,
   topic_type VARCHAR(32) DEFAULT 'NORMAL',
   read_queue_nums INT DEFAULT 8,
@@ -90,7 +90,7 @@ CREATE TABLE IF NOT EXISTS rmq_instance_topic (
   status VARCHAR(32) DEFAULT 'ACTIVE',
   created_by VARCHAR(64),
   PRIMARY KEY (`id`),
-  UNIQUE KEY uk_cluster_topic (cluster_id, name),
+  UNIQUE KEY uk_cluster_instance_topic (cluster_id, instance_id, name),
   INDEX idx_topic_instance (instance_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
@@ -100,7 +100,7 @@ CREATE TABLE IF NOT EXISTS rmq_instance_group (
   `gmt_create`   datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP COMMENT '修改时间',
   cluster_id VARCHAR(64) NOT NULL,
-  instance_id VARCHAR(128) COMMENT '归属实例 ID(rmq_instance.name,全局唯一)',
+  instance_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '归属实例 
ID(rmq_instance.name;空字符串表示历史未归属记录)',
   name VARCHAR(255) NOT NULL,
   consume_type VARCHAR(32) DEFAULT 'CONCURRENTLY',
   message_model VARCHAR(32) DEFAULT 'CLUSTERING',
@@ -108,7 +108,7 @@ CREATE TABLE IF NOT EXISTS rmq_instance_group (
   status VARCHAR(32) DEFAULT 'ACTIVE',
   created_by VARCHAR(64),
   PRIMARY KEY (`id`),
-  UNIQUE KEY uk_cluster_group (cluster_id, name),
+  UNIQUE KEY uk_cluster_instance_group (cluster_id, instance_id, name),
   INDEX idx_group_instance (instance_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
index 727689dc2..ab559c1ee 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
@@ -175,7 +175,7 @@ class MybatisPlusInstanceRepositoryTest {
     void countTopicsByInstanceShouldDelegateToTopicMapperTest() {
         when(topicMapper.selectCount(any(QueryWrapper.class))).thenReturn(5L);
 
-        assertThat(repository.countTopicsByInstance(2L)).isEqualTo(5L);
+        
assertThat(repository.countTopicsByInstance("instance-proxy-1")).isEqualTo(5L);
         verify(topicMapper).selectCount(any(QueryWrapper.class));
     }
 
@@ -183,7 +183,7 @@ class MybatisPlusInstanceRepositoryTest {
     void countGroupsByInstanceShouldDelegateToGroupMapperTest() {
         when(groupMapper.selectCount(any(QueryWrapper.class))).thenReturn(2L);
 
-        assertThat(repository.countGroupsByInstance(2L)).isEqualTo(2L);
+        
assertThat(repository.countGroupsByInstance("instance-proxy-1")).isEqualTo(2L);
         verify(groupMapper).selectCount(any(QueryWrapper.class));
     }
 
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProviderTest.java
index b99657915..ed63f46fb 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProviderTest.java
@@ -72,7 +72,7 @@ class ApacheInstanceProviderTest {
         InstanceVO instance = InstanceVO.builder().name("inst-1").build();
         instance.setId(1L);
         
when(instanceRepository.findByIdentifier("inst-1")).thenReturn(Optional.of(instance));
-        when(instanceRepository.countTopicsByInstance(1L)).thenReturn(3L);
+        
when(instanceRepository.countTopicsByInstance("inst-1")).thenReturn(3L);
 
         assertThat(provider.countTopics("inst-1")).isEqualTo(3);
     }
@@ -82,11 +82,20 @@ class ApacheInstanceProviderTest {
         InstanceVO instance = InstanceVO.builder().name("inst-1").build();
         instance.setId(1L);
         
when(instanceRepository.findByIdentifier("inst-1")).thenReturn(Optional.of(instance));
-        when(instanceRepository.countGroupsByInstance(1L)).thenReturn(2L);
+        
when(instanceRepository.countGroupsByInstance("inst-1")).thenReturn(2L);
 
         assertThat(provider.countGroups("inst-1")).isEqualTo(2);
     }
 
+    @Test
+    void listTopicsShouldPassTheSelectedInstanceToMetadataProvider() {
+        when(metadataProvider.listTopics("inst-1", null, "FIFO", 
"orders")).thenReturn(java.util.List.of());
+
+        assertThat(provider.listTopics("inst-1", "FIFO", "orders")).isEmpty();
+
+        verify(metadataProvider).listTopics("inst-1", null, "FIFO", "orders");
+    }
+
     @Test
     void listConsumerGroupsShouldPassTheSelectedInstanceToMetadataProvider() {
         when(metadataProvider.listConsumerGroups("inst-1", null, 
"orders")).thenReturn(java.util.List.of());
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
index 6786c071c..ccfcf8e2c 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
@@ -200,7 +200,7 @@ class RocketMQAdminClientImplTest {
     }
 
     @Test
-    void createTopicScopesLookupToCluster() throws Exception {
+    void createTopicScopesLookupToClusterAndLegacyInstanceScope() throws 
Exception {
         TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new 
MybatisConfiguration(), ""), RmqTopic.class);
         ClusterInfo clusterInfo = new ClusterInfo();
         Map<String, Set<String>> clusterAddrTable = new HashMap<>();
@@ -224,7 +224,7 @@ class RocketMQAdminClientImplTest {
                 ArgumentCaptor.forClass(LambdaQueryWrapper.class);
         verify(topicMapper, times(2)).selectOne(captor.capture());
         for (LambdaQueryWrapper<RmqTopic> wrapper : captor.getAllValues()) {
-            assertThat(wrapper.getSqlSegment()).contains("cluster_id");
+            assertThat(wrapper.getSqlSegment()).contains("cluster_id", 
"instance_id");
         }
     }
 
@@ -359,7 +359,7 @@ class RocketMQAdminClientImplTest {
     }
 
     @Test
-    void topicDeleteUsesSelectedInstanceAndScopesMetadataToCluster() throws 
Exception {
+    void 
topicDeleteUsesSelectedInstanceAndScopesMetadataToClusterAndInstance() throws 
Exception {
         TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new 
MybatisConfiguration(), ""), RmqTopic.class);
         DefaultMQAdminExt selectedAdmin = 
org.mockito.Mockito.mock(DefaultMQAdminExt.class);
         
when(selectedAdmin.examineBrokerClusterInfo()).thenReturn(clusterInfoWithMaster());
@@ -374,7 +374,7 @@ class RocketMQAdminClientImplTest {
 
         ArgumentCaptor<LambdaQueryWrapper<RmqTopic>> captor = 
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
         verify(topicMapper).delete(captor.capture());
-        assertThat(captor.getValue().getSqlSegment()).contains("cluster_id", 
"name");
+        assertThat(captor.getValue().getSqlSegment()).contains("cluster_id", 
"instance_id", "name");
         verify(selectedAdmin).deleteTopicInBroker(Set.of("10.0.0.1:10911"), 
"orders");
         verify(selectedAdmin).deleteTopicInNameServer(Set.of("10.0.0.2:9876"), 
"cluster-1", "orders");
     }
@@ -422,6 +422,9 @@ class RocketMQAdminClientImplTest {
         verify(selectedAdmin).createAndUpdateSubscriptionGroupConfig(
                 org.mockito.ArgumentMatchers.eq("10.0.0.1:10911"), any());
         verify(adminExt, 
never()).createAndUpdateSubscriptionGroupConfig(anyString(), any());
+        ArgumentCaptor<LambdaQueryWrapper<RmqGroup>> captor = 
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
+        verify(groupMapper).selectOne(captor.capture());
+        assertThat(captor.getValue().getSqlSegment()).contains("cluster_id", 
"instance_id", "name");
     }
 
     @Test
@@ -448,7 +451,7 @@ class RocketMQAdminClientImplTest {
         verify(adminExt, never()).deleteSubscriptionGroup(anyString(), 
anyString(), org.mockito.ArgumentMatchers.anyBoolean());
         ArgumentCaptor<LambdaQueryWrapper<RmqGroup>> captor = 
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
         verify(groupMapper).delete(captor.capture());
-        assertThat(captor.getValue().getSqlSegment()).contains("cluster_id", 
"name");
+        assertThat(captor.getValue().getSqlSegment()).contains("cluster_id", 
"instance_id", "name");
     }
 
     @Test
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
index e6d5da09c..f74e81cc1 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
@@ -16,6 +16,10 @@
  */
 package org.apache.rocketmq.studio.provider.apache;
 
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.common.message.MessageQueue;
 import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
@@ -38,6 +42,7 @@ import 
org.apache.rocketmq.studio.instance.topic.TopicConsumerVO;
 import org.apache.rocketmq.studio.instance.topic.TopicConsumerPageVO;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 import org.apache.rocketmq.studio.persistence.entity.RmqGroup;
+import org.apache.rocketmq.studio.persistence.entity.RmqTopic;
 import org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
 import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
 import org.junit.jupiter.api.Test;
@@ -59,6 +64,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.never;
@@ -163,6 +169,34 @@ class RocketMQMetadataProviderTest {
         verifyNoInteractions(runtimeAdminClientResolver);
     }
 
+    @Test
+    void listTopicsShouldScopeDatabaseQueryToSelectedInstance() {
+        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new 
MybatisConfiguration(), ""), RmqTopic.class);
+        when(topicMapper.selectList(any())).thenReturn(List.of());
+        RocketMQMetadataProvider provider = newProvider();
+
+        assertThat(provider.listTopics("instance-a", "cluster-1", "NORMAL", 
"orders")).isEmpty();
+
+        
org.mockito.ArgumentCaptor<LambdaQueryWrapper<org.apache.rocketmq.studio.persistence.entity.RmqTopic>>
 captor =
+                org.mockito.ArgumentCaptor.forClass(LambdaQueryWrapper.class);
+        verify(topicMapper).selectList(captor.capture());
+        assertThat(captor.getValue().getSqlSegment()).contains("instance_id", 
"cluster_id");
+    }
+
+    @Test
+    void listConsumerGroupsShouldScopeDatabaseQueryToSelectedInstance() {
+        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new 
MybatisConfiguration(), ""), RmqGroup.class);
+        when(groupMapper.selectList(any())).thenReturn(List.of());
+        RocketMQMetadataProvider provider = newProvider();
+
+        assertThat(provider.listConsumerGroups("instance-a", "cluster-1", 
"orders")).isEmpty();
+
+        org.mockito.ArgumentCaptor<LambdaQueryWrapper<RmqGroup>> captor =
+                org.mockito.ArgumentCaptor.forClass(LambdaQueryWrapper.class);
+        verify(groupMapper, times(1)).selectList(captor.capture());
+        assertThat(captor.getValue().getSqlSegment()).contains("instance_id", 
"cluster_id");
+    }
+
     @Test
     void getTopicRoutesShouldUseSelectedInstanceRuntimeClient() {
         List<BrokerRouteVO> routes = 
List.of(BrokerRouteVO.builder().brokerName("broker-a").build());

Reply via email to