this repo has no description

Delete old all-in-one indexer mockup. Add first draft of BskyPostUnionFind

+189 -1428
+25
indexer/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "either" 7 + version = "1.15.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 10 + 11 + [[package]] 12 + name = "eyewall-indexer" 13 + version = "0.1.0" 14 + dependencies = [ 15 + "itertools", 16 + ] 17 + 18 + [[package]] 19 + name = "itertools" 20 + version = "0.14.0" 21 + source = "registry+https://github.com/rust-lang/crates.io-index" 22 + checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 23 + dependencies = [ 24 + "either", 25 + ]
+1
indexer/Cargo.toml
··· 4 4 edition = "2024" 5 5 6 6 [dependencies] 7 + itertools = "0.14.0"
-1213
indexer/src/storage/memory.rs
··· 1 - 2 - use std::collections::{HashMap, HashSet}; 3 - 4 - #[derive(Clone, Debug, Eq, Hash, PartialEq)] 5 - struct BskyPostId { 6 - did: String, 7 - rkey: String, 8 - } 9 - 10 - impl BskyPostId { 11 - fn from(did: &str, rkey: &str) -> BskyPostId { 12 - BskyPostId { did: String::from(did), rkey: String::from(rkey) } 13 - } 14 - } 15 - 16 - struct BskyPostGraphData { 17 - ro_sid: Option<SubgraphId>, 18 - qo_sid: Option<SubgraphId>, 19 - rq_sid: Option<SubgraphId>, 20 - ro_depth: u32, 21 - qo_depth: u32, 22 - // with the reply-quote graph, there isn't a well-defined depth, so we 23 - // only speak of max-depth 24 - rq_max_depth: u32, 25 - is_ro_leaf: bool, 26 - is_qo_leaf: bool, 27 - is_rq_leaf: bool, 28 - } 29 - 30 - impl BskyPostGraphData { 31 - fn new_root() -> BskyPostGraphData { 32 - BskyPostGraphData { 33 - ro_sid: None, 34 - qo_sid: None, 35 - rq_sid: None, 36 - ro_depth: 1, 37 - qo_depth: 1, 38 - rq_max_depth: 1, 39 - is_ro_leaf: true, 40 - is_qo_leaf: true, 41 - is_rq_leaf: true 42 - } 43 - } 44 - } 45 - 46 - 47 - #[derive(Clone, Eq, Hash, PartialEq)] 48 - struct BskyPostRecord { 49 - id: BskyPostId, 50 - reply_to: Option<BskyPostReplyTo>, 51 - quote_of: Option<BskyPostId>, 52 - } 53 - 54 - #[derive(Clone, Eq, Hash, PartialEq)] 55 - struct BskyPostReplyTo { 56 - target: BskyPostId, 57 - root: BskyPostId, 58 - } 59 - 60 - 61 - type SubgraphId = u32; 62 - 63 - #[derive(Clone, Copy, Debug, Eq, PartialEq)] 64 - enum SubgraphType { 65 - Reply, 66 - Quote, 67 - ReplyQuote, 68 - } 69 - 70 - #[derive(Clone)] 71 - struct SubgraphInfo { 72 - ty: SubgraphType, 73 - sources: Vec<BskyPostId>, 74 - size: u32, 75 - max_width: u32, 76 - max_depth: u32, 77 - } 78 - 79 - impl SubgraphInfo { 80 - fn new(ty: SubgraphType, sources: Vec<BskyPostId>, size: u32, max_width: u32, max_depth: u32) -> SubgraphInfo { 81 - SubgraphInfo { 82 - ty, 83 - sources, 84 - size, 85 - max_width, 86 - max_depth 87 - } 88 - } 89 - } 90 - 91 - 92 - struct BskyGraphData { 93 - posts: HashMap<BskyPostId, BskyPostGraphData>, 94 - pending: HashSet<BskyPostRecord>, 95 - subgraphs: HashMap<SubgraphId, SubgraphInfo>, 96 - subgraph_nodes: HashMap<SubgraphId, Vec<BskyPostId>>, 97 - next_subgraph_id: u32, 98 - } 99 - 100 - impl BskyGraphData { 101 - fn new() -> BskyGraphData { 102 - BskyGraphData { 103 - posts: HashMap::new(), 104 - pending: HashSet::new(), 105 - subgraphs: HashMap::new(), 106 - subgraph_nodes: HashMap::new(), 107 - next_subgraph_id: 1, 108 - } 109 - } 110 - 111 - fn make_subgraph(&mut self, post_ids: Vec<BskyPostId>, subgraph_type: SubgraphType, size: u32, max_width: u32, max_depth: u32) -> SubgraphId { 112 - let subgraph_id = self.next_subgraph_id; 113 - self.next_subgraph_id += 1; 114 - 115 - self.subgraphs.insert(subgraph_id, SubgraphInfo::new(subgraph_type, post_ids.clone(), size, max_width, max_depth)); 116 - 117 - self.subgraph_nodes.insert(subgraph_id, post_ids); 118 - subgraph_id 119 - } 120 - 121 - fn ingest_record(&mut self, record: BskyPostRecord) { 122 - // invariant: if a post has been processed, all ancestors have also 123 - if self.posts.contains_key(&record.id) { 124 - return; 125 - } 126 - if record.reply_to.is_none() && record.quote_of.is_none() { 127 - let record_id = record.id.clone(); 128 - self.posts.insert(record.id, BskyPostGraphData::new_root()); 129 - // defer making subgraph until we have a parent/child relationship 130 - } else if record.quote_of.is_none() { 131 - let reply_to = record.reply_to.as_ref().unwrap(); 132 - if !self.posts.contains_key(&reply_to.target) { 133 - self.pending.insert(record.clone()); 134 - return; 135 - } 136 - 137 - // not pending, didn't process yet 138 - 139 - // calculate depths for the new node, update parent leaf flags 140 - let parent_data = self.posts.get_mut(&reply_to.target).unwrap(); 141 - let ro_depth = parent_data.ro_depth + 1; 142 - let rq_max_depth = parent_data.rq_max_depth + 1; 143 - let parent_was_ro_leaf = parent_data.is_ro_leaf; 144 - let parent_was_rq_leaf = parent_data.is_rq_leaf; 145 - let parent_qo_sid_maybe = parent_data.qo_sid; 146 - let parent_rq_sid_maybe = parent_data.rq_sid; 147 - parent_data.is_ro_leaf = false; 148 - parent_data.is_rq_leaf = false; 149 - 150 - let ro_sid: SubgraphId; 151 - 152 - // parent is a reply root iff ro_sid is None. we defer creating a subgraph until we have a parent/child 153 - // relationship, so we have to create it here 154 - // if parent_data.ro_sid.is_none() && !self.source_subgraphs.contains_key(&reply_to.target) { 155 - if parent_data.ro_sid.is_none() { 156 - let post_ids = vec![reply_to.target.clone()]; 157 - ro_sid = self.make_subgraph( post_ids, SubgraphType::Reply, 1, 1, 1); 158 - let parent_post = self.posts.get_mut(&reply_to.target).unwrap(); 159 - parent_post.ro_sid = Some(ro_sid); 160 - // don't insert RQ subgraph yet. only do it if it's actually different from the R subgraph, 161 - // which happens if some post in the R subgraph gets quoted 162 - } else { 163 - ro_sid = parent_data.ro_sid.unwrap(); 164 - } 165 - 166 - // if we replied to a post in quote subgraph and the corresponding RQ graph doesnt exist 167 - let mut rq_sid_maybe: Option<SubgraphId> = None; 168 - if parent_qo_sid_maybe.is_some() && parent_rq_sid_maybe.is_none() { 169 - let qo_sid = parent_qo_sid_maybe.unwrap(); 170 - let qo_subgraph = self.subgraphs.get(&qo_sid).expect("Expected quote subgraph"); 171 - let post_ids = qo_subgraph.sources.clone(); 172 - let sid = self.make_subgraph(post_ids, SubgraphType::ReplyQuote, qo_subgraph.size, qo_subgraph.max_width, qo_subgraph.max_depth); 173 - rq_sid_maybe = Some(sid); 174 - 175 - for node in self.subgraph_nodes.get(&qo_sid).expect("Expected subgraph nodes") { 176 - let post = self.posts.get_mut(node).unwrap(); 177 - post.rq_sid = rq_sid_maybe; 178 - } 179 - } else if parent_rq_sid_maybe.is_some() { 180 - rq_sid_maybe = parent_rq_sid_maybe; 181 - } 182 - 183 - let tree_data = BskyPostGraphData { 184 - ro_sid: Some(ro_sid), 185 - qo_sid: None, 186 - rq_sid: rq_sid_maybe, 187 - ro_depth: ro_depth, 188 - qo_depth: 1, 189 - rq_max_depth: rq_max_depth, 190 - is_ro_leaf: true, 191 - is_qo_leaf: true, 192 - is_rq_leaf: true 193 - }; 194 - 195 - self.posts.insert(record.id.clone(), tree_data); 196 - 197 - self.subgraph_nodes.entry(ro_sid).and_modify(|e| e.push(record.id.clone())); 198 - 199 - // the new post we just inserted is part of at most two subgraphs: 200 - // - reply subgraph 201 - // - reply-quote subgraph 202 - // find each of these, if they exist, and update the appropriate stats 203 - 204 - let ro_subgraph = self.subgraphs 205 - .get_mut(&ro_sid) 206 - .expect("Expected reply subgraph to exist for reply"); 207 - 208 - ro_subgraph.size += 1; 209 - ro_subgraph.max_depth = ro_subgraph.max_depth.max(ro_depth); 210 - if !parent_was_ro_leaf { 211 - ro_subgraph.max_width += 1; 212 - } 213 - 214 - if let Some(rq_sid) = rq_sid_maybe { 215 - let rq_subgraph = self.subgraphs 216 - .get_mut(&rq_sid) 217 - .expect("Expected reply-quote subgraph to exist for reply"); 218 - 219 - rq_subgraph.size += 1; 220 - rq_subgraph.max_depth = rq_subgraph.max_depth.max(rq_max_depth); 221 - if !parent_was_rq_leaf { 222 - rq_subgraph.max_width += 1; 223 - } 224 - } 225 - 226 - } else if record.reply_to.is_none() { 227 - let target = record.quote_of.as_ref().unwrap(); 228 - if !self.posts.contains_key(target) { 229 - self.pending.insert(record.clone()); 230 - return; 231 - } 232 - 233 - // not pending, didn't process yet 234 - 235 - // calculate depths for the new node, update parent leaf flags 236 - let parent_data = self.posts.get_mut(target).unwrap(); 237 - let qo_depth = parent_data.qo_depth + 1; 238 - let rq_max_depth = parent_data.rq_max_depth + 1; 239 - let parent_was_qo_leaf = parent_data.is_qo_leaf; 240 - let parent_was_rq_leaf = parent_data.is_rq_leaf; 241 - let parent_ro_sid_maybe = parent_data.ro_sid; 242 - let parent_rq_sid_maybe = parent_data.rq_sid; 243 - parent_data.is_qo_leaf = false; 244 - parent_data.is_rq_leaf = false; 245 - 246 - let qo_sid: SubgraphId; 247 - 248 - // parent is a quote root iff qo_sid is None. we defer creating a subgraph until we have a parent/child 249 - // relationship, so we have to create it here 250 - // if parent_data.ro_sid.is_none() && !self.source_subgraphs.contains_key(&reply_to.target) { 251 - if parent_data.qo_sid.is_none() { 252 - let post_ids = vec![target.clone()]; 253 - qo_sid = self.make_subgraph( post_ids, SubgraphType::Quote, 1, 1, 1); 254 - let parent_post = self.posts.get_mut(target).unwrap(); 255 - parent_post.qo_sid = Some(qo_sid); 256 - // don't insert RQ subgraph yet. only do it if it's actually different from the R subgraph, 257 - // which happens if some post in the R subgraph gets quoted 258 - } else { 259 - qo_sid = parent_data.qo_sid.unwrap(); 260 - } 261 - 262 - 263 - // if we quoted a post in reply subgraph and the corresponding RQ graph doesnt exist 264 - let mut rq_sid_maybe: Option<SubgraphId> = None; 265 - if parent_ro_sid_maybe.is_some() && parent_rq_sid_maybe.is_none() { 266 - let ro_sid = parent_ro_sid_maybe.unwrap(); 267 - let ro_subgraph = self.subgraphs.get(&ro_sid).expect("Expected reply subgraph"); 268 - let post_ids = ro_subgraph.sources.clone(); 269 - let sid = self.make_subgraph(post_ids, SubgraphType::ReplyQuote, ro_subgraph.size, ro_subgraph.max_width, ro_subgraph.max_depth); 270 - rq_sid_maybe = Some(sid); 271 - 272 - for node in self.subgraph_nodes.get(&ro_sid).expect("Expected subgraph nodes") { 273 - let post = self.posts.get_mut(node).unwrap(); 274 - post.rq_sid = rq_sid_maybe; 275 - } 276 - } else if parent_rq_sid_maybe.is_some() { 277 - rq_sid_maybe = parent_rq_sid_maybe; 278 - } 279 - 280 - 281 - let tree_data = BskyPostGraphData { 282 - ro_sid: None, 283 - qo_sid: Some(qo_sid), 284 - rq_sid: rq_sid_maybe, 285 - ro_depth: 1, 286 - qo_depth: qo_depth, 287 - rq_max_depth: rq_max_depth, 288 - is_ro_leaf: true, 289 - is_qo_leaf: true, 290 - is_rq_leaf: true 291 - }; 292 - 293 - self.posts.insert(record.id.clone(), tree_data); 294 - 295 - self.subgraph_nodes.entry(qo_sid).and_modify(|e| e.push(record.id.clone())); 296 - 297 - // the new post we just inserted is part of at most two subgraphs: 298 - // - reply subgraph 299 - // - reply-quote subgraph 300 - // find each of these, if they exist, and update the appropriate stats 301 - 302 - let qo_subgraph = self.subgraphs 303 - .get_mut(&qo_sid) 304 - .expect("Expected reply subgraph to exist for reply"); 305 - 306 - qo_subgraph.size += 1; 307 - qo_subgraph.max_depth = qo_subgraph.max_depth.max(qo_depth); 308 - if !parent_was_qo_leaf { 309 - qo_subgraph.max_width += 1; 310 - } 311 - 312 - if let Some(rq_sid) = rq_sid_maybe { 313 - let rq_subgraph = self.subgraphs 314 - .get_mut(&rq_sid) 315 - .expect("Expected reply-quote subgraph to exist for reply"); 316 - 317 - rq_subgraph.size += 1; 318 - rq_subgraph.max_depth = rq_subgraph.max_depth.max(rq_max_depth); 319 - if !parent_was_rq_leaf { 320 - rq_subgraph.max_width += 1; 321 - } 322 - } 323 - 324 - } else { 325 - let reply_to = record.reply_to.as_ref().unwrap(); 326 - let quote_target = record.quote_of.as_ref().unwrap(); 327 - 328 - if !self.posts.contains_key(&reply_to.target) || !self.posts.contains_key(quote_target) { 329 - self.pending.insert(record.clone()); 330 - return; 331 - } 332 - 333 - panic!("TODO"); 334 - } 335 - } 336 - } 337 - 338 - #[cfg(test)] 339 - mod tests { 340 - use super::{BskyGraphData, BskyPostId, BskyPostRecord, BskyPostReplyTo, SubgraphType}; 341 - 342 - #[test] 343 - fn test_two_roots() { 344 - let mut data = BskyGraphData::new(); 345 - let id = BskyPostId::from("a", "1"); 346 - let id1 = id.clone(); 347 - data.ingest_record(BskyPostRecord { id, reply_to: None, quote_of: None }); 348 - 349 - let id = BskyPostId::from("b", "2"); 350 - let id2 = id.clone(); 351 - data.ingest_record(BskyPostRecord { id, reply_to: None, quote_of: None }); 352 - 353 - assert_eq!(data.posts.len(), 2); 354 - assert_eq!(data.pending.len(), 0); 355 - assert_eq!(data.subgraphs.len(), 0); 356 - 357 - let post1 = data.posts.get(&id1); 358 - assert!(post1.is_some()); 359 - let post = post1.unwrap(); 360 - assert!(post.ro_sid.is_none()); 361 - assert!(post.qo_sid.is_none()); 362 - assert!(post.rq_sid.is_none()); 363 - assert_eq!(post.ro_depth, 1); 364 - assert_eq!(post.qo_depth, 1); 365 - assert_eq!(post.rq_max_depth, 1); 366 - assert!(post.is_ro_leaf); 367 - assert!(post.is_qo_leaf); 368 - assert!(post.is_rq_leaf); 369 - 370 - let post2 = data.posts.get(&id2); 371 - assert!(post2.is_some()); 372 - let post = post2.unwrap(); 373 - assert!(post.ro_sid.is_none()); 374 - assert!(post.qo_sid.is_none()); 375 - assert!(post.rq_sid.is_none()); 376 - assert_eq!(post.ro_depth, 1); 377 - assert_eq!(post.qo_depth, 1); 378 - assert_eq!(post.rq_max_depth, 1); 379 - assert!(post.is_ro_leaf); 380 - assert!(post.is_qo_leaf); 381 - assert!(post.is_rq_leaf); 382 - } 383 - 384 - #[test] 385 - fn test_simple_reply_thread() { 386 - let mut data = BskyGraphData::new(); 387 - 388 - let id1 = BskyPostId::from("a", "1"); 389 - let id2 = BskyPostId::from("b", "2"); 390 - let id3 = BskyPostId::from("a", "2"); 391 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 392 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 393 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: Some(BskyPostReplyTo { target: id2.clone(), root: id1.clone() }), quote_of: None }); 394 - 395 - assert_eq!(data.posts.len(), 3); 396 - assert_eq!(data.pending.len(), 0); 397 - assert_eq!(data.subgraphs.len(), 1); 398 - 399 - let post1 = data.posts.get(&id1); 400 - assert!(post1.is_some()); 401 - let post = post1.unwrap(); 402 - assert!(post.ro_sid.is_some()); 403 - assert_eq!(post.ro_sid.unwrap(), 1); 404 - assert!(post.qo_sid.is_none()); 405 - assert!(post.rq_sid.is_none()); 406 - assert_eq!(post.ro_depth, 1); 407 - assert_eq!(post.qo_depth, 1); 408 - assert_eq!(post.rq_max_depth, 1); 409 - assert!(!post.is_ro_leaf); 410 - assert!(post.is_qo_leaf); 411 - assert!(!post.is_rq_leaf); 412 - 413 - let post2 = data.posts.get(&id2); 414 - assert!(post2.is_some()); 415 - let post = post2.unwrap(); 416 - assert!(post.ro_sid.is_some()); 417 - assert_eq!(post.ro_sid.unwrap(), 1); 418 - assert!(post.qo_sid.is_none()); 419 - assert!(post.rq_sid.is_none()); 420 - assert_eq!(post.ro_depth, 2); 421 - assert_eq!(post.qo_depth, 1); 422 - assert_eq!(post.rq_max_depth, 2); 423 - assert!(!post.is_ro_leaf); 424 - assert!(post.is_qo_leaf); 425 - assert!(!post.is_rq_leaf); 426 - 427 - let post3 = data.posts.get(&id3); 428 - assert!(post3.is_some()); 429 - let post = post3.unwrap(); 430 - assert!(post.ro_sid.is_some()); 431 - assert_eq!(post.ro_sid.unwrap(), 1); 432 - assert!(post.qo_sid.is_none()); 433 - assert!(post.rq_sid.is_none()); 434 - assert_eq!(post.ro_depth, 3); 435 - assert_eq!(post.qo_depth, 1); 436 - assert_eq!(post.rq_max_depth, 3); 437 - assert!(post.is_ro_leaf); 438 - assert!(post.is_qo_leaf); 439 - assert!(post.is_rq_leaf); 440 - 441 - let sid = 1; 442 - let sg_maybe = data.subgraphs.get(&sid); 443 - assert!(sg_maybe.is_some()); 444 - let sg = sg_maybe.unwrap(); 445 - assert_eq!(sg.ty, SubgraphType::Reply); 446 - assert_eq!(sg.sources.len(), 1); 447 - assert_eq!(&sg.sources[0], &id1); 448 - assert_eq!(sg.size, 3); 449 - assert_eq!(sg.max_width, 1); 450 - assert_eq!(sg.max_depth, 3); 451 - } 452 - 453 - #[test] 454 - fn test_simple_quote_thread() { 455 - let mut data = BskyGraphData::new(); 456 - 457 - let id1 = BskyPostId::from("a", "1"); 458 - let id2 = BskyPostId::from("b", "2"); 459 - let id3 = BskyPostId::from("a", "2"); 460 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 461 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 462 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: None, quote_of: Some(id2.clone()) }); 463 - 464 - assert_eq!(data.posts.len(), 3); 465 - assert_eq!(data.pending.len(), 0); 466 - assert_eq!(data.subgraphs.len(), 1); 467 - 468 - let post1 = data.posts.get(&id1); 469 - assert!(post1.is_some()); 470 - let post = post1.unwrap(); 471 - assert!(post.ro_sid.is_none()); 472 - assert!(post.qo_sid.is_some()); 473 - assert_eq!(post.qo_sid.unwrap(), 1); 474 - assert!(post.rq_sid.is_none()); 475 - assert_eq!(post.ro_depth, 1); 476 - assert_eq!(post.qo_depth, 1); 477 - assert_eq!(post.rq_max_depth, 1); 478 - assert!(post.is_ro_leaf); 479 - assert!(!post.is_qo_leaf); 480 - assert!(!post.is_rq_leaf); 481 - 482 - let post2 = data.posts.get(&id2); 483 - assert!(post2.is_some()); 484 - let post = post2.unwrap(); 485 - assert!(post.ro_sid.is_none()); 486 - assert!(post.qo_sid.is_some()); 487 - assert_eq!(post.qo_sid.unwrap(), 1); 488 - assert!(post.rq_sid.is_none()); 489 - 490 - assert_eq!(post.ro_depth, 1); 491 - assert_eq!(post.qo_depth, 2); 492 - assert_eq!(post.rq_max_depth, 2); 493 - assert!(post.is_ro_leaf); 494 - assert!(!post.is_qo_leaf); 495 - assert!(!post.is_rq_leaf); 496 - 497 - let post3 = data.posts.get(&id3); 498 - assert!(post3.is_some()); 499 - let post = post3.unwrap(); 500 - assert!(post.ro_sid.is_none()); 501 - assert!(post.qo_sid.is_some()); 502 - assert_eq!(post.qo_sid.unwrap(), 1); 503 - assert!(post.rq_sid.is_none()); 504 - assert_eq!(post.ro_depth, 1); 505 - assert_eq!(post.qo_depth, 3); 506 - assert_eq!(post.rq_max_depth, 3); 507 - assert!(post.is_ro_leaf); 508 - assert!(post.is_qo_leaf); 509 - assert!(post.is_rq_leaf); 510 - 511 - let sid = 1; 512 - let sg_maybe = data.subgraphs.get(&sid); 513 - assert!(sg_maybe.is_some()); 514 - let sg = sg_maybe.unwrap(); 515 - assert_eq!(sg.ty, SubgraphType::Quote); 516 - assert_eq!(sg.sources.len(), 1); 517 - assert_eq!(&sg.sources[0], &id1); 518 - assert_eq!(sg.size, 3); 519 - assert_eq!(sg.max_width, 1); 520 - assert_eq!(sg.max_depth, 3); 521 - } 522 - 523 - #[test] 524 - fn test_simple_quote_reply_graph_1() { 525 - let mut data = BskyGraphData::new(); 526 - 527 - // 1 528 - // \Q\ 529 - // 2 530 - // |R| 531 - // 3 532 - 533 - let id1 = BskyPostId::from("a", "1"); 534 - let id2 = BskyPostId::from("b", "2"); 535 - let id3 = BskyPostId::from("a", "2"); 536 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 537 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 538 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: Some(BskyPostReplyTo { target: id2.clone(), root: id2.clone() }), quote_of: None }); 539 - 540 - assert_eq!(data.posts.len(), 3); 541 - assert_eq!(data.pending.len(), 0); 542 - assert_eq!(data.subgraphs.len(), 3); 543 - 544 - let post1 = data.posts.get(&id1); 545 - assert!(post1.is_some()); 546 - let post = post1.unwrap(); 547 - assert!(post.ro_sid.is_none()); 548 - assert!(post.qo_sid.is_some()); 549 - assert_eq!(post.qo_sid.unwrap(), 1); 550 - assert!(post.rq_sid.is_some()); 551 - assert_eq!(post.rq_sid.unwrap(), 3); 552 - assert_eq!(post.ro_depth, 1); 553 - assert_eq!(post.qo_depth, 1); 554 - assert_eq!(post.rq_max_depth, 1); 555 - assert!(post.is_ro_leaf); 556 - assert!(!post.is_qo_leaf); 557 - assert!(!post.is_rq_leaf); 558 - 559 - let post2 = data.posts.get(&id2); 560 - assert!(post2.is_some()); 561 - let post = post2.unwrap(); 562 - assert!(post.ro_sid.is_some()); 563 - assert_eq!(post.ro_sid.unwrap(), 2); 564 - assert!(post.qo_sid.is_some()); 565 - assert_eq!(post.qo_sid.unwrap(), 1); 566 - assert!(post.rq_sid.is_some()); 567 - assert_eq!(post.rq_sid.unwrap(), 3); 568 - assert_eq!(post.ro_depth, 1); 569 - assert_eq!(post.qo_depth, 2); 570 - assert_eq!(post.rq_max_depth, 2); 571 - assert!(!post.is_ro_leaf); 572 - assert!(post.is_qo_leaf); 573 - assert!(!post.is_rq_leaf); 574 - 575 - let post3 = data.posts.get(&id3); 576 - assert!(post3.is_some()); 577 - let post = post3.unwrap(); 578 - assert!(post.ro_sid.is_some()); 579 - assert_eq!(post.ro_sid.unwrap(), 2); 580 - assert!(post.qo_sid.is_none()); 581 - assert!(post.rq_sid.is_some()); 582 - assert_eq!(post.rq_sid.unwrap(), 3); 583 - assert_eq!(post.ro_depth, 2); 584 - assert_eq!(post.qo_depth, 1); 585 - assert_eq!(post.rq_max_depth, 3); 586 - assert!(post.is_ro_leaf); 587 - assert!(post.is_qo_leaf); 588 - assert!(post.is_rq_leaf); 589 - 590 - let sid = 1; 591 - let sg_maybe = data.subgraphs.get(&sid); 592 - assert!(sg_maybe.is_some()); 593 - let sg = sg_maybe.unwrap(); 594 - assert_eq!(sg.ty, SubgraphType::Quote); 595 - assert_eq!(sg.sources.len(), 1); 596 - assert_eq!(&sg.sources[0], &id1); 597 - assert_eq!(sg.size, 2); 598 - assert_eq!(sg.max_width, 1); 599 - assert_eq!(sg.max_depth, 2); 600 - 601 - let sid = 2; 602 - let sg_maybe = data.subgraphs.get(&sid); 603 - assert!(sg_maybe.is_some()); 604 - let sg = sg_maybe.unwrap(); 605 - assert_eq!(sg.ty, SubgraphType::Reply); 606 - assert_eq!(sg.sources.len(), 1); 607 - assert_eq!(&sg.sources[0], &id2); 608 - assert_eq!(sg.size, 2); 609 - assert_eq!(sg.max_width, 1); 610 - assert_eq!(sg.max_depth, 2); 611 - 612 - let sid = 3; 613 - let sg_maybe = data.subgraphs.get(&sid); 614 - assert!(sg_maybe.is_some()); 615 - let sg = sg_maybe.unwrap(); 616 - assert_eq!(sg.ty, SubgraphType::ReplyQuote); 617 - assert_eq!(sg.sources.len(), 1); 618 - assert_eq!(&sg.sources[0], &id1); 619 - assert_eq!(sg.size, 3); 620 - assert_eq!(sg.max_width, 1); 621 - assert_eq!(sg.max_depth, 3); 622 - 623 - } 624 - 625 - #[test] 626 - fn test_simple_quote_reply_graph_2() { 627 - let mut data = BskyGraphData::new(); 628 - 629 - // 1 630 - // |R| 631 - // 2 632 - // \Q\ 633 - // 3 634 - 635 - let id1 = BskyPostId::from("a", "1"); 636 - let id2 = BskyPostId::from("b", "2"); 637 - let id3 = BskyPostId::from("a", "2"); 638 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 639 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 640 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: None, quote_of: Some(id2.clone()) }); 641 - 642 - assert_eq!(data.posts.len(), 3); 643 - assert_eq!(data.pending.len(), 0); 644 - assert_eq!(data.subgraphs.len(), 3); 645 - 646 - let post1 = data.posts.get(&id1); 647 - assert!(post1.is_some()); 648 - let post = post1.unwrap(); 649 - assert!(post.ro_sid.is_some()); 650 - assert_eq!(post.ro_sid.unwrap(), 1); 651 - assert!(post.qo_sid.is_none()); 652 - assert!(post.rq_sid.is_some()); 653 - assert_eq!(post.rq_sid.unwrap(), 3); 654 - assert_eq!(post.ro_depth, 1); 655 - assert_eq!(post.qo_depth, 1); 656 - assert_eq!(post.rq_max_depth, 1); 657 - assert!(!post.is_ro_leaf); 658 - assert!(post.is_qo_leaf); 659 - assert!(!post.is_rq_leaf); 660 - 661 - let post2 = data.posts.get(&id2); 662 - assert!(post2.is_some()); 663 - let post = post2.unwrap(); 664 - assert!(post.ro_sid.is_some()); 665 - assert_eq!(post.ro_sid.unwrap(), 1); 666 - assert!(post.qo_sid.is_some()); 667 - assert_eq!(post.qo_sid.unwrap(), 2); 668 - assert!(post.rq_sid.is_some()); 669 - assert_eq!(post.rq_sid.unwrap(), 3); 670 - assert_eq!(post.ro_depth, 2); 671 - assert_eq!(post.qo_depth, 1); 672 - assert_eq!(post.rq_max_depth, 2); 673 - assert!(post.is_ro_leaf); 674 - assert!(!post.is_qo_leaf); 675 - assert!(!post.is_rq_leaf); 676 - 677 - let post3 = data.posts.get(&id3); 678 - assert!(post3.is_some()); 679 - let post = post3.unwrap(); 680 - assert!(post.ro_sid.is_none()); 681 - assert!(post.qo_sid.is_some()); 682 - assert_eq!(post.qo_sid.unwrap(), 2); 683 - assert!(post.rq_sid.is_some()); 684 - assert_eq!(post.rq_sid.unwrap(), 3); 685 - assert_eq!(post.ro_depth, 1); 686 - assert_eq!(post.qo_depth, 2); 687 - assert_eq!(post.rq_max_depth, 3); 688 - assert!(post.is_ro_leaf); 689 - assert!(post.is_qo_leaf); 690 - assert!(post.is_rq_leaf); 691 - 692 - let sid = 1; 693 - let sg_maybe = data.subgraphs.get(&sid); 694 - assert!(sg_maybe.is_some()); 695 - let sg = sg_maybe.unwrap(); 696 - assert_eq!(sg.ty, SubgraphType::Reply); 697 - assert_eq!(sg.sources.len(), 1); 698 - assert_eq!(&sg.sources[0], &id1); 699 - assert_eq!(sg.size, 2); 700 - assert_eq!(sg.max_width, 1); 701 - assert_eq!(sg.max_depth, 2); 702 - 703 - let sid = 2; 704 - let sg_maybe = data.subgraphs.get(&sid); 705 - assert!(sg_maybe.is_some()); 706 - let sg = sg_maybe.unwrap(); 707 - assert_eq!(sg.ty, SubgraphType::Quote); 708 - assert_eq!(sg.sources.len(), 1); 709 - assert_eq!(&sg.sources[0], &id2); 710 - assert_eq!(sg.size, 2); 711 - assert_eq!(sg.max_width, 1); 712 - assert_eq!(sg.max_depth, 2); 713 - 714 - let sid = 3; 715 - let sg_maybe = data.subgraphs.get(&sid); 716 - assert!(sg_maybe.is_some()); 717 - let sg = sg_maybe.unwrap(); 718 - assert_eq!(sg.ty, SubgraphType::ReplyQuote); 719 - assert_eq!(sg.sources.len(), 1); 720 - assert_eq!(&sg.sources[0], &id1); 721 - assert_eq!(sg.size, 3); 722 - assert_eq!(sg.max_width, 1); 723 - assert_eq!(sg.max_depth, 3); 724 - } 725 - 726 - #[test] 727 - fn test_simple_quote_reply_graph_3() { 728 - let mut data = BskyGraphData::new(); 729 - 730 - // 1 731 - // \Q\ 732 - // |R| 2 733 - // 3 734 - 735 - let id1 = BskyPostId::from("a", "1"); 736 - let id2 = BskyPostId::from("b", "2"); 737 - let id3 = BskyPostId::from("a", "2"); 738 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 739 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 740 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 741 - 742 - assert_eq!(data.posts.len(), 3); 743 - assert_eq!(data.pending.len(), 0); 744 - assert_eq!(data.subgraphs.len(), 3); 745 - 746 - let post1 = data.posts.get(&id1); 747 - assert!(post1.is_some()); 748 - let post = post1.unwrap(); 749 - assert!(post.ro_sid.is_some()); 750 - assert_eq!(post.ro_sid.unwrap(), 2); 751 - assert!(post.qo_sid.is_some()); 752 - assert_eq!(post.qo_sid.unwrap(), 1); 753 - assert!(post.rq_sid.is_some()); 754 - assert_eq!(post.rq_sid.unwrap(), 3); 755 - assert_eq!(post.ro_depth, 1); 756 - assert_eq!(post.qo_depth, 1); 757 - assert_eq!(post.rq_max_depth, 1); 758 - assert!(!post.is_ro_leaf); 759 - assert!(!post.is_qo_leaf); 760 - assert!(!post.is_rq_leaf); 761 - 762 - let post2 = data.posts.get(&id2); 763 - assert!(post2.is_some()); 764 - let post = post2.unwrap(); 765 - assert!(post.ro_sid.is_none()); 766 - assert!(post.qo_sid.is_some()); 767 - assert_eq!(post.qo_sid.unwrap(), 1); 768 - assert!(post.rq_sid.is_some()); 769 - assert_eq!(post.rq_sid.unwrap(), 3); 770 - assert_eq!(post.ro_depth, 1); 771 - assert_eq!(post.qo_depth, 2); 772 - assert_eq!(post.rq_max_depth, 2); 773 - assert!(post.is_ro_leaf); 774 - assert!(post.is_qo_leaf); 775 - assert!(post.is_rq_leaf); 776 - 777 - let post3 = data.posts.get(&id3); 778 - assert!(post3.is_some()); 779 - let post = post3.unwrap(); 780 - assert!(post.ro_sid.is_some()); 781 - assert_eq!(post.ro_sid.unwrap(), 2); 782 - assert!(post.qo_sid.is_none()); 783 - assert!(post.rq_sid.is_some()); 784 - assert_eq!(post.rq_sid.unwrap(), 3); 785 - assert_eq!(post.ro_depth, 2); 786 - assert_eq!(post.qo_depth, 1); 787 - assert_eq!(post.rq_max_depth, 2); 788 - assert!(post.is_ro_leaf); 789 - assert!(post.is_qo_leaf); 790 - assert!(post.is_rq_leaf); 791 - 792 - let sid = 1; 793 - let sg_maybe = data.subgraphs.get(&sid); 794 - assert!(sg_maybe.is_some()); 795 - let sg = sg_maybe.unwrap(); 796 - assert_eq!(sg.ty, SubgraphType::Quote); 797 - assert_eq!(sg.sources.len(), 1); 798 - assert_eq!(&sg.sources[0], &id1); 799 - assert_eq!(sg.size, 2); 800 - assert_eq!(sg.max_width, 1); 801 - assert_eq!(sg.max_depth, 2); 802 - 803 - let sid = 2; 804 - let sg_maybe = data.subgraphs.get(&sid); 805 - assert!(sg_maybe.is_some()); 806 - let sg = sg_maybe.unwrap(); 807 - assert_eq!(sg.ty, SubgraphType::Reply); 808 - assert_eq!(sg.sources.len(), 1); 809 - assert_eq!(&sg.sources[0], &id1); 810 - assert_eq!(sg.size, 2); 811 - assert_eq!(sg.max_width, 1); 812 - assert_eq!(sg.max_depth, 2); 813 - 814 - let sid = 3; 815 - let sg_maybe = data.subgraphs.get(&sid); 816 - assert!(sg_maybe.is_some()); 817 - let sg = sg_maybe.unwrap(); 818 - assert_eq!(sg.ty, SubgraphType::ReplyQuote); 819 - assert_eq!(sg.sources.len(), 1); 820 - assert_eq!(&sg.sources[0], &id1); 821 - assert_eq!(sg.size, 3); 822 - assert_eq!(sg.max_width, 2); 823 - assert_eq!(sg.max_depth, 2); 824 - } 825 - 826 - #[test] 827 - fn test_simple_quote_reply_graph_4() { 828 - let mut data = BskyGraphData::new(); 829 - 830 - // 1 831 - // \Q\ 832 - // |R| 3 833 - // 2 834 - 835 - let id1 = BskyPostId::from("a", "1"); 836 - let id2 = BskyPostId::from("b", "2"); 837 - let id3 = BskyPostId::from("a", "2"); 838 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 839 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 840 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 841 - 842 - assert_eq!(data.posts.len(), 3); 843 - assert_eq!(data.pending.len(), 0); 844 - assert_eq!(data.subgraphs.len(), 3); 845 - 846 - let post1 = data.posts.get(&id1); 847 - assert!(post1.is_some()); 848 - let post = post1.unwrap(); 849 - assert!(post.ro_sid.is_some()); 850 - assert_eq!(post.ro_sid.unwrap(), 1); 851 - assert!(post.qo_sid.is_some()); 852 - assert_eq!(post.qo_sid.unwrap(), 2); 853 - assert!(post.rq_sid.is_some()); 854 - assert_eq!(post.rq_sid.unwrap(), 3); 855 - assert_eq!(post.ro_depth, 1); 856 - assert_eq!(post.qo_depth, 1); 857 - assert_eq!(post.rq_max_depth, 1); 858 - assert!(!post.is_ro_leaf); 859 - assert!(!post.is_qo_leaf); 860 - assert!(!post.is_rq_leaf); 861 - 862 - let post2 = data.posts.get(&id2); 863 - assert!(post2.is_some()); 864 - let post = post2.unwrap(); 865 - assert!(post.ro_sid.is_some()); 866 - assert_eq!(post.ro_sid.unwrap(), 1); 867 - assert!(post.qo_sid.is_none()); 868 - assert!(post.rq_sid.is_some()); 869 - assert_eq!(post.rq_sid.unwrap(), 3); 870 - assert_eq!(post.ro_depth, 2); 871 - assert_eq!(post.qo_depth, 1); 872 - assert_eq!(post.rq_max_depth, 2); 873 - assert!(post.is_ro_leaf); 874 - assert!(post.is_qo_leaf); 875 - assert!(post.is_rq_leaf); 876 - 877 - let post3 = data.posts.get(&id3); 878 - assert!(post3.is_some()); 879 - let post = post3.unwrap(); 880 - assert!(post.ro_sid.is_none()); 881 - assert!(post.qo_sid.is_some()); 882 - assert_eq!(post.qo_sid.unwrap(), 2); 883 - assert!(post.rq_sid.is_some()); 884 - assert_eq!(post.rq_sid.unwrap(), 3); 885 - assert_eq!(post.ro_depth, 1); 886 - assert_eq!(post.qo_depth, 2); 887 - assert_eq!(post.rq_max_depth, 2); 888 - assert!(post.is_ro_leaf); 889 - assert!(post.is_qo_leaf); 890 - assert!(post.is_rq_leaf); 891 - 892 - let sid = 1; 893 - let sg_maybe = data.subgraphs.get(&sid); 894 - assert!(sg_maybe.is_some()); 895 - let sg = sg_maybe.unwrap(); 896 - assert_eq!(sg.ty, SubgraphType::Reply); 897 - assert_eq!(sg.sources.len(), 1); 898 - assert_eq!(&sg.sources[0], &id1); 899 - assert_eq!(sg.size, 2); 900 - assert_eq!(sg.max_width, 1); 901 - assert_eq!(sg.max_depth, 2); 902 - 903 - let sid = 2; 904 - let sg_maybe = data.subgraphs.get(&sid); 905 - assert!(sg_maybe.is_some()); 906 - let sg = sg_maybe.unwrap(); 907 - assert_eq!(sg.ty, SubgraphType::Quote); 908 - assert_eq!(sg.sources.len(), 1); 909 - assert_eq!(&sg.sources[0], &id1); 910 - assert_eq!(sg.size, 2); 911 - assert_eq!(sg.max_width, 1); 912 - assert_eq!(sg.max_depth, 2); 913 - 914 - let sid = 3; 915 - let sg_maybe = data.subgraphs.get(&sid); 916 - assert!(sg_maybe.is_some()); 917 - let sg = sg_maybe.unwrap(); 918 - assert_eq!(sg.ty, SubgraphType::ReplyQuote); 919 - assert_eq!(sg.sources.len(), 1); 920 - assert_eq!(&sg.sources[0], &id1); 921 - assert_eq!(sg.size, 3); 922 - assert_eq!(sg.max_width, 2); 923 - assert_eq!(sg.max_depth, 2); 924 - } 925 - 926 - #[test] 927 - fn test_3_root_replies() { 928 - let mut data = BskyGraphData::new(); 929 - 930 - let id1 = BskyPostId::from("a", "1"); 931 - let id2 = BskyPostId::from("b", "2"); 932 - let id3 = BskyPostId::from("a", "2"); 933 - let id4 = BskyPostId::from("c", "1"); 934 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 935 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 936 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 937 - data.ingest_record(BskyPostRecord { id: id4.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 938 - 939 - assert_eq!(data.posts.len(), 4); 940 - assert_eq!(data.pending.len(), 0); 941 - assert_eq!(data.subgraphs.len(), 1); 942 - 943 - let post2 = data.posts.get(&id2); 944 - assert!(post2.is_some()); 945 - let post = post2.unwrap(); 946 - assert!(post.ro_sid.is_some()); 947 - assert_eq!(post.ro_sid.unwrap(), 1); 948 - assert!(post.qo_sid.is_none()); 949 - assert!(post.rq_sid.is_none()); 950 - assert_eq!(post.ro_depth, 2); 951 - assert_eq!(post.qo_depth, 1); 952 - assert_eq!(post.rq_max_depth, 2); 953 - assert!(post.is_ro_leaf); 954 - assert!(post.is_qo_leaf); 955 - assert!(post.is_rq_leaf); 956 - 957 - let post3 = data.posts.get(&id3); 958 - assert!(post3.is_some()); 959 - let post = post3.unwrap(); 960 - assert!(post.ro_sid.is_some()); 961 - assert_eq!(post.ro_sid.unwrap(), 1); 962 - assert!(post.qo_sid.is_none()); 963 - assert!(post.rq_sid.is_none()); 964 - assert_eq!(post.ro_depth, 2); 965 - assert_eq!(post.qo_depth, 1); 966 - assert_eq!(post.rq_max_depth, 2); 967 - assert!(post.is_ro_leaf); 968 - assert!(post.is_qo_leaf); 969 - assert!(post.is_rq_leaf); 970 - 971 - let post4 = data.posts.get(&id4); 972 - assert!(post4.is_some()); 973 - let post = post4.unwrap(); 974 - assert!(post.ro_sid.is_some()); 975 - assert_eq!(post.ro_sid.unwrap(), 1); 976 - assert!(post.qo_sid.is_none()); 977 - assert!(post.rq_sid.is_none()); 978 - assert_eq!(post.ro_depth, 2); 979 - assert_eq!(post.qo_depth, 1); 980 - assert_eq!(post.rq_max_depth, 2); 981 - assert!(post.is_ro_leaf); 982 - assert!(post.is_qo_leaf); 983 - assert!(post.is_rq_leaf); 984 - 985 - let sid = 1; 986 - let sg_maybe = data.subgraphs.get(&sid); 987 - assert!(sg_maybe.is_some()); 988 - let sg = sg_maybe.unwrap(); 989 - assert_eq!(sg.ty, SubgraphType::Reply); 990 - assert_eq!(sg.sources.len(), 1); 991 - assert_eq!(&sg.sources[0], &id1); 992 - assert_eq!(sg.size, 4); 993 - assert_eq!(sg.max_width, 3); 994 - assert_eq!(sg.max_depth, 2); 995 - } 996 - 997 - #[test] 998 - fn test_3_root_quotes() { 999 - let mut data = BskyGraphData::new(); 1000 - 1001 - let id1 = BskyPostId::from("a", "1"); 1002 - let id2 = BskyPostId::from("b", "2"); 1003 - let id3 = BskyPostId::from("a", "2"); 1004 - let id4 = BskyPostId::from("c", "1"); 1005 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 1006 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 1007 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 1008 - data.ingest_record(BskyPostRecord { id: id4.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 1009 - 1010 - assert_eq!(data.posts.len(), 4); 1011 - assert_eq!(data.pending.len(), 0); 1012 - assert_eq!(data.subgraphs.len(), 1); 1013 - 1014 - let post2 = data.posts.get(&id2); 1015 - assert!(post2.is_some()); 1016 - let post = post2.unwrap(); 1017 - assert!(post.ro_sid.is_none()); 1018 - assert!(post.qo_sid.is_some()); 1019 - assert_eq!(post.qo_sid.unwrap(), 1); 1020 - assert!(post.rq_sid.is_none()); 1021 - assert_eq!(post.ro_depth, 1); 1022 - assert_eq!(post.qo_depth, 2); 1023 - assert_eq!(post.rq_max_depth, 2); 1024 - assert!(post.is_ro_leaf); 1025 - assert!(post.is_qo_leaf); 1026 - assert!(post.is_rq_leaf); 1027 - 1028 - let post3 = data.posts.get(&id3); 1029 - assert!(post3.is_some()); 1030 - let post = post3.unwrap(); 1031 - assert!(post.ro_sid.is_none()); 1032 - assert!(post.qo_sid.is_some()); 1033 - assert_eq!(post.qo_sid.unwrap(), 1); 1034 - assert!(post.rq_sid.is_none()); 1035 - assert_eq!(post.ro_depth, 1); 1036 - assert_eq!(post.qo_depth, 2); 1037 - assert_eq!(post.rq_max_depth, 2); 1038 - assert!(post.is_ro_leaf); 1039 - assert!(post.is_qo_leaf); 1040 - assert!(post.is_rq_leaf); 1041 - 1042 - let post4 = data.posts.get(&id4); 1043 - assert!(post4.is_some()); 1044 - let post = post4.unwrap(); 1045 - assert!(post.ro_sid.is_none()); 1046 - assert!(post.qo_sid.is_some()); 1047 - assert_eq!(post.qo_sid.unwrap(), 1); 1048 - assert!(post.rq_sid.is_none()); 1049 - assert_eq!(post.ro_depth, 1); 1050 - assert_eq!(post.qo_depth, 2); 1051 - assert_eq!(post.rq_max_depth, 2); 1052 - assert!(post.is_ro_leaf); 1053 - assert!(post.is_qo_leaf); 1054 - assert!(post.is_rq_leaf); 1055 - 1056 - let sid = 1; 1057 - let sg_maybe = data.subgraphs.get(&sid); 1058 - assert!(sg_maybe.is_some()); 1059 - let sg = sg_maybe.unwrap(); 1060 - assert_eq!(sg.ty, SubgraphType::Quote); 1061 - assert_eq!(sg.sources.len(), 1); 1062 - assert_eq!(&sg.sources[0], &id1); 1063 - assert_eq!(sg.size, 4); 1064 - assert_eq!(sg.max_width, 3); 1065 - assert_eq!(sg.max_depth, 2); 1066 - } 1067 - 1068 - #[test] 1069 - fn test_3_non_root_replies() { 1070 - let mut data = BskyGraphData::new(); 1071 - 1072 - let id1 = BskyPostId::from("a", "1"); 1073 - let id2 = BskyPostId::from("b", "1"); 1074 - let id3 = BskyPostId::from("a", "2"); 1075 - let id4 = BskyPostId::from("b", "2"); 1076 - let id5 = BskyPostId::from("c", "1"); 1077 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 1078 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), quote_of: None }); 1079 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: Some(BskyPostReplyTo { target: id2.clone(), root: id1.clone() }), quote_of: None }); 1080 - data.ingest_record(BskyPostRecord { id: id4.clone(), reply_to: Some(BskyPostReplyTo { target: id2.clone(), root: id1.clone() }), quote_of: None }); 1081 - data.ingest_record(BskyPostRecord { id: id5.clone(), reply_to: Some(BskyPostReplyTo { target: id2.clone(), root: id1.clone() }), quote_of: None }); 1082 - 1083 - assert_eq!(data.posts.len(), 5); 1084 - assert_eq!(data.pending.len(), 0); 1085 - assert_eq!(data.subgraphs.len(), 1); 1086 - 1087 - let post2 = data.posts.get(&id2); 1088 - assert!(post2.is_some()); 1089 - let post = post2.unwrap(); 1090 - assert!(post.ro_sid.is_some()); 1091 - assert_eq!(post.ro_sid.unwrap(), 1); 1092 - assert!(post.qo_sid.is_none()); 1093 - assert!(post.rq_sid.is_none()); 1094 - assert_eq!(post.ro_depth, 2); 1095 - assert_eq!(post.qo_depth, 1); 1096 - assert_eq!(post.rq_max_depth, 2); 1097 - assert!(!post.is_ro_leaf); 1098 - assert!(post.is_qo_leaf); 1099 - assert!(!post.is_rq_leaf); 1100 - 1101 - let post3 = data.posts.get(&id3); 1102 - assert!(post3.is_some()); 1103 - let post = post3.unwrap(); 1104 - assert!(post.ro_sid.is_some()); 1105 - assert_eq!(post.ro_sid.unwrap(), 1); 1106 - assert!(post.qo_sid.is_none()); 1107 - assert!(post.rq_sid.is_none()); 1108 - assert_eq!(post.ro_depth, 3); 1109 - assert_eq!(post.qo_depth, 1); 1110 - assert_eq!(post.rq_max_depth, 3); 1111 - assert!(post.is_ro_leaf); 1112 - assert!(post.is_qo_leaf); 1113 - assert!(post.is_rq_leaf); 1114 - 1115 - let post4 = data.posts.get(&id4); 1116 - assert!(post4.is_some()); 1117 - let post = post4.unwrap(); 1118 - assert!(post.ro_sid.is_some()); 1119 - assert_eq!(post.ro_sid.unwrap(), 1); 1120 - assert!(post.qo_sid.is_none()); 1121 - assert!(post.rq_sid.is_none()); 1122 - assert_eq!(post.ro_depth, 3); 1123 - assert_eq!(post.qo_depth, 1); 1124 - assert_eq!(post.rq_max_depth, 3); 1125 - assert!(post.is_ro_leaf); 1126 - assert!(post.is_qo_leaf); 1127 - assert!(post.is_rq_leaf); 1128 - 1129 - let sid = 1; 1130 - let sg_maybe = data.subgraphs.get(&sid); 1131 - assert!(sg_maybe.is_some()); 1132 - let sg = sg_maybe.unwrap(); 1133 - assert_eq!(sg.ty, SubgraphType::Reply); 1134 - assert_eq!(sg.sources.len(), 1); 1135 - assert_eq!(&sg.sources[0], &id1); 1136 - assert_eq!(sg.size, 5); 1137 - assert_eq!(sg.max_width, 3); 1138 - assert_eq!(sg.max_depth, 3); 1139 - } 1140 - 1141 - #[test] 1142 - fn test_3_non_root_quotes() { 1143 - let mut data = BskyGraphData::new(); 1144 - 1145 - let id1 = BskyPostId::from("a", "1"); 1146 - let id2 = BskyPostId::from("b", "1"); 1147 - let id3 = BskyPostId::from("a", "2"); 1148 - let id4 = BskyPostId::from("b", "2"); 1149 - let id5 = BskyPostId::from("c", "1"); 1150 - data.ingest_record(BskyPostRecord { id: id1.clone(), reply_to: None, quote_of: None }); 1151 - data.ingest_record(BskyPostRecord { id: id2.clone(), reply_to: None, quote_of: Some(id1.clone()) }); 1152 - data.ingest_record(BskyPostRecord { id: id3.clone(), reply_to: None, quote_of: Some(id2.clone()) }); 1153 - data.ingest_record(BskyPostRecord { id: id4.clone(), reply_to: None, quote_of: Some(id2.clone()) }); 1154 - data.ingest_record(BskyPostRecord { id: id5.clone(), reply_to: None, quote_of: Some(id2.clone()) }); 1155 - 1156 - assert_eq!(data.posts.len(), 5); 1157 - assert_eq!(data.pending.len(), 0); 1158 - assert_eq!(data.subgraphs.len(), 1); 1159 - 1160 - let post2 = data.posts.get(&id2); 1161 - assert!(post2.is_some()); 1162 - let post = post2.unwrap(); 1163 - assert!(post.ro_sid.is_none()); 1164 - assert!(post.qo_sid.is_some()); 1165 - assert_eq!(post.qo_sid.unwrap(), 1); 1166 - assert!(post.rq_sid.is_none()); 1167 - assert_eq!(post.ro_depth, 1); 1168 - assert_eq!(post.qo_depth, 2); 1169 - assert_eq!(post.rq_max_depth, 2); 1170 - assert!(post.is_ro_leaf); 1171 - assert!(!post.is_qo_leaf); 1172 - assert!(!post.is_rq_leaf); 1173 - 1174 - let post3 = data.posts.get(&id3); 1175 - assert!(post3.is_some()); 1176 - let post = post3.unwrap(); 1177 - assert!(post.ro_sid.is_none()); 1178 - assert!(post.qo_sid.is_some()); 1179 - assert_eq!(post.qo_sid.unwrap(), 1); 1180 - assert!(post.rq_sid.is_none()); 1181 - assert_eq!(post.ro_depth, 1); 1182 - assert_eq!(post.qo_depth, 3); 1183 - assert_eq!(post.rq_max_depth, 3); 1184 - assert!(post.is_ro_leaf); 1185 - assert!(post.is_qo_leaf); 1186 - assert!(post.is_rq_leaf); 1187 - 1188 - let post4 = data.posts.get(&id4); 1189 - assert!(post4.is_some()); 1190 - let post = post4.unwrap(); 1191 - assert!(post.ro_sid.is_none()); 1192 - assert!(post.qo_sid.is_some()); 1193 - assert_eq!(post.qo_sid.unwrap(), 1); 1194 - assert!(post.rq_sid.is_none()); 1195 - assert_eq!(post.ro_depth, 1); 1196 - assert_eq!(post.qo_depth, 3); 1197 - assert_eq!(post.rq_max_depth, 3); 1198 - assert!(post.is_ro_leaf); 1199 - assert!(post.is_qo_leaf); 1200 - assert!(post.is_rq_leaf); 1201 - 1202 - let sid = 1; 1203 - let sg_maybe = data.subgraphs.get(&sid); 1204 - assert!(sg_maybe.is_some()); 1205 - let sg = sg_maybe.unwrap(); 1206 - assert_eq!(sg.ty, SubgraphType::Quote); 1207 - assert_eq!(sg.sources.len(), 1); 1208 - assert_eq!(&sg.sources[0], &id1); 1209 - assert_eq!(sg.size, 5); 1210 - assert_eq!(sg.max_width, 3); 1211 - assert_eq!(sg.max_depth, 3); 1212 - } 1213 - }
+163 -215
indexer/src/storage/mod.rs
··· 1 1 use std::{collections::HashMap, hash::Hash}; 2 2 3 - // pub mod memory; 4 - 5 - 6 3 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 7 4 struct BskyPostId { 8 5 did: String, ··· 152 149 self.union_qo(parent_idx, post_parent_idx); 153 150 }, 154 151 (Some(parent_parent), None) => { 155 - let parent_root_idx = self.find_qo(*parent_parent); 152 + let parent_root_idx = self.find_qo(*parent_parent).unwrap(); 156 153 self.parents_qo.insert(post_idx, parent_root_idx); 157 154 self.sizes_qo.entry(parent_root_idx).and_modify(|e| *e += 1); 158 155 }, ··· 179 176 true 180 177 } 181 178 182 - // find the representative node for a given index 183 - fn find_qo(&mut self, idx: UFIndex) -> UFIndex { 179 + fn find(parents: &mut HashMap<UFIndex, UFIndex>, idx: UFIndex) -> Option<UFIndex> { 184 180 let mut curr_idx = idx; 185 - let mut pa_idx = *self.parents_qo.get(&curr_idx).unwrap(); 181 + let mut pa_idx = *parents.get(&curr_idx)?; 186 182 while curr_idx != pa_idx { 187 183 curr_idx = pa_idx; 188 - pa_idx = *self.parents_qo.get(&curr_idx).unwrap(); 184 + pa_idx = *parents.get(&curr_idx).unwrap(); 189 185 } 190 186 let rep = curr_idx; 191 187 192 188 curr_idx = idx; 193 - pa_idx = *self.parents_qo.get(&curr_idx).unwrap(); 189 + pa_idx = *parents.get(&curr_idx).unwrap(); 194 190 while curr_idx != pa_idx { 195 - self.parents_qo.insert(curr_idx, rep); 191 + parents.insert(curr_idx, rep); 196 192 curr_idx = pa_idx; 197 - pa_idx = *self.parents_qo.get(&curr_idx).unwrap(); 193 + pa_idx = *parents.get(&curr_idx).unwrap(); 198 194 } 199 - curr_idx 195 + Some(curr_idx) 196 + } 197 + 198 + // find the representative node for a given index 199 + fn find_qo(&mut self, idx: UFIndex) -> Option<UFIndex> { 200 + Self::find(&mut self.parents_qo, idx) 201 + } 202 + 203 + fn find_rq(&mut self, idx: UFIndex) -> Option<UFIndex> { 204 + Self::find(&mut self.parents_rq, idx) 200 205 } 206 + 201 207 202 208 fn union_qo(&mut self, idx1: UFIndex, idx2: UFIndex) { 203 - let pa1 = self.find_qo(idx1); 204 - let pa2 = self.find_qo(idx2); 209 + let pa1 = self.find_qo(idx1).unwrap(); 210 + let pa2 = self.find_qo(idx2).unwrap(); 205 211 206 212 if pa1 == pa2 { 207 213 return; ··· 221 227 } 222 228 } 223 229 224 - fn find_rq(&mut self, idx: UFIndex) -> UFIndex { 225 - let mut curr_idx = idx; 226 - let mut pa_idx = *self.parents_rq.get(&curr_idx).unwrap(); 227 - while curr_idx != pa_idx { 228 - curr_idx = pa_idx; 229 - pa_idx = *self.parents_rq.get(&curr_idx).unwrap(); 230 - } 231 - let rep = curr_idx; 232 - 233 - curr_idx = idx; 234 - pa_idx = *self.parents_rq.get(&curr_idx).unwrap(); 235 - while curr_idx != pa_idx { 236 - self.parents_rq.insert(curr_idx, rep); 237 - curr_idx = pa_idx; 238 - pa_idx = *self.parents_rq.get(&curr_idx).unwrap(); 239 - } 240 - curr_idx 241 - } 242 - 243 230 fn union_rq(&mut self, idx1: UFIndex, idx2: UFIndex) { 244 - let pa1 = self.find_rq(idx1); 245 - let pa2 = self.find_rq(idx2); 231 + let pa1 = self.find_rq(idx1).unwrap(); 232 + let pa2 = self.find_rq(idx2).unwrap(); 246 233 247 234 if pa1 == pa2 { 248 235 return; ··· 267 254 match subgraph_type { 268 255 SubgraphType::Reply => { 269 256 // special structure of replies means all non-root nodes are direct children of root 270 - let pa = *self.parents_ro.get(&idx).unwrap(); 257 + let pa = *self.parents_ro.get(&idx)?; 271 258 self.sizes_ro.get(&pa).map(|idx| *idx) 272 259 }, 273 260 SubgraphType::Quote => { 274 - let pa = self.find_qo(idx); 261 + let pa = self.find_qo(idx)?; 275 262 self.sizes_qo.get(&pa).map(|idx| *idx) 276 263 }, 277 264 SubgraphType::ReplyQuote => { 278 - let pa = self.find_rq(idx); 265 + let pa = self.find_rq(idx)?; 279 266 self.sizes_rq.get(&pa).map(|idx| *idx) 280 267 }, 281 268 } ··· 284 271 285 272 #[cfg(test)] 286 273 mod tests { 287 - use crate::storage::SubgraphType; 274 + use itertools::Itertools; 288 275 289 - use super::{BskyPostId, BskyPostRecord, BskyPostUnionFind}; 276 + use super::{BskyPostId, BskyPostRecord, BskyPostReplyTo, BskyPostUnionFind, SubgraphType}; 290 277 291 278 #[test] 292 279 fn test_simple_reply_thread_1() { ··· 359 346 } 360 347 361 348 #[test] 362 - fn test_simple_quote_chain_1() { 363 - let mut uf = BskyPostUnionFind::new(); 364 - 349 + fn test_simple_quote_chain() { 365 350 let id1 = BskyPostId::from("a", "1"); 366 351 let id2 = BskyPostId::from("b", "1"); 367 352 let id3 = BskyPostId::from("a", "2"); 368 353 let id4 = BskyPostId::from("a", "3"); 369 - uf.ingest_post(BskyPostRecord { 370 - id: id2.clone(), 371 - reply_to: None, 372 - quote_of: Some(id1.clone()) 373 - }); 374 - uf.ingest_post(BskyPostRecord { 375 - id: id3.clone(), 376 - reply_to: None, 377 - quote_of: Some(id2.clone()) 378 - }); 379 - uf.ingest_post(BskyPostRecord { 380 - id: id4.clone(), 381 - reply_to: None, 382 - quote_of: Some(id3.clone()) 383 - }); 354 + let mut records: Vec<BskyPostRecord<BskyPostId>> = vec![ 355 + BskyPostRecord { 356 + id: id2.clone(), 357 + reply_to: None, 358 + quote_of: Some(id1.clone()) 359 + }, 360 + BskyPostRecord { 361 + id: id3.clone(), 362 + reply_to: None, 363 + quote_of: Some(id2.clone()) 364 + }, 365 + BskyPostRecord { 366 + id: id4.clone(), 367 + reply_to: None, 368 + quote_of: Some(id3.clone()) 369 + } 370 + ]; 384 371 385 - assert_eq!(uf.component_size(SubgraphType::Quote, &id1), Some(4)); 386 - assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(4)); 387 - assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(4)); 388 - assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(4)); 389 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 390 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 391 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 392 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 372 + for (i, perm) in records.iter().permutations(records.len()).enumerate() { 373 + let mut uf = BskyPostUnionFind::new(); 374 + uf.ingest_post(perm[0].clone()); 375 + uf.ingest_post(perm[1].clone()); 376 + uf.ingest_post(perm[2].clone()); 377 + assert_eq!(uf.component_size(SubgraphType::Quote, &id1), Some(4)); 378 + assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(4)); 379 + assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(4)); 380 + assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(4)); 381 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 382 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 383 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 384 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 385 + } 393 386 394 - } 395 387 396 - #[test] 397 - fn test_simple_quote_chain_2() { 398 - let mut uf = BskyPostUnionFind::new(); 399 - 400 - let id1 = BskyPostId::from("a", "1"); 401 - let id2 = BskyPostId::from("b", "1"); 402 - let id3 = BskyPostId::from("a", "2"); 403 - let id4 = BskyPostId::from("a", "3"); 404 - uf.ingest_post(BskyPostRecord { 405 - id: id2.clone(), 406 - reply_to: None, 407 - quote_of: Some(id1.clone()) 408 - }); 409 - uf.ingest_post(BskyPostRecord { 410 - id: id4.clone(), 411 - reply_to: None, 412 - quote_of: Some(id3.clone()) 413 - }); 414 - uf.ingest_post(BskyPostRecord { 415 - id: id3.clone(), 416 - reply_to: None, 417 - quote_of: Some(id2.clone()) 418 - }); 419 - 420 - assert_eq!(uf.component_size(SubgraphType::Quote, &id1), Some(4)); 421 - assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(4)); 422 - assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(4)); 423 - assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(4)); 424 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 425 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 426 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 427 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 428 388 429 389 } 430 390 431 391 #[test] 432 - fn test_simple_quote_chain_3() { 433 - let mut uf = BskyPostUnionFind::new(); 434 - 435 - let id1 = BskyPostId::from("a", "1"); 436 - let id2 = BskyPostId::from("b", "1"); 437 - let id3 = BskyPostId::from("a", "2"); 438 - let id4 = BskyPostId::from("a", "3"); 439 - uf.ingest_post(BskyPostRecord { 440 - id: id3.clone(), 441 - reply_to: None, 442 - quote_of: Some(id2.clone()) 443 - }); 444 - uf.ingest_post(BskyPostRecord { 445 - id: id2.clone(), 446 - reply_to: None, 447 - quote_of: Some(id1.clone()) 448 - }); 449 - uf.ingest_post(BskyPostRecord { 450 - id: id4.clone(), 451 - reply_to: None, 452 - quote_of: Some(id3.clone()) 453 - }); 454 - 455 - assert_eq!(uf.component_size(SubgraphType::Quote, &id1), Some(4)); 456 - assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(4)); 457 - assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(4)); 458 - assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(4)); 459 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 460 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 461 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 462 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 463 - 464 - } 465 - 466 - #[test] 467 - fn test_simple_quote_chain_4() { 468 - let mut uf = BskyPostUnionFind::new(); 469 - 392 + fn test_simple_reply_quote_graph_1() { 470 393 let id1 = BskyPostId::from("a", "1"); 471 394 let id2 = BskyPostId::from("b", "1"); 472 395 let id3 = BskyPostId::from("a", "2"); 473 396 let id4 = BskyPostId::from("a", "3"); 474 - uf.ingest_post(BskyPostRecord { 475 - id: id3.clone(), 476 - reply_to: None, 477 - quote_of: Some(id2.clone()) 478 - }); 479 - uf.ingest_post(BskyPostRecord { 480 - id: id4.clone(), 481 - reply_to: None, 482 - quote_of: Some(id3.clone()) 483 - }); 484 - uf.ingest_post(BskyPostRecord { 485 - id: id2.clone(), 486 - reply_to: None, 487 - quote_of: Some(id1.clone()) 488 - }); 397 + let mut records: Vec<BskyPostRecord<BskyPostId>> = vec![ 398 + BskyPostRecord { 399 + id: id2.clone(), 400 + reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), 401 + quote_of: None 402 + }, 403 + BskyPostRecord { 404 + id: id3.clone(), 405 + reply_to: Some(BskyPostReplyTo { target: id2.clone(), root: id1.clone() }), 406 + quote_of: None 407 + }, 408 + BskyPostRecord { 409 + id: id4.clone(), 410 + reply_to: None, 411 + quote_of: Some(id3.clone()) 412 + } 413 + ]; 489 414 490 - assert_eq!(uf.component_size(SubgraphType::Quote, &id1), Some(4)); 491 - assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(4)); 492 - assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(4)); 493 - assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(4)); 494 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 495 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 496 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 497 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 498 - 415 + for perm in records.iter().permutations(records.len()) { 416 + let mut uf = BskyPostUnionFind::new(); 417 + uf.ingest_post(perm[0].clone()); 418 + uf.ingest_post(perm[1].clone()); 419 + uf.ingest_post(perm[2].clone()); 420 + assert_eq!(uf.component_size(SubgraphType::Reply, &id1), Some(3)); 421 + assert_eq!(uf.component_size(SubgraphType::Reply, &id2), Some(3)); 422 + assert_eq!(uf.component_size(SubgraphType::Reply, &id3), Some(3)); 423 + assert_eq!(uf.component_size(SubgraphType::Reply, &id4), None); 424 + assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(2)); 425 + assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(2)); 426 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 427 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 428 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 429 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 430 + } 499 431 } 500 432 501 433 #[test] 502 - fn test_simple_quote_chain_5() { 503 - let mut uf = BskyPostUnionFind::new(); 504 - 434 + fn test_simple_reply_quote_graph_2() { 505 435 let id1 = BskyPostId::from("a", "1"); 506 436 let id2 = BskyPostId::from("b", "1"); 507 437 let id3 = BskyPostId::from("a", "2"); 508 438 let id4 = BskyPostId::from("a", "3"); 509 - uf.ingest_post(BskyPostRecord { 510 - id: id4.clone(), 511 - reply_to: None, 512 - quote_of: Some(id3.clone()) 513 - }); 514 - uf.ingest_post(BskyPostRecord { 515 - id: id2.clone(), 516 - reply_to: None, 517 - quote_of: Some(id1.clone()) 518 - }); 519 - uf.ingest_post(BskyPostRecord { 520 - id: id3.clone(), 521 - reply_to: None, 522 - quote_of: Some(id2.clone()) 523 - }); 439 + let mut records: Vec<BskyPostRecord<BskyPostId>> = vec![ 440 + BskyPostRecord { 441 + id: id2.clone(), 442 + reply_to: Some(BskyPostReplyTo { target: id1.clone(), root: id1.clone() }), 443 + quote_of: None 444 + }, 445 + BskyPostRecord { 446 + id: id4.clone(), 447 + reply_to: Some(BskyPostReplyTo { target: id3.clone(), root: id3.clone() }), 448 + quote_of: Some(id2.clone()) 449 + }, 450 + ]; 524 451 525 - assert_eq!(uf.component_size(SubgraphType::Quote, &id1), Some(4)); 526 - assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(4)); 527 - assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(4)); 528 - assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(4)); 529 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 530 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 531 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 532 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 452 + for (i, perm) in records.iter().permutations(records.len()).enumerate() { 453 + let mut uf = BskyPostUnionFind::new(); 454 + uf.ingest_post(perm[0].clone()); 455 + uf.ingest_post(perm[1].clone()); 456 + assert_eq!(uf.component_size(SubgraphType::Reply, &id1), Some(2)); 457 + assert_eq!(uf.component_size(SubgraphType::Reply, &id2), Some(2)); 458 + assert_eq!(uf.component_size(SubgraphType::Reply, &id3), Some(2)); 459 + assert_eq!(uf.component_size(SubgraphType::Reply, &id4), Some(2)); 460 + assert_eq!(uf.component_size(SubgraphType::Quote, &id1), None); 461 + assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(2)); 462 + assert_eq!(uf.component_size(SubgraphType::Quote, &id3), None); 463 + assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(2)); 464 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 465 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 466 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 467 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 468 + } 533 469 534 470 } 535 471 536 472 #[test] 537 - fn test_simple_quote_chain_6() { 538 - let mut uf = BskyPostUnionFind::new(); 539 - 473 + fn test_reply_quote_graph() { 540 474 let id1 = BskyPostId::from("a", "1"); 541 475 let id2 = BskyPostId::from("b", "1"); 542 - let id3 = BskyPostId::from("a", "2"); 543 - let id4 = BskyPostId::from("a", "3"); 544 - uf.ingest_post(BskyPostRecord { 545 - id: id4.clone(), 546 - reply_to: None, 547 - quote_of: Some(id3.clone()) 548 - }); 549 - uf.ingest_post(BskyPostRecord { 550 - id: id3.clone(), 551 - reply_to: None, 552 - quote_of: Some(id2.clone()) 553 - }); 554 - uf.ingest_post(BskyPostRecord { 555 - id: id2.clone(), 556 - reply_to: None, 557 - quote_of: Some(id1.clone()) 558 - }); 476 + let id3 = BskyPostId::from("c", "1"); 477 + let id4 = BskyPostId::from("a", "2"); 478 + let id5 = BskyPostId::from("a", "3"); 479 + let id6 = BskyPostId::from("a", "4"); 480 + let mut records: Vec<BskyPostRecord<BskyPostId>> = vec![ 481 + BskyPostRecord { 482 + id: id3.clone(), 483 + reply_to: Some(BskyPostReplyTo { target: id2.clone(), root: id1.clone() }), 484 + quote_of: None 485 + }, 486 + BskyPostRecord { 487 + id: id6.clone(), 488 + reply_to: Some(BskyPostReplyTo { target: id5.clone(), root: id4.clone() }), 489 + quote_of: Some(id3.clone()) 490 + }, 491 + ]; 559 492 560 - assert_eq!(uf.component_size(SubgraphType::Quote, &id1), Some(4)); 561 - assert_eq!(uf.component_size(SubgraphType::Quote, &id2), Some(4)); 562 - assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(4)); 563 - assert_eq!(uf.component_size(SubgraphType::Quote, &id4), Some(4)); 564 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(4)); 565 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(4)); 566 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(4)); 567 - assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(4)); 493 + for (i, perm) in records.iter().permutations(records.len()).enumerate() { 494 + let mut uf = BskyPostUnionFind::new(); 495 + uf.ingest_post(perm[0].clone()); 496 + uf.ingest_post(perm[1].clone()); 497 + assert_eq!(uf.component_size(SubgraphType::Reply, &id1), Some(3)); 498 + assert_eq!(uf.component_size(SubgraphType::Reply, &id2), Some(3)); 499 + assert_eq!(uf.component_size(SubgraphType::Reply, &id3), Some(3)); 500 + assert_eq!(uf.component_size(SubgraphType::Reply, &id4), Some(3)); 501 + assert_eq!(uf.component_size(SubgraphType::Reply, &id5), Some(3)); 502 + assert_eq!(uf.component_size(SubgraphType::Reply, &id6), Some(3)); 503 + assert_eq!(uf.component_size(SubgraphType::Quote, &id1), None); 504 + assert_eq!(uf.component_size(SubgraphType::Quote, &id2), None); 505 + assert_eq!(uf.component_size(SubgraphType::Quote, &id3), Some(2)); 506 + assert_eq!(uf.component_size(SubgraphType::Quote, &id4), None); 507 + assert_eq!(uf.component_size(SubgraphType::Quote, &id5), None); 508 + assert_eq!(uf.component_size(SubgraphType::Quote, &id6), Some(2)); 509 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id1), Some(6)); 510 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id2), Some(6)); 511 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id3), Some(6)); 512 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id4), Some(6)); 513 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id5), Some(6)); 514 + assert_eq!(uf.component_size(SubgraphType::ReplyQuote, &id6), Some(6)); 515 + } 568 516 569 517 } 570 518 }