Skip to main content

rocketmq_client_rust/admin/default_mq_admin_ext_impl/
topic.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::group::java_long_to_u64;
16use super::group::offset_to_java_long;
17use super::group::sync_pull_result_missing;
18use super::group::timestamp_to_java_long;
19use super::*;
20
21pub(super) fn encode_topic_attributes(attributes: &HashMap<CheetahString, CheetahString>) -> Option<CheetahString> {
22    if attributes.is_empty() {
23        return None;
24    }
25
26    let serialized = AttributeParser::parse_to_string(
27        &attributes
28            .iter()
29            .map(|(key, value)| (key.to_string(), value.to_string()))
30            .collect::<HashMap<String, String>>(),
31    );
32
33    if serialized.is_empty() {
34        None
35    } else {
36        Some(serialized.into())
37    }
38}
39
40pub(super) fn master_flush_offset_to_java_long(master_flush_offset: u64) -> crate::ClientResult<i64> {
41    offset_to_java_long("resetMasterFlushOffset", master_flush_offset)
42}
43
44pub(super) fn query_consume_queue_index_to_java_long(index: u64) -> crate::ClientResult<i64> {
45    offset_to_java_long("queryConsumeQueue", index)
46}
47
48#[allow(clippy::too_many_arguments, reason = "mirrors the Java query-message wire header")]
49pub(super) fn query_message_request_header(
50    topic: &CheetahString,
51    key: &CheetahString,
52    max_num: i32,
53    begin_timestamp: i64,
54    end_timestamp: i64,
55    key_type: &CheetahString,
56    last_key: Option<&CheetahString>,
57) -> rocketmq_protocol::protocol::header::query_message_request_header::QueryMessageRequestHeader {
58    rocketmq_protocol::protocol::header::query_message_request_header::QueryMessageRequestHeader {
59        topic: topic.clone(),
60        key: key.clone(),
61        max_num,
62        begin_timestamp,
63        end_timestamp,
64        index_type: Some(key_type.clone()),
65        last_key: last_key.cloned(),
66        topic_request_header: None,
67    }
68}
69
70pub(super) fn search_offset_timestamp_to_java_long(timestamp: u64) -> crate::ClientResult<i64> {
71    timestamp_to_java_long("searchOffset", timestamp)
72}
73
74pub(super) fn topic_list_from_lite_topic_names(
75    broker_addr: Option<CheetahString>,
76    topic_names: impl IntoIterator<Item = CheetahString>,
77) -> TopicList {
78    let mut topic_list = topic_names.into_iter().collect::<Vec<_>>();
79    topic_list.sort_by(|left, right| left.as_str().cmp(right.as_str()));
80    topic_list.dedup();
81    TopicList {
82        topic_list,
83        broker_addr,
84    }
85}
86
87pub(super) fn lite_topic_list_from_broker_lite_info(
88    broker_addr: Option<CheetahString>,
89    lite_info: &GetBrokerLiteInfoResponseBody,
90) -> TopicList {
91    topic_list_from_lite_topic_names(broker_addr, lite_info.get_topic_meta().keys().cloned())
92}
93
94pub(super) fn lite_subscription_group_list_from_broker_lite_info(
95    topic: &CheetahString,
96    lite_info: &GetBrokerLiteInfoResponseBody,
97) -> GroupList {
98    GroupList::new(lite_info.get_group_meta().get(topic).cloned().unwrap_or_default())
99}
100
101pub(super) fn resolve_lite_pull_queue_num(
102    field_name: &'static str,
103    value: i32,
104    fallback_queue_num: i32,
105    allow_fallback: bool,
106) -> crate::ClientResult<u32> {
107    let resolved = if value > 0 {
108        value
109    } else if allow_fallback && fallback_queue_num > 0 {
110        fallback_queue_num
111    } else {
112        return Err(mq_client_err!(format!("{field_name} must be positive")));
113    };
114    u32::try_from(resolved).map_err(ClientError::illegal_argument_source)
115}
116
117pub(super) fn lite_pull_topic_config(
118    topic: CheetahString,
119    queue_num: i32,
120    topic_sys_flag: i32,
121    read_queue_nums: i32,
122    write_queue_nums: i32,
123    update_existing: bool,
124) -> crate::ClientResult<TopicConfig> {
125    if topic.is_empty() {
126        return Err(mq_client_err!("Lite pull topic cannot be empty"));
127    }
128    if topic_sys_flag < 0 {
129        return Err(mq_client_err!("topicSysFlag must be non-negative"));
130    }
131
132    let read_queue_nums = resolve_lite_pull_queue_num("readQueueNums", read_queue_nums, queue_num, !update_existing)?;
133    let write_queue_nums =
134        resolve_lite_pull_queue_num("writeQueueNums", write_queue_nums, queue_num, !update_existing)?;
135    let topic_sys_flag = u32::try_from(topic_sys_flag).map_err(ClientError::illegal_argument_source)?;
136
137    let mut config = TopicConfig::with_sys_flag(
138        topic,
139        read_queue_nums,
140        write_queue_nums,
141        PermName::PERM_READ | PermName::PERM_WRITE,
142        topic_sys_flag,
143    );
144    config.attributes.insert(
145        TopicAttributes::topic_message_type_attribute().name().clone(),
146        CheetahString::from_static_str(TopicMessageType::Lite.as_str()),
147    );
148    Ok(config)
149}
150
151impl DefaultMQAdminExtImpl {
152    pub(super) async fn query_topics_by_consumer_from_route(
153        &self,
154        group: CheetahString,
155    ) -> crate::ClientResult<TopicList> {
156        let timeout = self.remoting_timeout_millis()?;
157        let retry_topic: CheetahString = mix_all::get_retry_topic(&group).into();
158        let topic_route = self
159            .mq_client_api()?
160            .get_topic_route_info_from_name_server(&retry_topic, timeout)
161            .await?;
162        let Some(route_data) = topic_route else {
163            return Ok(TopicList::default());
164        };
165
166        let mut result = TopicList::default();
167        for broker_data in &route_data.broker_datas {
168            let Some(addr) = broker_data.select_broker_addr() else {
169                continue;
170            };
171            let topic_list = self
172                .mq_client_api()?
173                .query_topics_by_consumer(&addr, QueryTopicsByConsumerRequestHeader::new(group.clone()), timeout)
174                .await?;
175            for topic in topic_list.topic_list {
176                if !result.topic_list.contains(&topic) {
177                    result.topic_list.push(topic);
178                }
179            }
180        }
181
182        Ok(result)
183    }
184
185    pub async fn pull_message_from_queue(
186        &self,
187        broker_addr: &str,
188        mq: &MessageQueue,
189        sub_expression: &str,
190        offset: i64,
191        max_nums: i32,
192        timeout_millis: u64,
193    ) -> crate::ClientResult<crate::consumer::pull_result::PullResult> {
194        let sys_flag = PullSysFlag::build_sys_flag(false, false, true, false);
195
196        let request_header = PullMessageRequestHeader {
197            consumer_group: CheetahString::from_static_str(mix_all::TOOLS_CONSUMER_GROUP),
198            topic: mq.topic().clone(),
199            lite_topic: None,
200            queue_id: mq.queue_id(),
201            queue_offset: offset,
202            max_msg_nums: max_nums,
203            sys_flag: sys_flag as i32,
204            commit_offset: 0,
205            suspend_timeout_millis: 0,
206            sub_version: 0,
207            subscription: Some(CheetahString::from(sub_expression)),
208            expression_type: None,
209            max_msg_bytes: None,
210            request_source: None,
211            proxy_forward_client_id: None,
212            topic_request: None,
213        };
214
215        struct NoopPullCallback;
216        impl PullCallback for NoopPullCallback {
217            async fn on_success(&mut self, _pull_result: PullResultExt) {}
218            fn on_exception(&mut self, _e: crate::ClientError) {}
219        }
220
221        let api_impl = self.mq_client_api()?;
222
223        let mut result = MQClientAPIImpl::pull_message(
224            api_impl,
225            CheetahString::from(broker_addr),
226            request_header,
227            timeout_millis,
228            CommunicationMode::Sync,
229            NoopPullCallback,
230        )
231        .await?
232        .ok_or_else(|| sync_pull_result_missing("DefaultMQAdminExtImpl::pull_message_from_queue"))?;
233
234        if result.pull_result.pull_status == PullStatus::Found {
235            if let Some(mut message_binary) = result.message_binary.take() {
236                let msg_vec = MessageDecoder::decodes_batch(&mut message_binary, true, true);
237                result.pull_result.msg_found_list = Some(msg_vec);
238            }
239        }
240
241        Ok(result.pull_result)
242    }
243
244    pub async fn query_message_by_key(
245        &self,
246        cluster_name: Option<CheetahString>,
247        topic: CheetahString,
248        key: CheetahString,
249        max_num: i32,
250        begin_timestamp: i64,
251        end_timestamp: i64,
252        key_type: CheetahString,
253        last_key: Option<CheetahString>,
254    ) -> crate::ClientResult<crate::base::query_result::QueryResult> {
255        self.query_message_by_key_internal(
256            cluster_name,
257            topic,
258            key,
259            max_num,
260            begin_timestamp,
261            end_timestamp,
262            key_type,
263            last_key,
264            false,
265        )
266        .await
267    }
268
269    pub async fn query_message_by_unique_key(
270        &self,
271        cluster_name: Option<CheetahString>,
272        topic: CheetahString,
273        unique_key: CheetahString,
274        max_num: i32,
275        begin_timestamp: i64,
276        end_timestamp: i64,
277    ) -> crate::ClientResult<crate::base::query_result::QueryResult> {
278        self.query_message_by_key_internal(
279            cluster_name,
280            topic,
281            unique_key,
282            max_num,
283            begin_timestamp,
284            end_timestamp,
285            CheetahString::from_static_str(MessageConst::INDEX_UNIQUE_TYPE),
286            None,
287            true,
288        )
289        .await
290    }
291
292    pub(super) async fn query_message_by_key_internal(
293        &self,
294        cluster_name: Option<CheetahString>,
295        topic: CheetahString,
296        key: CheetahString,
297        max_num: i32,
298        begin_timestamp: i64,
299        end_timestamp: i64,
300        key_type: CheetahString,
301        last_key: Option<CheetahString>,
302        unique_key_flag: bool,
303    ) -> crate::ClientResult<crate::base::query_result::QueryResult> {
304        let route_topic = cluster_name.unwrap_or_else(|| topic.clone());
305        let topic_route_data = self
306            .examine_topic_route_info(route_topic.clone())
307            .await?
308            .ok_or_else(|| admin_route_not_found(&route_topic))?;
309
310        let mut message_list: Vec<MessageExt> = Vec::new();
311        let mut index_last_update_timestamp: u64 = 0;
312
313        let api_impl = self.mq_client_api()?;
314        let timeout = self.remoting_timeout_millis()?;
315
316        for broker_data in &topic_route_data.broker_datas {
317            let broker_addr = match broker_data.select_broker_addr() {
318                Some(addr) => addr,
319                None => continue,
320            };
321
322            let request_header = query_message_request_header(
323                &topic,
324                &key,
325                max_num,
326                begin_timestamp,
327                end_timestamp,
328                &key_type,
329                last_key.as_ref(),
330            );
331
332            match MQClientAPIImpl::query_message(&api_impl, &broker_addr, request_header, unique_key_flag, timeout)
333                .await
334            {
335                Ok(Some((response_header, body))) => {
336                    if let Some(mut body_bytes) = body {
337                        let msgs = MessageDecoder::decodes_batch(&mut body_bytes, true, true);
338                        message_list.extend(msgs);
339                    }
340                    let response_index_timestamp = java_long_to_u64(
341                        "queryMessage",
342                        "indexLastUpdateTimestamp",
343                        response_header.index_last_update_timestamp,
344                    )?;
345                    if response_index_timestamp > index_last_update_timestamp {
346                        index_last_update_timestamp = response_index_timestamp;
347                    }
348                }
349                Ok(None) => {
350                    // No messages found on this broker, continue
351                }
352                Err(e) => {
353                    tracing::warn!("Failed to query message by key from broker {}: {}", broker_addr, e);
354                }
355            }
356        }
357
358        Ok(crate::base::query_result::QueryResult::new(
359            index_last_update_timestamp,
360            message_list,
361        ))
362    }
363}
364
365pub(super) fn merge_order_conf_entries(existing: &str, value: &str) -> String {
366    let mut entries = HashMap::new();
367    for item in existing.split(';').filter(|item| !item.trim().is_empty()) {
368        if let Some((broker_name, _)) = item.split_once(':') {
369            entries.insert(broker_name.to_string(), item.to_string());
370        }
371    }
372    if let Some((broker_name, _)) = value.split_once(':') {
373        entries.insert(broker_name.to_string(), value.to_string());
374    } else if !value.trim().is_empty() {
375        entries.insert(value.to_string(), value.to_string());
376    }
377
378    let mut broker_names: Vec<String> = entries.keys().cloned().collect();
379    broker_names.sort();
380    broker_names
381        .into_iter()
382        .filter_map(|broker_name| entries.remove(&broker_name))
383        .collect::<Vec<_>>()
384        .join(";")
385}
386
387pub(super) fn retain_java_user_topic_config(
388    topic_table: &mut HashMap<CheetahString, TopicConfig>,
389    broker_system_topics: &[CheetahString],
390    special_topic: bool,
391) {
392    topic_table.retain(|topic_name, topic_config| {
393        let topic = topic_config
394            .topic_name
395            .as_ref()
396            .map(CheetahString::as_str)
397            .unwrap_or_else(|| topic_name.as_str());
398        if broker_system_topics
399            .iter()
400            .any(|system_topic| system_topic.as_str() == topic)
401            || TopicValidator::is_system_topic(topic)
402        {
403            return false;
404        }
405        if !special_topic && (topic.starts_with(RETRY_GROUP_TOPIC_PREFIX) || topic.starts_with(DLQ_GROUP_TOPIC_PREFIX))
406        {
407            return false;
408        }
409        PermName::is_valid(topic_config.perm)
410    });
411}
412
413pub(super) fn admin_route_not_found(route_topic: &CheetahString) -> crate::ClientError {
414    crate::ClientError::route_not_found(route_topic)
415}