1use bytes::Bytes;
5use futures::stream::{Peekable, Stream, StreamExt};
6use futures::task::{Context, Poll};
7
8use super::super::builder::*;
9use super::super::id_map::*;
10use super::super::layer::*;
11use crate::layer::InternalLayer;
12use crate::{chrono_log, storage::*};
13use tdb_succinct::*;
14
15use std::io;
16use std::pin::Pin;
17
18#[derive(Clone)]
24pub struct BaseLayer {
25 pub(super) name: [u32; 5],
26 pub(super) node_dictionary: StringDict,
27 pub(super) predicate_dictionary: StringDict,
28 pub(super) value_dictionary: TypedDict,
29
30 pub(super) node_value_idmap: IdMap,
31 pub(super) predicate_idmap: IdMap,
32
33 pub(super) subjects: Option<MonotonicLogArray>,
34 pub(super) objects: Option<MonotonicLogArray>,
35
36 pub(super) s_p_adjacency_list: AdjacencyList,
37 pub(super) sp_o_adjacency_list: AdjacencyList,
38 pub(super) o_ps_adjacency_list: AdjacencyList,
39
40 pub(super) predicate_wavelet_tree: WaveletTree,
41}
42
43impl BaseLayer {
44 pub async fn load_from_files<F: FileLoad + FileStore>(
45 name: [u32; 5],
46 files: &BaseLayerFiles<F>,
47 ) -> io::Result<InternalLayer> {
48 let maps = files.map_all().await?;
49 Ok(Self::load(name, maps))
50 }
51
52 pub fn load(name: [u32; 5], maps: BaseLayerMaps) -> InternalLayer {
53 let node_dictionary = StringDict::parse(
54 maps.node_dictionary_maps.offsets_map,
55 maps.node_dictionary_maps.blocks_map,
56 );
57 let predicate_dictionary = StringDict::parse(
58 maps.predicate_dictionary_maps.offsets_map,
59 maps.predicate_dictionary_maps.blocks_map,
60 );
61 let value_dictionary = TypedDict::from_parts(
62 maps.value_dictionary_maps.types_present_map,
63 maps.value_dictionary_maps.type_offsets_map,
64 maps.value_dictionary_maps.offsets_map,
65 maps.value_dictionary_maps.blocks_map,
66 );
67
68 let node_value_idmap = match maps.id_map_maps.node_value_idmap_maps {
69 None => IdMap::default(),
70 Some(maps) => IdMap::from_maps(
71 maps,
72 util::calculate_width(
73 (node_dictionary.num_entries() + value_dictionary.num_entries()) as u64,
74 ),
75 ),
76 };
77
78 let predicate_idmap = match maps.id_map_maps.predicate_idmap_maps {
79 None => IdMap::default(),
80 Some(map) => IdMap::from_maps(
81 map,
82 util::calculate_width(predicate_dictionary.num_entries() as u64),
83 ),
84 };
85
86 let subjects = maps.subjects_map.map(|subjects_map| {
87 MonotonicLogArray::from_logarray(LogArray::parse(subjects_map).unwrap())
88 });
89 let objects = maps.objects_map.map(|objects_map| {
90 MonotonicLogArray::from_logarray(LogArray::parse(objects_map).unwrap())
91 });
92
93 let s_p_adjacency_list = AdjacencyList::parse(
94 maps.s_p_adjacency_list_maps.nums_map,
95 maps.s_p_adjacency_list_maps.bitindex_maps.bits_map,
96 maps.s_p_adjacency_list_maps.bitindex_maps.blocks_map,
97 maps.s_p_adjacency_list_maps.bitindex_maps.sblocks_map,
98 );
99 let sp_o_adjacency_list = AdjacencyList::parse(
100 maps.sp_o_adjacency_list_maps.nums_map,
101 maps.sp_o_adjacency_list_maps.bitindex_maps.bits_map,
102 maps.sp_o_adjacency_list_maps.bitindex_maps.blocks_map,
103 maps.sp_o_adjacency_list_maps.bitindex_maps.sblocks_map,
104 );
105 let o_ps_adjacency_list = AdjacencyList::parse(
106 maps.o_ps_adjacency_list_maps.nums_map,
107 maps.o_ps_adjacency_list_maps.bitindex_maps.bits_map,
108 maps.o_ps_adjacency_list_maps.bitindex_maps.blocks_map,
109 maps.o_ps_adjacency_list_maps.bitindex_maps.sblocks_map,
110 );
111
112 let predicate_wavelet_tree_width = s_p_adjacency_list.nums().width();
113 let predicate_wavelet_tree = WaveletTree::from_parts(
114 BitIndex::from_maps(
115 maps.predicate_wavelet_tree_maps.bits_map,
116 maps.predicate_wavelet_tree_maps.blocks_map,
117 maps.predicate_wavelet_tree_maps.sblocks_map,
118 ),
119 predicate_wavelet_tree_width,
120 );
121
122 InternalLayer::Base(BaseLayer {
123 name,
124 node_dictionary,
125 predicate_dictionary,
126 value_dictionary,
127
128 node_value_idmap,
129 predicate_idmap,
130
131 subjects,
132 objects,
133
134 s_p_adjacency_list,
135 sp_o_adjacency_list,
136
137 o_ps_adjacency_list,
138
139 predicate_wavelet_tree,
140 })
141 }
142}
143
144pub struct BaseLayerFileBuilder<F: 'static + FileLoad + FileStore> {
151 files: BaseLayerFiles<F>,
152
153 builder: DictionarySetFileBuilder<F>,
154}
155
156impl<F: 'static + FileLoad + FileStore + Clone> BaseLayerFileBuilder<F> {
157 pub async fn from_files(files: &BaseLayerFiles<F>) -> io::Result<Self> {
159 let builder = DictionarySetFileBuilder::from_files(
160 files.node_dictionary_files.clone(),
161 files.predicate_dictionary_files.clone(),
162 files.value_dictionary_files.clone(),
163 )
164 .await?;
165
166 Ok(BaseLayerFileBuilder {
167 files: files.clone(),
168 builder,
169 })
170 }
171
172 pub fn add_node(&mut self, node: &str) -> u64 {
176 let id = self.builder.add_node(node);
177
178 id
179 }
180
181 pub fn add_node_bytes(&mut self, node: Bytes) -> u64 {
182 self.builder.add_node_bytes(node)
183 }
184
185 pub fn add_predicate(&mut self, predicate: &str) -> u64 {
189 let id = self.builder.add_predicate(predicate);
190
191 id
192 }
193
194 pub fn add_predicate_bytes(&mut self, predicate: Bytes) -> u64 {
195 self.builder.add_predicate_bytes(predicate)
196 }
197
198 pub fn add_value(&mut self, value: TypedDictEntry) -> u64 {
202 let id = self.builder.add_value(value);
203
204 id
205 }
206
207 pub fn add_nodes<I: 'static + IntoIterator<Item = String> + Send>(
211 &mut self,
212 nodes: I,
213 ) -> Vec<u64>
214 where
215 <I as std::iter::IntoIterator>::IntoIter: Unpin + Send + Sync,
216 I: Unpin + Sync,
217 {
218 let ids = self.builder.add_nodes(nodes);
219
220 ids
221 }
222
223 pub fn add_nodes_bytes<I: 'static + IntoIterator<Item = Bytes> + Send>(
224 &mut self,
225 nodes: I,
226 ) -> Vec<u64>
227 where
228 <I as std::iter::IntoIterator>::IntoIter: Unpin + Send + Sync,
229 I: Unpin + Sync,
230 {
231 let ids = self.builder.add_nodes_bytes(nodes);
232
233 ids
234 }
235
236 pub fn add_predicates<I: 'static + IntoIterator<Item = String> + Send>(
240 &mut self,
241 predicates: I,
242 ) -> Vec<u64>
243 where
244 <I as std::iter::IntoIterator>::IntoIter: Unpin + Send + Sync,
245 I: Unpin + Sync,
246 {
247 let ids = self.builder.add_predicates(predicates);
248
249 ids
250 }
251
252 pub fn add_predicates_bytes<I: 'static + IntoIterator<Item = Bytes> + Send>(
253 &mut self,
254 predicates: I,
255 ) -> Vec<u64>
256 where
257 <I as std::iter::IntoIterator>::IntoIter: Unpin + Send + Sync,
258 I: Unpin + Sync,
259 {
260 let ids = self.builder.add_predicates_bytes(predicates);
261
262 ids
263 }
264
265 pub fn add_values<I: 'static + IntoIterator<Item = TypedDictEntry> + Send>(
269 &mut self,
270 values: I,
271 ) -> Vec<u64>
272 where
273 <I as std::iter::IntoIterator>::IntoIter: Unpin + Send + Sync,
274 I: Unpin + Sync,
275 {
276 let ids = self.builder.add_values(values);
277
278 ids
279 }
280
281 pub async fn into_phase2(self) -> io::Result<BaseLayerFileBuilderPhase2<F>> {
283 let BaseLayerFileBuilder { files, builder } = self;
284
285 builder.finalize().await?;
286
287 let node_dict_blocks_map = files.node_dictionary_files.blocks_file.map().await?;
288 let node_dict_offsets_map = files.node_dictionary_files.offsets_file.map().await?;
289 let predicate_dict_blocks_map = files.predicate_dictionary_files.blocks_file.map().await?;
290 let predicate_dict_offsets_map =
291 files.predicate_dictionary_files.offsets_file.map().await?;
292 let value_dict_types_present_map = files
293 .value_dictionary_files
294 .types_present_file
295 .map()
296 .await?;
297 let value_dict_type_offsets_map =
298 files.value_dictionary_files.type_offsets_file.map().await?;
299 let value_dict_blocks_map = files.value_dictionary_files.blocks_file.map().await?;
300 let value_dict_offsets_map = files.value_dictionary_files.offsets_file.map().await?;
301
302 let node_dict = StringDict::parse(node_dict_offsets_map, node_dict_blocks_map);
303 let pred_dict = StringDict::parse(predicate_dict_offsets_map, predicate_dict_blocks_map);
304 let val_dict = TypedDict::from_parts(
305 value_dict_types_present_map,
306 value_dict_type_offsets_map,
307 value_dict_offsets_map,
308 value_dict_blocks_map,
309 );
310
311 let num_nodes = node_dict.num_entries();
313 let num_predicates = pred_dict.num_entries();
314 let num_values = val_dict.num_entries();
315
316 BaseLayerFileBuilderPhase2::new(files, num_nodes, num_predicates, num_values).await
317 }
318}
319
320pub struct BaseLayerFileBuilderPhase2<F: 'static + FileLoad + FileStore> {
325 files: BaseLayerFiles<F>,
326
327 builder: TripleFileBuilder<F>,
328}
329
330impl<F: 'static + FileLoad + FileStore> BaseLayerFileBuilderPhase2<F> {
331 pub async fn new(
332 files: BaseLayerFiles<F>,
333
334 num_nodes: usize,
335 num_predicates: usize,
336 num_values: usize,
337 ) -> io::Result<Self> {
338 let builder = TripleFileBuilder::new(
339 files.s_p_adjacency_list_files.clone(),
340 files.sp_o_adjacency_list_files.clone(),
341 num_nodes,
342 num_predicates,
343 num_values,
344 None,
345 )
346 .await?;
347
348 Ok(BaseLayerFileBuilderPhase2 { files, builder })
349 }
350
351 pub async fn add_triple(
355 &mut self,
356 subject: u64,
357 predicate: u64,
358 object: u64,
359 ) -> io::Result<()> {
360 self.builder.add_triple(subject, predicate, object).await
361 }
362
363 pub async fn add_id_triples<I: 'static + IntoIterator<Item = IdTriple>>(
367 &mut self,
368 triples: I,
369 ) -> io::Result<()>
370 where
371 <I as std::iter::IntoIterator>::IntoIter: Unpin + Send,
372 {
373 self.builder.add_id_triples(triples).await
374 }
375
376 pub(crate) async fn partial_finalize(self) -> io::Result<BaseLayerFiles<F>> {
377 self.builder.finalize().await?;
378 chrono_log!("finalized base triples builder");
379
380 Ok(self.files)
381 }
382
383 pub async fn finalize(self) -> io::Result<()> {
384 self.builder.finalize().await?;
385 chrono_log!("finalized base triples builder");
386 let s_p_adjacency_list_files = self.files.s_p_adjacency_list_files.clone();
387 let sp_o_adjacency_list_files = self.files.sp_o_adjacency_list_files.clone();
388 let o_ps_adjacency_list_files = self.files.o_ps_adjacency_list_files.clone();
389 let predicate_wavelet_tree_files = self.files.predicate_wavelet_tree_files.clone();
390 build_indexes(
391 s_p_adjacency_list_files,
392 sp_o_adjacency_list_files,
393 o_ps_adjacency_list_files,
394 None,
395 predicate_wavelet_tree_files,
396 )
397 .await?;
398
399 chrono_log!("finalized base builder");
400
401 Ok(())
402 }
403}
404
405pub struct BaseTripleStream<S: Stream<Item = io::Result<(u64, u64)>> + Send> {
406 s_p_stream: Peekable<S>,
407 sp_o_stream: Peekable<S>,
408 last_s_p: (u64, u64),
409 last_sp: u64,
410}
411
412impl<S: Stream<Item = io::Result<(u64, u64)>> + Unpin + Send> BaseTripleStream<S> {
413 pub fn new(s_p_stream: S, sp_o_stream: S) -> BaseTripleStream<S> {
414 BaseTripleStream {
415 s_p_stream: s_p_stream.peekable(),
416 sp_o_stream: sp_o_stream.peekable(),
417 last_s_p: (0, 0),
418 last_sp: 0,
419 }
420 }
421}
422
423impl<S: Stream<Item = io::Result<(u64, u64)>> + Unpin + Send> Stream for BaseTripleStream<S> {
424 type Item = io::Result<(u64, u64, u64)>;
425
426 fn poll_next(
427 mut self: Pin<&mut Self>,
428 cx: &mut Context,
429 ) -> Poll<Option<io::Result<(u64, u64, u64)>>> {
430 let peeked = Pin::new(&mut self.sp_o_stream).poll_peek(cx);
431 match peeked {
432 Poll::Ready(Some(Ok((sp, o)))) => {
433 let sp = *sp;
434 let o = *o;
435 if sp > self.last_sp {
436 let peeked = Pin::new(&mut self.s_p_stream).poll_peek(cx);
437 match peeked {
438 Poll::Ready(None) => Poll::Ready(Some(Err(io::Error::new(
439 io::ErrorKind::UnexpectedEof,
440 "unexpected end of s_p_stream",
441 )))),
442 Poll::Ready(Some(Ok((s, p)))) => {
443 let s = *s;
444 let p = *p;
445 util::assert_poll_next(Pin::new(&mut self.s_p_stream), cx).unwrap();
446 util::assert_poll_next(Pin::new(&mut self.sp_o_stream), cx).unwrap();
447
448 self.last_s_p = (s, p);
449 self.last_sp = sp;
450
451 Poll::Ready(Some(Ok((s, p, o))))
452 }
453 Poll::Ready(Some(Err(_))) => Poll::Ready(Some(Err(
454 util::assert_poll_next(Pin::new(&mut self.s_p_stream), cx)
455 .err()
456 .unwrap(),
457 ))),
458 Poll::Pending => Poll::Pending,
459 }
460 } else {
461 util::assert_poll_next(Pin::new(&mut self.sp_o_stream), cx).unwrap();
462
463 Poll::Ready(Some(Ok((self.last_s_p.0, self.last_s_p.1, o))))
464 }
465 }
466 Poll::Ready(Some(Err(_))) => Poll::Ready(Some(Err(util::assert_poll_next(
467 Pin::new(&mut self.sp_o_stream),
468 cx,
469 )
470 .err()
471 .unwrap()))),
472 Poll::Ready(None) => Poll::Ready(None),
473 Poll::Pending => Poll::Pending,
474 }
475 }
476}
477
478pub async fn open_base_triple_stream<F: 'static + FileLoad + FileStore>(
479 s_p_files: AdjacencyListFiles<F>,
480 sp_o_files: AdjacencyListFiles<F>,
481) -> io::Result<impl Stream<Item = io::Result<(u64, u64, u64)>> + Unpin + Send> {
482 let s_p_stream =
483 adjacency_list_stream_pairs(s_p_files.bitindex_files.bits_file, s_p_files.nums_file)
484 .await?;
485 let sp_o_stream =
486 adjacency_list_stream_pairs(sp_o_files.bitindex_files.bits_file, sp_o_files.nums_file)
487 .await?;
488
489 Ok(BaseTripleStream::new(s_p_stream, sp_o_stream))
490}
491
492#[cfg(test)]
493pub mod base_tests {
494 use super::*;
495 use crate::storage::memory::*;
496 use futures::stream::TryStreamExt;
497
498 pub fn base_layer_files() -> BaseLayerFiles<MemoryBackedStore> {
499 base_layer_memory_files()
501 }
502
503 pub async fn example_base_layer_files() -> io::Result<BaseLayerFiles<MemoryBackedStore>> {
504 let nodes = vec!["aaaaa", "baa", "bbbbb", "ccccc", "mooo"];
505 let predicates = vec!["abcde", "fghij", "klmno", "lll"];
506 let values = vec!["chicken", "cow", "dog", "pig", "zebra"];
507
508 let base_layer_files = base_layer_files();
509
510 let mut builder = BaseLayerFileBuilder::from_files(&base_layer_files).await?;
511
512 builder.add_nodes(nodes.into_iter().map(|s| s.to_string()));
513 builder.add_predicates(predicates.into_iter().map(|s| s.to_string()));
514 builder.add_values(values.into_iter().map(|s| String::make_entry(&s)));
515
516 let mut builder = builder.into_phase2().await?;
517
518 builder.add_triple(1, 1, 1).await?;
519 builder.add_triple(2, 1, 1).await?;
520 builder.add_triple(2, 1, 3).await?;
521 builder.add_triple(2, 3, 6).await?;
522 builder.add_triple(3, 2, 5).await?;
523 builder.add_triple(3, 3, 6).await?;
524 builder.add_triple(4, 3, 6).await?;
525
526 builder.finalize().await?;
527
528 Ok(base_layer_files)
529 }
530
531 pub async fn example_base_layer() -> InternalLayer {
532 let base_layer_files = example_base_layer_files().await.unwrap();
533
534 BaseLayer::load_from_files([1, 2, 3, 4, 5], &base_layer_files)
535 .await
536 .unwrap()
537 }
538
539 #[tokio::test]
540 async fn build_and_query_base_layer() {
541 let layer = example_base_layer().await;
542
543 assert!(layer.triple_exists(1, 1, 1));
544 assert!(layer.triple_exists(2, 1, 1));
545 assert!(layer.triple_exists(2, 1, 3));
546 assert!(layer.triple_exists(2, 3, 6));
547 assert!(layer.triple_exists(3, 2, 5));
548 assert!(layer.triple_exists(3, 3, 6));
549 assert!(layer.triple_exists(4, 3, 6));
550
551 assert!(!layer.triple_exists(2, 2, 0));
552 }
553
554 #[tokio::test]
555 async fn dictionary_entries_in_base() {
556 let base_layer = example_base_layer().await;
557
558 assert_eq!(3, base_layer.subject_id("bbbbb").unwrap());
559 assert_eq!(2, base_layer.predicate_id("fghij").unwrap());
560 assert_eq!(1, base_layer.object_node_id("aaaaa").unwrap());
561 assert_eq!(
562 6,
563 base_layer
564 .object_value_id(&String::make_entry(&"chicken"))
565 .unwrap()
566 );
567
568 assert_eq!("bbbbb", base_layer.id_subject(3).unwrap());
569 assert_eq!("fghij", base_layer.id_predicate(2).unwrap());
570 assert_eq!(
571 ObjectType::Node("aaaaa".to_string()),
572 base_layer.id_object(1).unwrap()
573 );
574 assert_eq!(
575 ObjectType::Value(String::make_entry(&"chicken")),
576 base_layer.id_object(6).unwrap()
577 );
578 }
579
580 #[tokio::test]
581 async fn everything_iterator() {
582 let layer = example_base_layer().await;
583 let triples: Vec<_> = layer
584 .triples()
585 .map(|t| (t.subject, t.predicate, t.object))
586 .collect();
587
588 assert_eq!(
589 vec![
590 (1, 1, 1),
591 (2, 1, 1),
592 (2, 1, 3),
593 (2, 3, 6),
594 (3, 2, 5),
595 (3, 3, 6),
596 (4, 3, 6)
597 ],
598 triples
599 );
600 }
601
602 #[tokio::test]
603 async fn lookup_by_object() {
604 let layer = example_base_layer().await;
605
606 let triples: Vec<_> = layer
607 .triples_o(1)
608 .map(|t| (t.subject, t.predicate, t.object))
609 .collect();
610 assert_eq!(vec![(1, 1, 1), (2, 1, 1)], triples);
611
612 let triples: Vec<_> = layer
613 .triples_o(3)
614 .map(|t| (t.subject, t.predicate, t.object))
615 .collect();
616 assert_eq!(vec![(2, 1, 3)], triples);
617
618 let triples: Vec<_> = layer
619 .triples_o(5)
620 .map(|t| (t.subject, t.predicate, t.object))
621 .collect();
622 assert_eq!(vec![(3, 2, 5)], triples);
623
624 let triples: Vec<_> = layer
625 .triples_o(6)
626 .map(|t| (t.subject, t.predicate, t.object))
627 .collect();
628 assert_eq!(vec![(2, 3, 6), (3, 3, 6), (4, 3, 6)], triples);
629 }
630
631 #[tokio::test]
632 async fn lookup_by_predicate() {
633 let layer = example_base_layer().await;
634
635 let pairs: Vec<_> = layer
636 .triples_p(1)
637 .map(|t| (t.subject, t.predicate, t.object))
638 .collect();
639
640 assert_eq!(vec![(1, 1, 1), (2, 1, 1), (2, 1, 3)], pairs);
641
642 let pairs: Vec<_> = layer
643 .triples_p(2)
644 .map(|t| (t.subject, t.predicate, t.object))
645 .collect();
646
647 assert_eq!(vec![(3, 2, 5)], pairs);
648
649 let pairs: Vec<_> = layer
650 .triples_p(3)
651 .map(|t| (t.subject, t.predicate, t.object))
652 .collect();
653
654 assert_eq!(vec![(2, 3, 6), (3, 3, 6), (4, 3, 6)], pairs);
655
656 assert!(layer.triples_p(4).next().is_none());
657 }
658
659 #[tokio::test]
660 async fn create_empty_base_layer() {
661 let base_layer_files = base_layer_files();
662 let builder = BaseLayerFileBuilder::from_files(&base_layer_files)
663 .await
664 .unwrap();
665
666 let builder = builder.into_phase2().await.unwrap();
667 builder.finalize().await.unwrap();
668
669 let layer = BaseLayer::load_from_files([1, 2, 3, 4, 5], &base_layer_files)
670 .await
671 .unwrap();
672
673 assert_eq!(0, layer.node_and_value_count());
674 assert_eq!(0, layer.predicate_count());
675 }
676
677 #[tokio::test]
678 async fn stream_base_triples() {
679 let layer_files = example_base_layer_files().await.unwrap();
680
681 let stream = open_base_triple_stream(
682 layer_files.s_p_adjacency_list_files,
683 layer_files.sp_o_adjacency_list_files,
684 )
685 .await
686 .unwrap();
687
688 let triples: Vec<_> = stream.try_collect().await.unwrap();
689
690 assert_eq!(
691 vec![
692 (1, 1, 1),
693 (2, 1, 1),
694 (2, 1, 3),
695 (2, 3, 6),
696 (3, 2, 5),
697 (3, 3, 6),
698 (4, 3, 6)
699 ],
700 triples
701 );
702 }
703
704 #[tokio::test]
705 async fn count_triples() {
706 let layer = example_base_layer().await;
707
708 assert_eq!(7, layer.internal_triple_layer_addition_count());
709 assert_eq!(0, layer.internal_triple_layer_removal_count());
710 assert_eq!(7, layer.triple_addition_count());
711 assert_eq!(0, layer.triple_removal_count());
712 assert_eq!(7, layer.triple_count());
713 }
714
715 #[tokio::test]
716 async fn count_triples_of_empty_base_layer() {
717 let layer_files = base_layer_files();
718 let builder = BaseLayerFileBuilder::from_files(&layer_files)
719 .await
720 .unwrap();
721 builder
722 .into_phase2()
723 .await
724 .unwrap()
725 .finalize()
726 .await
727 .unwrap();
728 let layer = BaseLayer::load_from_files([1, 2, 3, 4, 5], &layer_files)
729 .await
730 .unwrap();
731
732 assert_eq!(0, layer.internal_triple_layer_addition_count());
733 assert_eq!(0, layer.internal_triple_layer_removal_count());
734 assert_eq!(0, layer.triple_count());
735 assert_eq!(0, layer.triple_addition_count());
736 assert_eq!(0, layer.triple_removal_count());
737 }
738}