1use 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 }
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}