1use scirs2_core::metrics::{Counter, Histogram, Timer};
17use std::collections::HashMap;
18use std::hash::{Hash, Hasher};
19use std::sync::{Arc, RwLock, Weak};
20
21pub struct StringInterner {
29 strings: RwLock<HashMap<String, Weak<str>>>,
31 string_to_id: RwLock<HashMap<String, u32>>,
33 id_to_string: RwLock<HashMap<u32, Arc<str>>>,
35 next_id: AtomicU32,
37 stats: RwLock<InternerStats>,
39 cache_hit_counter: Arc<Counter>,
41 cache_miss_counter: Arc<Counter>,
42 intern_timer: Arc<Timer>,
43 string_length_histogram: Arc<Histogram>,
44 memory_usage_histogram: Arc<Histogram>,
45}
46
47use std::sync::atomic::AtomicU32;
49
50#[derive(Debug, Clone, Default)]
52pub struct InternerStats {
53 pub total_requests: usize,
54 pub cache_hits: usize,
55 pub cache_misses: usize,
56 pub total_strings_stored: usize,
57 pub memory_saved_bytes: usize,
58}
59
60#[derive(Debug, Clone)]
62pub struct MemoryUsage {
63 pub interned_strings: usize,
64 pub id_mappings: usize,
65 pub estimated_memory_bytes: usize,
66 pub memory_saved_bytes: usize,
67 pub compression_ratio: f64,
68}
69
70#[derive(Debug, Clone)]
72pub struct InternerMetrics {
73 pub cache_hits: u64,
75 pub cache_misses: u64,
77 pub total_requests: u64,
79 pub hit_ratio: f64,
81 pub avg_intern_time_secs: f64,
83 pub total_intern_observations: u64,
85 pub avg_string_length: f64,
87 pub total_memory_tracked_bytes: u64,
89}
90
91impl InternerStats {
92 pub fn hit_ratio(&self) -> f64 {
93 if self.total_requests == 0 {
94 0.0
95 } else {
96 self.cache_hits as f64 / self.total_requests as f64
97 }
98 }
99}
100
101impl StringInterner {
102 pub fn new() -> Self {
110 Self::with_capacity(1024) }
112
113 pub fn with_capacity(capacity: usize) -> Self {
118 StringInterner {
119 strings: RwLock::new(HashMap::with_capacity(capacity)),
120 string_to_id: RwLock::new(HashMap::with_capacity(capacity)),
121 id_to_string: RwLock::new(HashMap::with_capacity(capacity)),
122 next_id: AtomicU32::new(0),
123 stats: RwLock::new(InternerStats::default()),
124 cache_hit_counter: Arc::new(Counter::new("interner.cache_hits".to_string())),
125 cache_miss_counter: Arc::new(Counter::new("interner.cache_misses".to_string())),
126 intern_timer: Arc::new(Timer::new("interner.intern_time".to_string())),
127 string_length_histogram: Arc::new(Histogram::new("interner.string_length".to_string())),
128 memory_usage_histogram: Arc::new(Histogram::new("interner.memory_usage".to_string())),
129 }
130 }
131
132 pub fn intern(&self, s: &str) -> Arc<str> {
139 let _guard = self.intern_timer.start();
140
141 self.string_length_histogram.observe(s.len() as f64);
143
144 {
146 let strings = self
147 .strings
148 .read()
149 .unwrap_or_else(|poisoned| poisoned.into_inner());
150 if let Some(weak_ref) = strings.get(s) {
151 if let Some(arc_str) = weak_ref.upgrade() {
152 self.cache_hit_counter.inc();
154 {
155 let mut stats = self
156 .stats
157 .write()
158 .unwrap_or_else(|poisoned| poisoned.into_inner());
159 stats.total_requests += 1;
160 stats.cache_hits += 1;
161 }
162 return arc_str;
163 }
164 }
165 }
166
167 let mut strings = self
169 .strings
170 .write()
171 .unwrap_or_else(|poisoned| poisoned.into_inner());
172
173 if let Some(weak_ref) = strings.get(s) {
175 if let Some(arc_str) = weak_ref.upgrade() {
176 self.cache_hit_counter.inc();
178 drop(strings); {
180 let mut stats = self
181 .stats
182 .write()
183 .unwrap_or_else(|poisoned| poisoned.into_inner());
184 stats.total_requests += 1;
185 stats.cache_hits += 1;
186 }
187 return arc_str;
188 }
189 }
190
191 let arc_str: Arc<str> = Arc::from(s);
193 let weak_ref = Arc::downgrade(&arc_str);
194 strings.insert(s.to_string(), weak_ref);
195
196 self.cache_miss_counter.inc();
198 drop(strings); {
200 let mut stats = self
201 .stats
202 .write()
203 .unwrap_or_else(|poisoned| poisoned.into_inner());
204 stats.total_requests += 1;
205 stats.cache_misses += 1;
206 stats.total_strings_stored += 1;
207 stats.memory_saved_bytes += s.len(); }
209
210 arc_str
211 }
212
213 pub fn intern_with_id(&self, s: &str) -> (Arc<str>, u32) {
215 {
217 let string_to_id = self
218 .string_to_id
219 .read()
220 .unwrap_or_else(|poisoned| poisoned.into_inner());
221 if let Some(&id) = string_to_id.get(s) {
222 let id_to_string = self
224 .id_to_string
225 .read()
226 .unwrap_or_else(|poisoned| poisoned.into_inner());
227 if let Some(arc_str) = id_to_string.get(&id) {
228 {
230 let mut stats = self
231 .stats
232 .write()
233 .unwrap_or_else(|poisoned| poisoned.into_inner());
234 stats.total_requests += 1;
235 stats.cache_hits += 1;
236 }
237 return (arc_str.clone(), id);
238 }
239 }
240 }
241
242 let arc_str = self.intern(s); let mut string_to_id = self
256 .string_to_id
257 .write()
258 .unwrap_or_else(|poisoned| poisoned.into_inner());
259
260 if let Some(&existing_id) = string_to_id.get(s) {
264 return (arc_str, existing_id);
265 }
266
267 let id = self
268 .next_id
269 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
270 string_to_id.insert(s.to_string(), id);
271 drop(string_to_id);
272
273 {
274 let mut id_to_string = self
275 .id_to_string
276 .write()
277 .unwrap_or_else(|poisoned| poisoned.into_inner());
278 id_to_string.insert(id, arc_str.clone());
279 }
280
281 (arc_str, id)
282 }
283
284 pub fn get_id(&self, s: &str) -> Option<u32> {
286 let string_to_id = self
287 .string_to_id
288 .read()
289 .unwrap_or_else(|poisoned| poisoned.into_inner());
290 string_to_id.get(s).copied()
291 }
292
293 pub fn get_string(&self, id: u32) -> Option<Arc<str>> {
295 let id_to_string = self
296 .id_to_string
297 .read()
298 .unwrap_or_else(|poisoned| poisoned.into_inner());
299 id_to_string.get(&id).cloned()
300 }
301
302 pub fn get_all_mappings(&self) -> Vec<(u32, Arc<str>)> {
304 let id_to_string = self
305 .id_to_string
306 .read()
307 .unwrap_or_else(|poisoned| poisoned.into_inner());
308 id_to_string
309 .iter()
310 .map(|(&id, s)| (id, s.clone()))
311 .collect()
312 }
313
314 pub fn cleanup(&self) -> usize {
318 let mut strings = self
319 .strings
320 .write()
321 .unwrap_or_else(|poisoned| poisoned.into_inner());
322 let before = strings.len();
323 strings.retain(|_, weak_ref| weak_ref.strong_count() > 0);
324 let after = strings.len();
325 before - after
326 }
327
328 pub fn stats(&self) -> InternerStats {
330 self.stats
331 .read()
332 .unwrap_or_else(|poisoned| poisoned.into_inner())
333 .clone()
334 }
335
336 pub fn len(&self) -> usize {
338 let id_count = self
340 .string_to_id
341 .read()
342 .unwrap_or_else(|poisoned| poisoned.into_inner())
343 .len();
344 let string_count = self
345 .strings
346 .read()
347 .unwrap_or_else(|poisoned| poisoned.into_inner())
348 .len();
349 std::cmp::max(id_count, string_count)
350 }
351
352 pub fn id_mapping_count(&self) -> usize {
354 self.string_to_id
355 .read()
356 .unwrap_or_else(|poisoned| poisoned.into_inner())
357 .len()
358 }
359
360 pub fn is_empty(&self) -> bool {
362 self.strings
363 .read()
364 .unwrap_or_else(|poisoned| poisoned.into_inner())
365 .is_empty()
366 }
367
368 pub fn intern_batch(&self, strings: &[&str]) -> Vec<Arc<str>> {
371 let mut result = Vec::with_capacity(strings.len());
372 let mut to_create = Vec::new();
373
374 {
376 let string_map = self
377 .strings
378 .read()
379 .unwrap_or_else(|poisoned| poisoned.into_inner());
380 for &s in strings {
381 if let Some(weak_ref) = string_map.get(s) {
382 if let Some(arc_str) = weak_ref.upgrade() {
383 result.push(arc_str);
384 continue;
385 }
386 }
387 to_create.push((result.len(), s));
388 result.push(Arc::from("")); }
390 }
391
392 if !to_create.is_empty() {
394 let mut string_map = self
395 .strings
396 .write()
397 .unwrap_or_else(|poisoned| poisoned.into_inner());
398 let mut stats = self
399 .stats
400 .write()
401 .unwrap_or_else(|poisoned| poisoned.into_inner());
402
403 for (index, s) in to_create {
404 if let Some(weak_ref) = string_map.get(s) {
406 if let Some(arc_str) = weak_ref.upgrade() {
407 result[index] = arc_str;
408 stats.cache_hits += 1;
409 continue;
410 }
411 }
412
413 let arc_str: Arc<str> = Arc::from(s);
415 let weak_ref = Arc::downgrade(&arc_str);
416 string_map.insert(s.to_string(), weak_ref);
417 result[index] = arc_str;
418
419 stats.cache_misses += 1;
420 stats.total_strings_stored += 1;
421 stats.memory_saved_bytes += s.len();
422 }
423
424 stats.total_requests += strings.len();
425 }
426
427 result
428 }
429
430 pub fn prefetch(&self, strings: &[&str]) {
433 let _ = self.intern_batch(strings);
434 }
435
436 pub fn memory_usage(&self) -> MemoryUsage {
438 let string_map_size = self
439 .strings
440 .read()
441 .unwrap_or_else(|poisoned| poisoned.into_inner())
442 .len();
443 let id_map_size = self
444 .string_to_id
445 .read()
446 .unwrap_or_else(|poisoned| poisoned.into_inner())
447 .len();
448 let stats = self
449 .stats
450 .read()
451 .unwrap_or_else(|poisoned| poisoned.into_inner());
452
453 MemoryUsage {
454 interned_strings: string_map_size,
455 id_mappings: id_map_size,
456 estimated_memory_bytes: string_map_size * 64 + id_map_size * 8, memory_saved_bytes: stats.memory_saved_bytes,
458 compression_ratio: if stats.memory_saved_bytes > 0 {
459 stats.memory_saved_bytes as f64
460 / (stats.memory_saved_bytes + string_map_size * 32) as f64
461 } else {
462 0.0
463 },
464 }
465 }
466
467 pub fn get_metrics(&self) -> InternerMetrics {
475 let cache_hits = self.cache_hit_counter.get();
476 let cache_misses = self.cache_miss_counter.get();
477 let total_requests = cache_hits + cache_misses;
478 let hit_ratio = if total_requests > 0 {
479 cache_hits as f64 / total_requests as f64
480 } else {
481 0.0
482 };
483
484 let timer_stats = self.intern_timer.get_stats();
485 let string_length_stats = self.string_length_histogram.get_stats();
486 let memory_stats = self.memory_usage_histogram.get_stats();
487
488 InternerMetrics {
489 cache_hits,
490 cache_misses,
491 total_requests,
492 hit_ratio,
493 avg_intern_time_secs: timer_stats.mean,
494 total_intern_observations: timer_stats.count,
495 avg_string_length: string_length_stats.mean,
496 total_memory_tracked_bytes: memory_stats.sum as u64,
497 }
498 }
499
500 pub fn optimize(&self) {
508 let start = std::time::Instant::now();
509
510 let cleaned_count = self.cleanup();
512
513 let current_size = {
515 let strings = self
516 .strings
517 .read()
518 .unwrap_or_else(|poisoned| poisoned.into_inner());
519 strings.len()
520 };
521
522 let optimal_capacity = ((current_size as f64 * 1.3) as usize).max(1024);
524
525 {
526 let mut strings = self
527 .strings
528 .write()
529 .unwrap_or_else(|poisoned| poisoned.into_inner());
530 let mut string_to_id = self
531 .string_to_id
532 .write()
533 .unwrap_or_else(|poisoned| poisoned.into_inner());
534 let mut id_to_string = self
535 .id_to_string
536 .write()
537 .unwrap_or_else(|poisoned| poisoned.into_inner());
538
539 let mut new_strings = HashMap::with_capacity(optimal_capacity);
541 let mut new_string_to_id = HashMap::with_capacity(optimal_capacity);
542 let mut new_id_to_string = HashMap::with_capacity(optimal_capacity);
543
544 for (key, value) in strings.drain() {
546 new_strings.insert(key, value);
547 }
548 for (key, value) in string_to_id.drain() {
549 new_string_to_id.insert(key, value);
550 }
551 for (key, value) in id_to_string.drain() {
552 new_id_to_string.insert(key, value);
553 }
554
555 *strings = new_strings;
557 *string_to_id = new_string_to_id;
558 *id_to_string = new_id_to_string;
559 }
560
561 let mem_usage = self.memory_usage();
563 self.memory_usage_histogram
564 .observe(mem_usage.estimated_memory_bytes as f64);
565
566 {
568 let mut stats = self
569 .stats
570 .write()
571 .unwrap_or_else(|poisoned| poisoned.into_inner());
572 stats.total_strings_stored = current_size;
573 }
574
575 let duration = start.elapsed();
576 tracing::debug!(
577 "Interner optimized: cleaned {} entries, rehashed to capacity {}, took {:?}",
578 cleaned_count,
579 optimal_capacity,
580 duration
581 );
582 }
583}
584
585impl Default for StringInterner {
586 fn default() -> Self {
587 Self::new()
588 }
589}
590
591impl std::fmt::Debug for StringInterner {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 f.debug_struct("StringInterner")
594 .field(
595 "strings_count",
596 &self
597 .strings
598 .read()
599 .unwrap_or_else(|poisoned| poisoned.into_inner())
600 .len(),
601 )
602 .field(
603 "id_mappings_count",
604 &self
605 .string_to_id
606 .read()
607 .unwrap_or_else(|poisoned| poisoned.into_inner())
608 .len(),
609 )
610 .field(
611 "next_id",
612 &self.next_id.load(std::sync::atomic::Ordering::Relaxed),
613 )
614 .field(
615 "stats",
616 &self
617 .stats
618 .read()
619 .unwrap_or_else(|poisoned| poisoned.into_inner()),
620 )
621 .finish()
622 }
623}
624
625pub static IRI_INTERNER: once_cell::sync::Lazy<StringInterner> =
628 once_cell::sync::Lazy::new(StringInterner::new);
629
630pub static DATATYPE_INTERNER: once_cell::sync::Lazy<StringInterner> =
632 once_cell::sync::Lazy::new(StringInterner::new);
633
634pub static LANGUAGE_INTERNER: once_cell::sync::Lazy<StringInterner> =
636 once_cell::sync::Lazy::new(StringInterner::new);
637
638pub static STRING_INTERNER: once_cell::sync::Lazy<StringInterner> =
640 once_cell::sync::Lazy::new(StringInterner::new);
641
642#[derive(Debug, Clone)]
644pub struct InternedString {
645 inner: Arc<str>,
646}
647
648impl InternedString {
649 pub fn new(s: &str) -> Self {
651 InternedString {
652 inner: IRI_INTERNER.intern(s),
653 }
654 }
655
656 pub fn new_with_interner(s: &str, interner: &StringInterner) -> Self {
658 InternedString {
659 inner: interner.intern(s),
660 }
661 }
662
663 pub fn new_datatype(s: &str) -> Self {
665 InternedString {
666 inner: DATATYPE_INTERNER.intern(s),
667 }
668 }
669
670 pub fn new_language(s: &str) -> Self {
672 InternedString {
673 inner: LANGUAGE_INTERNER.intern(s),
674 }
675 }
676
677 pub fn as_str(&self) -> &str {
679 &self.inner
680 }
681
682 pub fn as_arc_str(&self) -> &Arc<str> {
684 &self.inner
685 }
686
687 pub fn into_arc_str(self) -> Arc<str> {
689 self.inner
690 }
691}
692
693impl std::fmt::Display for InternedString {
694 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695 write!(f, "{}", self.inner)
696 }
697}
698
699impl std::ops::Deref for InternedString {
700 type Target = str;
701
702 fn deref(&self) -> &Self::Target {
703 &self.inner
704 }
705}
706
707impl AsRef<str> for InternedString {
708 fn as_ref(&self) -> &str {
709 &self.inner
710 }
711}
712
713impl PartialEq for InternedString {
714 fn eq(&self, other: &Self) -> bool {
715 Arc::ptr_eq(&self.inner, &other.inner) || self.inner == other.inner
717 }
718}
719
720impl Eq for InternedString {}
721
722impl Hash for InternedString {
723 fn hash<H: Hasher>(&self, state: &mut H) {
724 self.inner.hash(state);
726 }
727}
728
729impl PartialOrd for InternedString {
730 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
731 Some(self.cmp(other))
732 }
733}
734
735impl Ord for InternedString {
736 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
737 self.inner.cmp(&other.inner)
738 }
739}
740
741impl From<&str> for InternedString {
742 fn from(s: &str) -> Self {
743 InternedString::new(s)
744 }
745}
746
747impl From<String> for InternedString {
748 fn from(s: String) -> Self {
749 InternedString::new(&s)
750 }
751}
752
753pub trait RdfVocabulary {
755 const XSD_NS: &'static str = "http://www.w3.org/2001/XMLSchema#";
757 const RDF_NS: &'static str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
759 const RDFS_NS: &'static str = "http://www.w3.org/2000/01/rdf-schema#";
761 const OWL_NS: &'static str = "http://www.w3.org/2002/07/owl#";
763
764 fn xsd_string() -> InternedString {
765 InternedString::new_datatype(&format!("{}string", Self::XSD_NS))
766 }
767
768 fn xsd_integer() -> InternedString {
769 InternedString::new_datatype(&format!("{}integer", Self::XSD_NS))
770 }
771
772 fn xsd_decimal() -> InternedString {
773 InternedString::new_datatype(&format!("{}decimal", Self::XSD_NS))
774 }
775
776 fn xsd_boolean() -> InternedString {
777 InternedString::new_datatype(&format!("{}boolean", Self::XSD_NS))
778 }
779
780 fn xsd_double() -> InternedString {
781 InternedString::new_datatype(&format!("{}double", Self::XSD_NS))
782 }
783
784 fn xsd_float() -> InternedString {
785 InternedString::new_datatype(&format!("{}float", Self::XSD_NS))
786 }
787
788 fn xsd_date_time() -> InternedString {
789 InternedString::new_datatype(&format!("{}dateTime", Self::XSD_NS))
790 }
791
792 fn rdf_type() -> InternedString {
793 InternedString::new(&format!("{}type", Self::RDF_NS))
794 }
795
796 fn rdfs_label() -> InternedString {
797 InternedString::new(&format!("{}label", Self::RDFS_NS))
798 }
799
800 fn rdfs_comment() -> InternedString {
801 InternedString::new(&format!("{}comment", Self::RDFS_NS))
802 }
803}
804
805impl RdfVocabulary for InternedString {}
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811
812 #[test]
813 fn test_string_interner_survives_lock_poisoning() {
814 let interner = Arc::new(StringInterner::new());
819
820 let poisoning_interner = interner.clone();
821 let handle = std::thread::spawn(move || {
822 let _guard = poisoning_interner.strings.write().unwrap();
823 panic!("intentionally poison the strings lock");
824 });
825 let _ = handle.join();
826
827 let s = interner.intern("http://example.org/after-poison");
828 assert_eq!(s.as_ref(), "http://example.org/after-poison");
829 let stats = interner.stats();
830 assert!(stats.total_requests >= 1);
831 }
832
833 #[test]
834 fn test_string_interner_basic() {
835 let interner = StringInterner::new();
836
837 let s1 = interner.intern("http://example.org/test");
838 let s2 = interner.intern("http://example.org/test");
839 let s3 = interner.intern("http://example.org/different");
840
841 assert!(Arc::ptr_eq(&s1, &s2));
843 assert!(!Arc::ptr_eq(&s1, &s3));
844
845 assert_eq!(s1.as_ref(), "http://example.org/test");
847 assert_eq!(s2.as_ref(), "http://example.org/test");
848 assert_eq!(s3.as_ref(), "http://example.org/different");
849 }
850
851 #[test]
852 fn test_string_interner_stats() {
853 let interner = StringInterner::new();
854
855 let _s1 = interner.intern("test");
857 let stats = interner.stats();
858 assert_eq!(stats.total_requests, 1);
859 assert_eq!(stats.cache_misses, 1);
860 assert_eq!(stats.cache_hits, 0);
861
862 let _s2 = interner.intern("test");
864 let stats = interner.stats();
865 assert_eq!(stats.total_requests, 2);
866 assert_eq!(stats.cache_misses, 1);
867 assert_eq!(stats.cache_hits, 1);
868 assert_eq!(stats.hit_ratio(), 0.5);
869 }
870
871 #[test]
872 fn test_string_interner_cleanup() {
873 let interner = StringInterner::new();
874
875 {
876 let _s1 = interner.intern("temporary");
877 assert_eq!(interner.len(), 1);
878 } interner.cleanup();
881 assert_eq!(interner.len(), 0);
882 }
883
884 #[test]
885 fn test_interned_string_creation() {
886 let s1 = InternedString::new("http://example.org/test");
887 let s2 = InternedString::new("http://example.org/test");
888 let s3 = InternedString::new("http://example.org/different");
889
890 assert_eq!(s1, s2);
891 assert_ne!(s1, s3);
892 assert_eq!(s1.as_str(), "http://example.org/test");
893 }
894
895 #[test]
896 fn test_interned_string_ordering() {
897 let s1 = InternedString::new("apple");
898 let s2 = InternedString::new("banana");
899 let s3 = InternedString::new("apple");
900
901 assert!(s1 < s2);
902 assert!(s2 > s1);
903 assert_eq!(s1, s3);
904
905 let mut strings = vec![s2.clone(), s1.clone(), s3.clone()];
907 strings.sort();
908 assert_eq!(strings, vec![s1, s3, s2]);
909 }
910
911 #[test]
912 fn test_interned_string_hashing() {
913 use std::collections::HashMap;
914
915 let s1 = InternedString::new("test");
916 let s2 = InternedString::new("test");
917 let s3 = InternedString::new("different");
918
919 let mut map = HashMap::new();
920 map.insert(s1.clone(), "value1");
921 map.insert(s3.clone(), "value2");
922
923 assert_eq!(map.get(&s2), Some(&"value1"));
925 assert_eq!(map.get(&s3), Some(&"value2"));
926 assert_eq!(map.len(), 2);
927 }
928
929 #[test]
930 fn test_global_interners() {
931 let iri1 = InternedString::new("http://example.org/test");
932 let iri2 = InternedString::new("http://example.org/test");
933
934 let datatype1 = InternedString::new_datatype("http://www.w3.org/2001/XMLSchema#string");
935 let datatype2 = InternedString::new_datatype("http://www.w3.org/2001/XMLSchema#string");
936
937 let lang1 = InternedString::new_language("en");
938 let lang2 = InternedString::new_language("en");
939
940 assert_eq!(iri1, iri2);
942 assert_eq!(datatype1, datatype2);
943 assert_eq!(lang1, lang2);
944 }
945
946 #[test]
947 fn test_rdf_vocabulary() {
948 let string_type = InternedString::xsd_string();
949 let integer_type = InternedString::xsd_integer();
950 let rdf_type = InternedString::rdf_type();
951
952 assert_eq!(
953 string_type.as_str(),
954 "http://www.w3.org/2001/XMLSchema#string"
955 );
956 assert_eq!(
957 integer_type.as_str(),
958 "http://www.w3.org/2001/XMLSchema#integer"
959 );
960 assert_eq!(
961 rdf_type.as_str(),
962 "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
963 );
964
965 let string_type2 = InternedString::xsd_string();
967 assert_eq!(string_type, string_type2);
968 }
969
970 #[test]
971 fn test_interned_string_display() {
972 let s = InternedString::new("http://example.org/test");
973 assert_eq!(format!("{s}"), "http://example.org/test");
974 }
975
976 #[test]
977 fn test_interned_string_deref() {
978 let s = InternedString::new("test");
979 assert_eq!(&*s, "test");
980 assert_eq!(s.len(), 4);
981 assert!(s.starts_with("te"));
982 }
983
984 #[test]
985 fn test_interned_string_conversions() {
986 let s1 = InternedString::from("test");
987 let s2 = InternedString::from("test".to_string());
988
989 assert_eq!(s1, s2);
990 assert_eq!(s1.as_str(), "test");
991 }
992
993 #[test]
994 fn test_concurrent_interning() {
995 use std::sync::Arc;
996 use std::thread;
997
998 let interner = Arc::new(StringInterner::new());
999 let handles: Vec<_> = (0..10)
1000 .map(|i| {
1001 let interner = Arc::clone(&interner);
1002 thread::spawn(move || {
1003 let s = format!("http://example.org/test{}", i % 3);
1004 (0..100).map(|_| interner.intern(&s)).collect::<Vec<_>>()
1005 })
1006 })
1007 .collect();
1008
1009 let results: Vec<Vec<Arc<str>>> = handles
1010 .into_iter()
1011 .map(|h| h.join().expect("thread should not panic"))
1012 .collect();
1013
1014 for result_set in &results {
1016 for (i, s1) in result_set.iter().enumerate() {
1017 for s2 in &result_set[i + 1..] {
1018 if s1.as_ref() == s2.as_ref() {
1019 assert!(Arc::ptr_eq(s1, s2));
1020 }
1021 }
1022 }
1023 }
1024
1025 assert!(interner.len() <= 3);
1027 }
1028
1029 #[test]
1030 fn test_term_id_mapping() {
1031 let interner = StringInterner::new();
1032
1033 let (arc1, id1) = interner.intern_with_id("test_string");
1035 let (arc2, id2) = interner.intern_with_id("test_string");
1036
1037 assert_eq!(id1, id2);
1039 assert!(Arc::ptr_eq(&arc1, &arc2));
1040
1041 let (arc3, id3) = interner.intern_with_id("different_string");
1043 assert_ne!(id1, id3);
1044 assert!(!Arc::ptr_eq(&arc1, &arc3));
1045
1046 assert_eq!(interner.get_id("test_string"), Some(id1));
1048 assert_eq!(interner.get_id("different_string"), Some(id3));
1049 assert_eq!(interner.get_id("nonexistent"), None);
1050
1051 assert_eq!(
1053 interner
1054 .get_string(id1)
1055 .expect("operation should succeed")
1056 .as_ref(),
1057 "test_string"
1058 );
1059 assert_eq!(
1060 interner
1061 .get_string(id3)
1062 .expect("operation should succeed")
1063 .as_ref(),
1064 "different_string"
1065 );
1066 assert_eq!(interner.get_string(999), None);
1067 }
1068
1069 #[test]
1070 fn test_id_mapping_stats() {
1071 let interner = StringInterner::new();
1072
1073 assert_eq!(interner.id_mapping_count(), 0);
1074
1075 interner.intern_with_id("string1");
1076 assert_eq!(interner.id_mapping_count(), 1);
1077
1078 interner.intern_with_id("string2");
1079 assert_eq!(interner.id_mapping_count(), 2);
1080
1081 interner.intern_with_id("string1");
1083 assert_eq!(interner.id_mapping_count(), 2);
1084 }
1085
1086 #[test]
1087 fn test_get_all_mappings() {
1088 let interner = StringInterner::new();
1089
1090 let (_, id1) = interner.intern_with_id("first");
1091 let (_, id2) = interner.intern_with_id("second");
1092 let (_, id3) = interner.intern_with_id("third");
1093
1094 let mappings = interner.get_all_mappings();
1095 assert_eq!(mappings.len(), 3);
1096
1097 let mut found_ids = [false; 3];
1099 for (id, string) in mappings {
1100 match string.as_ref() {
1101 "first" => {
1102 assert_eq!(id, id1);
1103 found_ids[0] = true;
1104 }
1105 "second" => {
1106 assert_eq!(id, id2);
1107 found_ids[1] = true;
1108 }
1109 "third" => {
1110 assert_eq!(id, id3);
1111 found_ids[2] = true;
1112 }
1113 _ => panic!("Unexpected string in mappings"),
1114 }
1115 }
1116 assert!(found_ids.iter().all(|&found| found));
1117 }
1118
1119 #[test]
1120 fn test_mixed_interning_modes() {
1121 let interner = StringInterner::new();
1122
1123 let arc1 = interner.intern("regular");
1125 let (_arc2, id2) = interner.intern_with_id("with_id");
1126 let arc3 = interner.intern("regular"); assert!(Arc::ptr_eq(&arc1, &arc3));
1130
1131 assert_eq!(
1133 interner
1134 .get_string(id2)
1135 .expect("operation should succeed")
1136 .as_ref(),
1137 "with_id"
1138 );
1139
1140 assert!(interner.len() >= 2);
1142 }
1143
1144 #[test]
1151 fn regression_intern_with_id_concurrent_same_string_single_id() {
1152 use std::collections::HashSet;
1153 use std::sync::Arc;
1154 use std::thread;
1155
1156 for _ in 0..20 {
1157 let interner = Arc::new(StringInterner::new());
1158 let num_threads = 16;
1159
1160 let handles: Vec<_> = (0..num_threads)
1161 .map(|_| {
1162 let interner = Arc::clone(&interner);
1163 thread::spawn(move || interner.intern_with_id("http://example.org/dup"))
1164 })
1165 .collect();
1166
1167 let results: Vec<(Arc<str>, u32)> = handles
1168 .into_iter()
1169 .map(|h| h.join().expect("thread should not panic"))
1170 .collect();
1171
1172 let unique_ids: HashSet<u32> = results.iter().map(|(_, id)| *id).collect();
1173 assert_eq!(
1174 unique_ids.len(),
1175 1,
1176 "all concurrent intern_with_id calls for the same string must return the same id"
1177 );
1178
1179 let id = *unique_ids.iter().next().expect("one id was collected");
1180 assert_eq!(interner.get_id("http://example.org/dup"), Some(id));
1181 assert_eq!(
1182 interner
1183 .get_string(id)
1184 .expect("id should resolve back to the string")
1185 .as_ref(),
1186 "http://example.org/dup"
1187 );
1188 }
1189 }
1190}