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 id = self
245 .next_id
246 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
247
248 {
250 let mut string_to_id = self
251 .string_to_id
252 .write()
253 .unwrap_or_else(|poisoned| poisoned.into_inner());
254 string_to_id.insert(s.to_string(), id);
255 }
256 {
257 let mut id_to_string = self
258 .id_to_string
259 .write()
260 .unwrap_or_else(|poisoned| poisoned.into_inner());
261 id_to_string.insert(id, arc_str.clone());
262 }
263
264 (arc_str, id)
265 }
266
267 pub fn get_id(&self, s: &str) -> Option<u32> {
269 let string_to_id = self
270 .string_to_id
271 .read()
272 .unwrap_or_else(|poisoned| poisoned.into_inner());
273 string_to_id.get(s).copied()
274 }
275
276 pub fn get_string(&self, id: u32) -> Option<Arc<str>> {
278 let id_to_string = self
279 .id_to_string
280 .read()
281 .unwrap_or_else(|poisoned| poisoned.into_inner());
282 id_to_string.get(&id).cloned()
283 }
284
285 pub fn get_all_mappings(&self) -> Vec<(u32, Arc<str>)> {
287 let id_to_string = self
288 .id_to_string
289 .read()
290 .unwrap_or_else(|poisoned| poisoned.into_inner());
291 id_to_string
292 .iter()
293 .map(|(&id, s)| (id, s.clone()))
294 .collect()
295 }
296
297 pub fn cleanup(&self) -> usize {
301 let mut strings = self
302 .strings
303 .write()
304 .unwrap_or_else(|poisoned| poisoned.into_inner());
305 let before = strings.len();
306 strings.retain(|_, weak_ref| weak_ref.strong_count() > 0);
307 let after = strings.len();
308 before - after
309 }
310
311 pub fn stats(&self) -> InternerStats {
313 self.stats
314 .read()
315 .unwrap_or_else(|poisoned| poisoned.into_inner())
316 .clone()
317 }
318
319 pub fn len(&self) -> usize {
321 let id_count = self
323 .string_to_id
324 .read()
325 .unwrap_or_else(|poisoned| poisoned.into_inner())
326 .len();
327 let string_count = self
328 .strings
329 .read()
330 .unwrap_or_else(|poisoned| poisoned.into_inner())
331 .len();
332 std::cmp::max(id_count, string_count)
333 }
334
335 pub fn id_mapping_count(&self) -> usize {
337 self.string_to_id
338 .read()
339 .unwrap_or_else(|poisoned| poisoned.into_inner())
340 .len()
341 }
342
343 pub fn is_empty(&self) -> bool {
345 self.strings
346 .read()
347 .unwrap_or_else(|poisoned| poisoned.into_inner())
348 .is_empty()
349 }
350
351 pub fn intern_batch(&self, strings: &[&str]) -> Vec<Arc<str>> {
354 let mut result = Vec::with_capacity(strings.len());
355 let mut to_create = Vec::new();
356
357 {
359 let string_map = self
360 .strings
361 .read()
362 .unwrap_or_else(|poisoned| poisoned.into_inner());
363 for &s in strings {
364 if let Some(weak_ref) = string_map.get(s) {
365 if let Some(arc_str) = weak_ref.upgrade() {
366 result.push(arc_str);
367 continue;
368 }
369 }
370 to_create.push((result.len(), s));
371 result.push(Arc::from("")); }
373 }
374
375 if !to_create.is_empty() {
377 let mut string_map = self
378 .strings
379 .write()
380 .unwrap_or_else(|poisoned| poisoned.into_inner());
381 let mut stats = self
382 .stats
383 .write()
384 .unwrap_or_else(|poisoned| poisoned.into_inner());
385
386 for (index, s) in to_create {
387 if let Some(weak_ref) = string_map.get(s) {
389 if let Some(arc_str) = weak_ref.upgrade() {
390 result[index] = arc_str;
391 stats.cache_hits += 1;
392 continue;
393 }
394 }
395
396 let arc_str: Arc<str> = Arc::from(s);
398 let weak_ref = Arc::downgrade(&arc_str);
399 string_map.insert(s.to_string(), weak_ref);
400 result[index] = arc_str;
401
402 stats.cache_misses += 1;
403 stats.total_strings_stored += 1;
404 stats.memory_saved_bytes += s.len();
405 }
406
407 stats.total_requests += strings.len();
408 }
409
410 result
411 }
412
413 pub fn prefetch(&self, strings: &[&str]) {
416 let _ = self.intern_batch(strings);
417 }
418
419 pub fn memory_usage(&self) -> MemoryUsage {
421 let string_map_size = self
422 .strings
423 .read()
424 .unwrap_or_else(|poisoned| poisoned.into_inner())
425 .len();
426 let id_map_size = self
427 .string_to_id
428 .read()
429 .unwrap_or_else(|poisoned| poisoned.into_inner())
430 .len();
431 let stats = self
432 .stats
433 .read()
434 .unwrap_or_else(|poisoned| poisoned.into_inner());
435
436 MemoryUsage {
437 interned_strings: string_map_size,
438 id_mappings: id_map_size,
439 estimated_memory_bytes: string_map_size * 64 + id_map_size * 8, memory_saved_bytes: stats.memory_saved_bytes,
441 compression_ratio: if stats.memory_saved_bytes > 0 {
442 stats.memory_saved_bytes as f64
443 / (stats.memory_saved_bytes + string_map_size * 32) as f64
444 } else {
445 0.0
446 },
447 }
448 }
449
450 pub fn get_metrics(&self) -> InternerMetrics {
458 let cache_hits = self.cache_hit_counter.get();
459 let cache_misses = self.cache_miss_counter.get();
460 let total_requests = cache_hits + cache_misses;
461 let hit_ratio = if total_requests > 0 {
462 cache_hits as f64 / total_requests as f64
463 } else {
464 0.0
465 };
466
467 let timer_stats = self.intern_timer.get_stats();
468 let string_length_stats = self.string_length_histogram.get_stats();
469 let memory_stats = self.memory_usage_histogram.get_stats();
470
471 InternerMetrics {
472 cache_hits,
473 cache_misses,
474 total_requests,
475 hit_ratio,
476 avg_intern_time_secs: timer_stats.mean,
477 total_intern_observations: timer_stats.count,
478 avg_string_length: string_length_stats.mean,
479 total_memory_tracked_bytes: memory_stats.sum as u64,
480 }
481 }
482
483 pub fn optimize(&self) {
491 let start = std::time::Instant::now();
492
493 let cleaned_count = self.cleanup();
495
496 let current_size = {
498 let strings = self
499 .strings
500 .read()
501 .unwrap_or_else(|poisoned| poisoned.into_inner());
502 strings.len()
503 };
504
505 let optimal_capacity = ((current_size as f64 * 1.3) as usize).max(1024);
507
508 {
509 let mut strings = self
510 .strings
511 .write()
512 .unwrap_or_else(|poisoned| poisoned.into_inner());
513 let mut string_to_id = self
514 .string_to_id
515 .write()
516 .unwrap_or_else(|poisoned| poisoned.into_inner());
517 let mut id_to_string = self
518 .id_to_string
519 .write()
520 .unwrap_or_else(|poisoned| poisoned.into_inner());
521
522 let mut new_strings = HashMap::with_capacity(optimal_capacity);
524 let mut new_string_to_id = HashMap::with_capacity(optimal_capacity);
525 let mut new_id_to_string = HashMap::with_capacity(optimal_capacity);
526
527 for (key, value) in strings.drain() {
529 new_strings.insert(key, value);
530 }
531 for (key, value) in string_to_id.drain() {
532 new_string_to_id.insert(key, value);
533 }
534 for (key, value) in id_to_string.drain() {
535 new_id_to_string.insert(key, value);
536 }
537
538 *strings = new_strings;
540 *string_to_id = new_string_to_id;
541 *id_to_string = new_id_to_string;
542 }
543
544 let mem_usage = self.memory_usage();
546 self.memory_usage_histogram
547 .observe(mem_usage.estimated_memory_bytes as f64);
548
549 {
551 let mut stats = self
552 .stats
553 .write()
554 .unwrap_or_else(|poisoned| poisoned.into_inner());
555 stats.total_strings_stored = current_size;
556 }
557
558 let duration = start.elapsed();
559 tracing::debug!(
560 "Interner optimized: cleaned {} entries, rehashed to capacity {}, took {:?}",
561 cleaned_count,
562 optimal_capacity,
563 duration
564 );
565 }
566}
567
568impl Default for StringInterner {
569 fn default() -> Self {
570 Self::new()
571 }
572}
573
574impl std::fmt::Debug for StringInterner {
575 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
576 f.debug_struct("StringInterner")
577 .field(
578 "strings_count",
579 &self
580 .strings
581 .read()
582 .unwrap_or_else(|poisoned| poisoned.into_inner())
583 .len(),
584 )
585 .field(
586 "id_mappings_count",
587 &self
588 .string_to_id
589 .read()
590 .unwrap_or_else(|poisoned| poisoned.into_inner())
591 .len(),
592 )
593 .field(
594 "next_id",
595 &self.next_id.load(std::sync::atomic::Ordering::Relaxed),
596 )
597 .field(
598 "stats",
599 &self
600 .stats
601 .read()
602 .unwrap_or_else(|poisoned| poisoned.into_inner()),
603 )
604 .finish()
605 }
606}
607
608pub static IRI_INTERNER: once_cell::sync::Lazy<StringInterner> =
611 once_cell::sync::Lazy::new(StringInterner::new);
612
613pub static DATATYPE_INTERNER: once_cell::sync::Lazy<StringInterner> =
615 once_cell::sync::Lazy::new(StringInterner::new);
616
617pub static LANGUAGE_INTERNER: once_cell::sync::Lazy<StringInterner> =
619 once_cell::sync::Lazy::new(StringInterner::new);
620
621pub static STRING_INTERNER: once_cell::sync::Lazy<StringInterner> =
623 once_cell::sync::Lazy::new(StringInterner::new);
624
625#[derive(Debug, Clone)]
627pub struct InternedString {
628 inner: Arc<str>,
629}
630
631impl InternedString {
632 pub fn new(s: &str) -> Self {
634 InternedString {
635 inner: IRI_INTERNER.intern(s),
636 }
637 }
638
639 pub fn new_with_interner(s: &str, interner: &StringInterner) -> Self {
641 InternedString {
642 inner: interner.intern(s),
643 }
644 }
645
646 pub fn new_datatype(s: &str) -> Self {
648 InternedString {
649 inner: DATATYPE_INTERNER.intern(s),
650 }
651 }
652
653 pub fn new_language(s: &str) -> Self {
655 InternedString {
656 inner: LANGUAGE_INTERNER.intern(s),
657 }
658 }
659
660 pub fn as_str(&self) -> &str {
662 &self.inner
663 }
664
665 pub fn as_arc_str(&self) -> &Arc<str> {
667 &self.inner
668 }
669
670 pub fn into_arc_str(self) -> Arc<str> {
672 self.inner
673 }
674}
675
676impl std::fmt::Display for InternedString {
677 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678 write!(f, "{}", self.inner)
679 }
680}
681
682impl std::ops::Deref for InternedString {
683 type Target = str;
684
685 fn deref(&self) -> &Self::Target {
686 &self.inner
687 }
688}
689
690impl AsRef<str> for InternedString {
691 fn as_ref(&self) -> &str {
692 &self.inner
693 }
694}
695
696impl PartialEq for InternedString {
697 fn eq(&self, other: &Self) -> bool {
698 Arc::ptr_eq(&self.inner, &other.inner) || self.inner == other.inner
700 }
701}
702
703impl Eq for InternedString {}
704
705impl Hash for InternedString {
706 fn hash<H: Hasher>(&self, state: &mut H) {
707 self.inner.hash(state);
709 }
710}
711
712impl PartialOrd for InternedString {
713 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
714 Some(self.cmp(other))
715 }
716}
717
718impl Ord for InternedString {
719 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
720 self.inner.cmp(&other.inner)
721 }
722}
723
724impl From<&str> for InternedString {
725 fn from(s: &str) -> Self {
726 InternedString::new(s)
727 }
728}
729
730impl From<String> for InternedString {
731 fn from(s: String) -> Self {
732 InternedString::new(&s)
733 }
734}
735
736pub trait RdfVocabulary {
738 const XSD_NS: &'static str = "http://www.w3.org/2001/XMLSchema#";
740 const RDF_NS: &'static str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
742 const RDFS_NS: &'static str = "http://www.w3.org/2000/01/rdf-schema#";
744 const OWL_NS: &'static str = "http://www.w3.org/2002/07/owl#";
746
747 fn xsd_string() -> InternedString {
748 InternedString::new_datatype(&format!("{}string", Self::XSD_NS))
749 }
750
751 fn xsd_integer() -> InternedString {
752 InternedString::new_datatype(&format!("{}integer", Self::XSD_NS))
753 }
754
755 fn xsd_decimal() -> InternedString {
756 InternedString::new_datatype(&format!("{}decimal", Self::XSD_NS))
757 }
758
759 fn xsd_boolean() -> InternedString {
760 InternedString::new_datatype(&format!("{}boolean", Self::XSD_NS))
761 }
762
763 fn xsd_double() -> InternedString {
764 InternedString::new_datatype(&format!("{}double", Self::XSD_NS))
765 }
766
767 fn xsd_float() -> InternedString {
768 InternedString::new_datatype(&format!("{}float", Self::XSD_NS))
769 }
770
771 fn xsd_date_time() -> InternedString {
772 InternedString::new_datatype(&format!("{}dateTime", Self::XSD_NS))
773 }
774
775 fn rdf_type() -> InternedString {
776 InternedString::new(&format!("{}type", Self::RDF_NS))
777 }
778
779 fn rdfs_label() -> InternedString {
780 InternedString::new(&format!("{}label", Self::RDFS_NS))
781 }
782
783 fn rdfs_comment() -> InternedString {
784 InternedString::new(&format!("{}comment", Self::RDFS_NS))
785 }
786}
787
788impl RdfVocabulary for InternedString {}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 #[test]
796 fn test_string_interner_survives_lock_poisoning() {
797 let interner = Arc::new(StringInterner::new());
802
803 let poisoning_interner = interner.clone();
804 let handle = std::thread::spawn(move || {
805 let _guard = poisoning_interner.strings.write().unwrap();
806 panic!("intentionally poison the strings lock");
807 });
808 let _ = handle.join();
809
810 let s = interner.intern("http://example.org/after-poison");
811 assert_eq!(s.as_ref(), "http://example.org/after-poison");
812 let stats = interner.stats();
813 assert!(stats.total_requests >= 1);
814 }
815
816 #[test]
817 fn test_string_interner_basic() {
818 let interner = StringInterner::new();
819
820 let s1 = interner.intern("http://example.org/test");
821 let s2 = interner.intern("http://example.org/test");
822 let s3 = interner.intern("http://example.org/different");
823
824 assert!(Arc::ptr_eq(&s1, &s2));
826 assert!(!Arc::ptr_eq(&s1, &s3));
827
828 assert_eq!(s1.as_ref(), "http://example.org/test");
830 assert_eq!(s2.as_ref(), "http://example.org/test");
831 assert_eq!(s3.as_ref(), "http://example.org/different");
832 }
833
834 #[test]
835 fn test_string_interner_stats() {
836 let interner = StringInterner::new();
837
838 let _s1 = interner.intern("test");
840 let stats = interner.stats();
841 assert_eq!(stats.total_requests, 1);
842 assert_eq!(stats.cache_misses, 1);
843 assert_eq!(stats.cache_hits, 0);
844
845 let _s2 = interner.intern("test");
847 let stats = interner.stats();
848 assert_eq!(stats.total_requests, 2);
849 assert_eq!(stats.cache_misses, 1);
850 assert_eq!(stats.cache_hits, 1);
851 assert_eq!(stats.hit_ratio(), 0.5);
852 }
853
854 #[test]
855 fn test_string_interner_cleanup() {
856 let interner = StringInterner::new();
857
858 {
859 let _s1 = interner.intern("temporary");
860 assert_eq!(interner.len(), 1);
861 } interner.cleanup();
864 assert_eq!(interner.len(), 0);
865 }
866
867 #[test]
868 fn test_interned_string_creation() {
869 let s1 = InternedString::new("http://example.org/test");
870 let s2 = InternedString::new("http://example.org/test");
871 let s3 = InternedString::new("http://example.org/different");
872
873 assert_eq!(s1, s2);
874 assert_ne!(s1, s3);
875 assert_eq!(s1.as_str(), "http://example.org/test");
876 }
877
878 #[test]
879 fn test_interned_string_ordering() {
880 let s1 = InternedString::new("apple");
881 let s2 = InternedString::new("banana");
882 let s3 = InternedString::new("apple");
883
884 assert!(s1 < s2);
885 assert!(s2 > s1);
886 assert_eq!(s1, s3);
887
888 let mut strings = vec![s2.clone(), s1.clone(), s3.clone()];
890 strings.sort();
891 assert_eq!(strings, vec![s1, s3, s2]);
892 }
893
894 #[test]
895 fn test_interned_string_hashing() {
896 use std::collections::HashMap;
897
898 let s1 = InternedString::new("test");
899 let s2 = InternedString::new("test");
900 let s3 = InternedString::new("different");
901
902 let mut map = HashMap::new();
903 map.insert(s1.clone(), "value1");
904 map.insert(s3.clone(), "value2");
905
906 assert_eq!(map.get(&s2), Some(&"value1"));
908 assert_eq!(map.get(&s3), Some(&"value2"));
909 assert_eq!(map.len(), 2);
910 }
911
912 #[test]
913 fn test_global_interners() {
914 let iri1 = InternedString::new("http://example.org/test");
915 let iri2 = InternedString::new("http://example.org/test");
916
917 let datatype1 = InternedString::new_datatype("http://www.w3.org/2001/XMLSchema#string");
918 let datatype2 = InternedString::new_datatype("http://www.w3.org/2001/XMLSchema#string");
919
920 let lang1 = InternedString::new_language("en");
921 let lang2 = InternedString::new_language("en");
922
923 assert_eq!(iri1, iri2);
925 assert_eq!(datatype1, datatype2);
926 assert_eq!(lang1, lang2);
927 }
928
929 #[test]
930 fn test_rdf_vocabulary() {
931 let string_type = InternedString::xsd_string();
932 let integer_type = InternedString::xsd_integer();
933 let rdf_type = InternedString::rdf_type();
934
935 assert_eq!(
936 string_type.as_str(),
937 "http://www.w3.org/2001/XMLSchema#string"
938 );
939 assert_eq!(
940 integer_type.as_str(),
941 "http://www.w3.org/2001/XMLSchema#integer"
942 );
943 assert_eq!(
944 rdf_type.as_str(),
945 "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
946 );
947
948 let string_type2 = InternedString::xsd_string();
950 assert_eq!(string_type, string_type2);
951 }
952
953 #[test]
954 fn test_interned_string_display() {
955 let s = InternedString::new("http://example.org/test");
956 assert_eq!(format!("{s}"), "http://example.org/test");
957 }
958
959 #[test]
960 fn test_interned_string_deref() {
961 let s = InternedString::new("test");
962 assert_eq!(&*s, "test");
963 assert_eq!(s.len(), 4);
964 assert!(s.starts_with("te"));
965 }
966
967 #[test]
968 fn test_interned_string_conversions() {
969 let s1 = InternedString::from("test");
970 let s2 = InternedString::from("test".to_string());
971
972 assert_eq!(s1, s2);
973 assert_eq!(s1.as_str(), "test");
974 }
975
976 #[test]
977 fn test_concurrent_interning() {
978 use std::sync::Arc;
979 use std::thread;
980
981 let interner = Arc::new(StringInterner::new());
982 let handles: Vec<_> = (0..10)
983 .map(|i| {
984 let interner = Arc::clone(&interner);
985 thread::spawn(move || {
986 let s = format!("http://example.org/test{}", i % 3);
987 (0..100).map(|_| interner.intern(&s)).collect::<Vec<_>>()
988 })
989 })
990 .collect();
991
992 let results: Vec<Vec<Arc<str>>> = handles
993 .into_iter()
994 .map(|h| h.join().expect("thread should not panic"))
995 .collect();
996
997 for result_set in &results {
999 for (i, s1) in result_set.iter().enumerate() {
1000 for s2 in &result_set[i + 1..] {
1001 if s1.as_ref() == s2.as_ref() {
1002 assert!(Arc::ptr_eq(s1, s2));
1003 }
1004 }
1005 }
1006 }
1007
1008 assert!(interner.len() <= 3);
1010 }
1011
1012 #[test]
1013 fn test_term_id_mapping() {
1014 let interner = StringInterner::new();
1015
1016 let (arc1, id1) = interner.intern_with_id("test_string");
1018 let (arc2, id2) = interner.intern_with_id("test_string");
1019
1020 assert_eq!(id1, id2);
1022 assert!(Arc::ptr_eq(&arc1, &arc2));
1023
1024 let (arc3, id3) = interner.intern_with_id("different_string");
1026 assert_ne!(id1, id3);
1027 assert!(!Arc::ptr_eq(&arc1, &arc3));
1028
1029 assert_eq!(interner.get_id("test_string"), Some(id1));
1031 assert_eq!(interner.get_id("different_string"), Some(id3));
1032 assert_eq!(interner.get_id("nonexistent"), None);
1033
1034 assert_eq!(
1036 interner
1037 .get_string(id1)
1038 .expect("operation should succeed")
1039 .as_ref(),
1040 "test_string"
1041 );
1042 assert_eq!(
1043 interner
1044 .get_string(id3)
1045 .expect("operation should succeed")
1046 .as_ref(),
1047 "different_string"
1048 );
1049 assert_eq!(interner.get_string(999), None);
1050 }
1051
1052 #[test]
1053 fn test_id_mapping_stats() {
1054 let interner = StringInterner::new();
1055
1056 assert_eq!(interner.id_mapping_count(), 0);
1057
1058 interner.intern_with_id("string1");
1059 assert_eq!(interner.id_mapping_count(), 1);
1060
1061 interner.intern_with_id("string2");
1062 assert_eq!(interner.id_mapping_count(), 2);
1063
1064 interner.intern_with_id("string1");
1066 assert_eq!(interner.id_mapping_count(), 2);
1067 }
1068
1069 #[test]
1070 fn test_get_all_mappings() {
1071 let interner = StringInterner::new();
1072
1073 let (_, id1) = interner.intern_with_id("first");
1074 let (_, id2) = interner.intern_with_id("second");
1075 let (_, id3) = interner.intern_with_id("third");
1076
1077 let mappings = interner.get_all_mappings();
1078 assert_eq!(mappings.len(), 3);
1079
1080 let mut found_ids = [false; 3];
1082 for (id, string) in mappings {
1083 match string.as_ref() {
1084 "first" => {
1085 assert_eq!(id, id1);
1086 found_ids[0] = true;
1087 }
1088 "second" => {
1089 assert_eq!(id, id2);
1090 found_ids[1] = true;
1091 }
1092 "third" => {
1093 assert_eq!(id, id3);
1094 found_ids[2] = true;
1095 }
1096 _ => panic!("Unexpected string in mappings"),
1097 }
1098 }
1099 assert!(found_ids.iter().all(|&found| found));
1100 }
1101
1102 #[test]
1103 fn test_mixed_interning_modes() {
1104 let interner = StringInterner::new();
1105
1106 let arc1 = interner.intern("regular");
1108 let (_arc2, id2) = interner.intern_with_id("with_id");
1109 let arc3 = interner.intern("regular"); assert!(Arc::ptr_eq(&arc1, &arc3));
1113
1114 assert_eq!(
1116 interner
1117 .get_string(id2)
1118 .expect("operation should succeed")
1119 .as_ref(),
1120 "with_id"
1121 );
1122
1123 assert!(interner.len() >= 2);
1125 }
1126}