rocketmq_protocol/protocol/static_topic/
topic_queue_mapping_detail.rs1use std::collections::HashMap;
16
17use serde::Deserialize;
18use serde::Serialize;
19
20use crate::protocol::static_topic::i32_key_map_serde;
21use crate::protocol::static_topic::logic_queue_mapping_item::LogicQueueMappingItem;
22use crate::protocol::static_topic::topic_queue_mapping_info::TopicQueueMappingInfo;
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25pub struct TopicQueueMappingDetail {
26 #[serde(flatten)]
27 pub topic_queue_mapping_info: TopicQueueMappingInfo,
28
29 #[serde(rename = "hostedQueues")]
30 #[serde(
31 default = "default_hosted_queues",
32 deserialize_with = "i32_key_map_serde::deserialize_optional_i32_key_map"
33 )]
34 pub hosted_queues: Option<HashMap<i32 , Vec<LogicQueueMappingItem>>>,
35}
36
37fn default_hosted_queues() -> Option<HashMap<i32, Vec<LogicQueueMappingItem>>> {
38 Some(HashMap::new())
39}
40
41impl Default for TopicQueueMappingDetail {
42 fn default() -> Self {
43 Self {
44 topic_queue_mapping_info: TopicQueueMappingInfo::default(),
45 hosted_queues: default_hosted_queues(),
46 }
47 }
48}
49
50impl TopicQueueMappingDetail {
51 pub fn get_mapping_info(
52 mapping_detail: &TopicQueueMappingDetail,
53 global_id: i32,
54 ) -> Option<&Vec<LogicQueueMappingItem>> {
55 mapping_detail.hosted_queues.as_ref()?.get(&global_id)
56 }
57
58 pub fn compute_max_offset_from_mapping(mapping_detail: &TopicQueueMappingDetail, global_id: Option<i32>) -> i64 {
59 let Some(global_id) = global_id else {
60 return -1;
61 };
62 match Self::get_mapping_info(mapping_detail, global_id) {
63 Some(mapping_items) => {
64 if mapping_items.is_empty() {
65 return -1;
66 }
67 mapping_items
68 .last()
69 .map_or(-1, LogicQueueMappingItem::compute_max_static_queue_offset)
70 }
71 None => -1,
72 }
73 }
74}
75
76impl TopicQueueMappingDetail {
78 pub fn build_id_map(mapping_detail: &TopicQueueMappingDetail) -> HashMap<i32, i32> {
79 let mut curr_id_map = HashMap::new();
80 let broker_name = mapping_detail.topic_queue_mapping_info.bname.as_ref();
81 if let (Some(hosted_queues), Some(broker_name)) = (&mapping_detail.hosted_queues, broker_name) {
82 for (global_id, items) in hosted_queues {
83 if let Some(last_item) = items.last() {
84 if last_item.bname.as_ref() == Some(broker_name) {
85 curr_id_map.insert(*global_id, last_item.queue_id);
86 }
87 }
88 }
89 }
90 curr_id_map
91 }
92
93 pub fn clone_as_mapping_info(mapping_detail: &TopicQueueMappingDetail) -> TopicQueueMappingInfo {
94 TopicQueueMappingInfo {
95 topic: mapping_detail.topic_queue_mapping_info.topic.clone(),
96 scope: mapping_detail.topic_queue_mapping_info.scope.clone(),
97 total_queues: mapping_detail.topic_queue_mapping_info.total_queues,
98 bname: mapping_detail.topic_queue_mapping_info.bname.clone(),
99 epoch: mapping_detail.topic_queue_mapping_info.epoch,
100 dirty: mapping_detail.topic_queue_mapping_info.dirty,
101 curr_id_map: Some(Self::build_id_map(mapping_detail)),
102 }
103 }
104 pub fn put_mapping_info(
105 mapping_detail: &mut TopicQueueMappingDetail,
106 global_id: i32,
107 mapping_info: Vec<LogicQueueMappingItem>,
108 ) {
109 if mapping_info.is_empty() {
110 return;
111 }
112 mapping_detail
113 .hosted_queues
114 .get_or_insert_with(HashMap::new)
115 .insert(global_id, mapping_info);
116 }
117
118 pub fn check_if_as_physical(mapping_detail: &TopicQueueMappingDetail, global_id: i32) -> bool {
119 match Self::get_mapping_info(mapping_detail, global_id) {
120 None => true,
121 Some(mapping_items) => {
122 mapping_items.len() == 1
123 && mapping_items
124 .first()
125 .is_some_and(|mapping_item| mapping_item.logic_offset == 0)
126 }
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn topic_queue_mapping_detail_default() {
137 let detail = TopicQueueMappingDetail::default();
138 assert_eq!(detail.topic_queue_mapping_info, TopicQueueMappingInfo::default());
139 assert_eq!(detail.hosted_queues, Some(HashMap::new()));
140 }
141
142 #[test]
143 fn topic_queue_mapping_detail_serde() {
144 let mut detail = TopicQueueMappingDetail::default();
145 detail.topic_queue_mapping_info.topic = Some("test".into());
146 let mut hosted_queues = HashMap::new();
147 hosted_queues.insert(1, vec![LogicQueueMappingItem::default()]);
148 detail.hosted_queues = Some(hosted_queues);
149
150 let json = serde_json::to_string(&detail).unwrap();
151 let expected = r#"{"topic":"test","scope":"__global__","totalQueues":0,"bname":null,"epoch":0,"dirty":false,"currIdMap":null,"hostedQueues":{"1":[{"gen":0,"queueId":0,"bname":null,"logicOffset":0,"startOffset":0,"endOffset":-1,"timeOfStart":-1,"timeOfEnd":-1}]}}"#;
152 assert_eq!(json, expected);
153
154 let deserialized: TopicQueueMappingDetail = serde_json::from_str(&json).unwrap();
155 assert_eq!(detail, deserialized);
156 }
157
158 #[test]
159 fn topic_queue_mapping_detail_missing_hosted_queues_uses_java_empty_default() {
160 let json = r#"{"topic":"test","scope":"__global__","totalQueues":0,"bname":null,"epoch":0,"dirty":false,"currIdMap":null}"#;
161
162 let detail: TopicQueueMappingDetail = serde_json::from_str(json).unwrap();
163
164 assert_eq!(detail.hosted_queues, Some(HashMap::new()));
165 }
166
167 #[test]
168 fn get_mapping_info() {
169 let mut detail = TopicQueueMappingDetail::default();
170 let items = vec![LogicQueueMappingItem::default()];
171 let mut hosted_queues = HashMap::new();
172 hosted_queues.insert(1, items.clone());
173 detail.hosted_queues = Some(hosted_queues);
174
175 assert_eq!(TopicQueueMappingDetail::get_mapping_info(&detail, 1).unwrap(), &items);
176 assert!(TopicQueueMappingDetail::get_mapping_info(&detail, 2).is_none());
177 }
178
179 #[test]
180 fn compute_max_offset_from_mapping() {
181 let mut detail = TopicQueueMappingDetail::default();
182 let mut items = vec![LogicQueueMappingItem::default()];
183 items[0].logic_offset = 100;
184 items[0].start_offset = 50;
185 items[0].end_offset = 150;
186 let mut hosted_queues = HashMap::new();
189 hosted_queues.insert(1, items);
190 detail.hosted_queues = Some(hosted_queues);
191
192 assert_eq!(
193 TopicQueueMappingDetail::compute_max_offset_from_mapping(&detail, Some(1)),
194 200
195 );
196
197 detail.hosted_queues.as_mut().unwrap().get_mut(&1).unwrap()[0].end_offset = 20;
199 assert_eq!(
201 TopicQueueMappingDetail::compute_max_offset_from_mapping(&detail, Some(1)),
202 100
203 );
204
205 assert_eq!(
207 TopicQueueMappingDetail::compute_max_offset_from_mapping(&detail, Some(2)),
208 -1
209 );
210 assert_eq!(
211 TopicQueueMappingDetail::compute_max_offset_from_mapping(&detail, None),
212 -1
213 );
214 }
215
216 #[test]
217 fn clone_as_mapping_info() {
218 let detail = TopicQueueMappingDetail {
219 topic_queue_mapping_info: TopicQueueMappingInfo {
220 topic: Some("test_topic".into()),
221 total_queues: 8,
222 bname: Some("broker_a".into()),
223 epoch: 12345,
224 ..TopicQueueMappingInfo::default()
225 },
226 ..Default::default()
227 };
228
229 let info = TopicQueueMappingDetail::clone_as_mapping_info(&detail);
230 assert_eq!(info.topic, detail.topic_queue_mapping_info.topic);
231 assert_eq!(info.total_queues, detail.topic_queue_mapping_info.total_queues);
232 assert_eq!(info.bname, detail.topic_queue_mapping_info.bname);
233 assert_eq!(info.epoch, detail.topic_queue_mapping_info.epoch);
234 }
235
236 #[test]
237 fn clone_as_mapping_info_builds_current_id_map_for_leader_items() {
238 let broker_a = cheetah_string::CheetahString::from_static_str("broker_a");
239 let broker_b = cheetah_string::CheetahString::from_static_str("broker_b");
240 let mut hosted_queues = HashMap::new();
241 hosted_queues.insert(
242 0,
243 vec![LogicQueueMappingItem {
244 queue_id: 3,
245 bname: Some(broker_a.clone()),
246 ..Default::default()
247 }],
248 );
249 hosted_queues.insert(
250 1,
251 vec![LogicQueueMappingItem {
252 queue_id: 7,
253 bname: Some(broker_b),
254 ..Default::default()
255 }],
256 );
257 let detail = TopicQueueMappingDetail {
258 topic_queue_mapping_info: TopicQueueMappingInfo {
259 topic: Some("test_topic".into()),
260 total_queues: 2,
261 bname: Some(broker_a),
262 epoch: 10,
263 ..TopicQueueMappingInfo::default()
264 },
265 hosted_queues: Some(hosted_queues),
266 };
267
268 let info = TopicQueueMappingDetail::clone_as_mapping_info(&detail);
269
270 let curr_id_map = info.curr_id_map.unwrap();
271 assert_eq!(curr_id_map.get(&0), Some(&3));
272 assert!(!curr_id_map.contains_key(&1));
273 }
274
275 #[test]
276 fn put_mapping_info() {
277 let mut detail = TopicQueueMappingDetail::default();
278
279 let items = vec![LogicQueueMappingItem::default()];
280 TopicQueueMappingDetail::put_mapping_info(&mut detail, 1, items.clone());
281
282 assert_eq!(detail.hosted_queues.as_ref().unwrap().get(&1).unwrap(), &items);
283
284 TopicQueueMappingDetail::put_mapping_info(&mut detail, 2, vec![]);
285 assert!(detail.hosted_queues.as_ref().unwrap().get(&2).is_none());
286 }
287
288 #[test]
289 fn put_mapping_info_initializes_missing_hosted_queues_like_java() {
290 let mut detail = TopicQueueMappingDetail {
291 hosted_queues: None,
292 ..Default::default()
293 };
294
295 let items = vec![LogicQueueMappingItem::default()];
296 TopicQueueMappingDetail::put_mapping_info(&mut detail, 1, items.clone());
297
298 assert_eq!(detail.hosted_queues.as_ref().unwrap().get(&1), Some(&items));
299 }
300
301 #[test]
302 fn check_if_as_physical_matches_java_rules() {
303 let mut detail = TopicQueueMappingDetail::default();
304 assert!(TopicQueueMappingDetail::check_if_as_physical(&detail, 0));
305
306 detail.hosted_queues.as_mut().unwrap().insert(
307 0,
308 vec![LogicQueueMappingItem {
309 logic_offset: 0,
310 ..Default::default()
311 }],
312 );
313 assert!(TopicQueueMappingDetail::check_if_as_physical(&detail, 0));
314
315 detail.hosted_queues.as_mut().unwrap().insert(
316 1,
317 vec![LogicQueueMappingItem {
318 logic_offset: 10,
319 ..Default::default()
320 }],
321 );
322 assert!(!TopicQueueMappingDetail::check_if_as_physical(&detail, 1));
323
324 detail.hosted_queues.as_mut().unwrap().insert(
325 2,
326 vec![
327 LogicQueueMappingItem {
328 logic_offset: 0,
329 ..Default::default()
330 },
331 LogicQueueMappingItem {
332 logic_offset: 0,
333 ..Default::default()
334 },
335 ],
336 );
337 assert!(!TopicQueueMappingDetail::check_if_as_physical(&detail, 2));
338 }
339}