1use rustc_hash::FxHashSet;
26use std::sync::Arc;
27
28use serde::{Deserialize, Deserializer, Serialize, Serializer};
29
30#[derive(Debug, Clone, Eq)]
36pub struct InternedStr(Arc<str>);
37
38#[cfg(feature = "rkyv")]
40pub use rkyv_impl::AsInternedStr;
41
42#[cfg(feature = "rkyv")]
45pub type AsOptionInternedStr = rkyv::with::Map<AsInternedStr>;
46
47#[cfg(feature = "rkyv")]
50pub type AsVecInternedStr = rkyv::with::Map<AsInternedStr>;
51
52#[cfg(feature = "rkyv")]
76#[must_use = "the interner is uninstalled as soon as the guard drops"]
77pub struct InternScope {
78 installed: bool,
80}
81
82#[cfg(feature = "rkyv")]
83thread_local! {
84 static SCOPED_INTERNER: std::cell::RefCell<Option<StringInterner>> =
85 const { std::cell::RefCell::new(None) };
86}
87
88#[cfg(feature = "rkyv")]
89impl InternScope {
90 pub fn new() -> Self {
92 let installed = SCOPED_INTERNER.with(|cell| {
93 let mut slot = cell.borrow_mut();
94 if slot.is_some() {
95 false
96 } else {
97 *slot = Some(StringInterner::with_capacity(1024));
98 true
99 }
100 });
101 Self { installed }
102 }
103}
104
105#[cfg(feature = "rkyv")]
106impl Default for InternScope {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112#[cfg(feature = "rkyv")]
113impl Drop for InternScope {
114 fn drop(&mut self) {
115 if self.installed {
116 SCOPED_INTERNER.with(|cell| {
117 *cell.borrow_mut() = None;
118 });
119 }
120 }
121}
122
123#[cfg(feature = "rkyv")]
126fn intern_scoped(s: &str) -> Option<InternedStr> {
127 SCOPED_INTERNER.with(|cell| {
128 cell.try_borrow_mut()
134 .ok()?
135 .as_mut()
136 .map(|interner| interner.intern(s))
137 })
138}
139
140#[cfg(feature = "rkyv")]
141mod rkyv_impl {
142 use super::InternedStr;
143 use rkyv::Place;
144 use rkyv::rancor::Fallible;
145 use rkyv::string::ArchivedString;
146 use rkyv::with::{ArchiveWith, DeserializeWith, SerializeWith};
147
148 pub struct AsInternedStr;
151
152 impl ArchiveWith<InternedStr> for AsInternedStr {
153 type Archived = ArchivedString;
154 type Resolver = rkyv::string::StringResolver;
155
156 fn resolve_with(field: &InternedStr, resolver: Self::Resolver, out: Place<Self::Archived>) {
157 ArchivedString::resolve_from_str(field.as_str(), resolver, out);
158 }
159 }
160
161 impl<S> SerializeWith<InternedStr, S> for AsInternedStr
162 where
163 S: Fallible + rkyv::ser::Writer + rkyv::ser::Allocator + ?Sized,
164 S::Error: rkyv::rancor::Source,
165 {
166 fn serialize_with(
167 field: &InternedStr,
168 serializer: &mut S,
169 ) -> Result<Self::Resolver, S::Error> {
170 ArchivedString::serialize_from_str(field.as_str(), serializer)
171 }
172 }
173
174 impl<D> DeserializeWith<ArchivedString, InternedStr, D> for AsInternedStr
175 where
176 D: Fallible + ?Sized,
177 {
178 fn deserialize_with(
179 field: &ArchivedString,
180 _deserializer: &mut D,
181 ) -> Result<InternedStr, D::Error> {
182 let s = field.as_str();
186 Ok(super::intern_scoped(s).unwrap_or_else(|| InternedStr::new(s)))
187 }
188 }
189}
190
191impl Serialize for InternedStr {
192 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
193 self.0.serialize(serializer)
194 }
195}
196
197impl<'de> Deserialize<'de> for InternedStr {
198 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
199 let s = String::deserialize(deserializer)?;
200 Ok(Self::new(s))
201 }
202}
203
204impl PartialOrd for InternedStr {
205 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
206 Some(self.cmp(other))
207 }
208}
209
210impl Ord for InternedStr {
211 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
212 self.0.cmp(&other.0)
213 }
214}
215
216impl InternedStr {
217 pub fn new(s: impl Into<Arc<str>>) -> Self {
220 Self(s.into())
221 }
222
223 pub fn as_str(&self) -> &str {
225 &self.0
226 }
227
228 pub fn ptr_eq(&self, other: &Self) -> bool {
231 Arc::ptr_eq(&self.0, &other.0)
232 }
233}
234
235impl PartialEq for InternedStr {
236 fn eq(&self, other: &Self) -> bool {
237 if Arc::ptr_eq(&self.0, &other.0) {
239 return true;
240 }
241 self.0 == other.0
243 }
244}
245
246impl std::hash::Hash for InternedStr {
247 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
248 self.0.hash(state);
249 }
250}
251
252impl std::fmt::Display for InternedStr {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 write!(f, "{}", self.0)
255 }
256}
257
258impl AsRef<str> for InternedStr {
259 fn as_ref(&self) -> &str {
260 &self.0
261 }
262}
263
264impl std::ops::Deref for InternedStr {
265 type Target = str;
266
267 fn deref(&self) -> &Self::Target {
268 &self.0
269 }
270}
271
272impl From<&str> for InternedStr {
273 fn from(s: &str) -> Self {
274 Self::new(s)
275 }
276}
277
278impl From<String> for InternedStr {
279 fn from(s: String) -> Self {
280 Self::new(s)
281 }
282}
283
284impl From<&String> for InternedStr {
285 fn from(s: &String) -> Self {
286 Self::new(s.as_str())
287 }
288}
289
290impl From<&Self> for InternedStr {
291 fn from(s: &Self) -> Self {
292 s.clone()
293 }
294}
295
296impl PartialEq<str> for InternedStr {
297 fn eq(&self, other: &str) -> bool {
298 self.as_str() == other
299 }
300}
301
302impl PartialEq<&str> for InternedStr {
303 fn eq(&self, other: &&str) -> bool {
304 self.as_str() == *other
305 }
306}
307
308impl PartialEq<String> for InternedStr {
309 fn eq(&self, other: &String) -> bool {
310 self.as_str() == other
311 }
312}
313
314impl Default for InternedStr {
315 fn default() -> Self {
316 Self::new("")
317 }
318}
319
320impl std::borrow::Borrow<str> for InternedStr {
321 fn borrow(&self) -> &str {
322 self.as_str()
323 }
324}
325
326#[derive(Debug, Default)]
332pub struct StringInterner {
333 strings: FxHashSet<Arc<str>>,
335}
336
337impl StringInterner {
338 pub fn new() -> Self {
340 Self {
341 strings: FxHashSet::default(),
342 }
343 }
344
345 pub fn with_capacity(capacity: usize) -> Self {
347 Self {
348 strings: FxHashSet::with_capacity_and_hasher(capacity, Default::default()),
349 }
350 }
351
352 pub fn intern(&mut self, s: &str) -> InternedStr {
358 self.intern_with_status(s).0
359 }
360
361 pub fn intern_with_status(&mut self, s: &str) -> (InternedStr, bool) {
369 if let Some(existing) = self.strings.get(s) {
370 (InternedStr(existing.clone()), false)
371 } else {
372 let arc: Arc<str> = s.into();
373 self.strings.insert(arc.clone());
374 (InternedStr(arc), true)
375 }
376 }
377
378 pub fn intern_string(&mut self, s: String) -> InternedStr {
380 if let Some(existing) = self.strings.get(s.as_str()) {
381 InternedStr(existing.clone())
382 } else {
383 let arc: Arc<str> = s.into();
384 self.strings.insert(arc.clone());
385 InternedStr(arc)
386 }
387 }
388
389 pub fn contains(&self, s: &str) -> bool {
391 self.strings.contains(s)
392 }
393
394 pub fn len(&self) -> usize {
396 self.strings.len()
397 }
398
399 pub fn is_empty(&self) -> bool {
401 self.strings.is_empty()
402 }
403
404 pub fn iter(&self) -> impl Iterator<Item = &str> {
406 self.strings.iter().map(std::convert::AsRef::as_ref)
407 }
408
409 pub fn clear(&mut self) {
411 self.strings.clear();
412 }
413}
414
415#[derive(Debug, Default)]
421pub struct AccountInterner {
422 interner: StringInterner,
423}
424
425impl AccountInterner {
426 pub fn new() -> Self {
428 Self {
429 interner: StringInterner::new(),
430 }
431 }
432
433 pub fn intern(&mut self, account: &str) -> InternedStr {
435 self.interner.intern(account)
436 }
437
438 pub fn len(&self) -> usize {
440 self.interner.len()
441 }
442
443 pub fn is_empty(&self) -> bool {
445 self.interner.is_empty()
446 }
447
448 pub fn accounts(&self) -> impl Iterator<Item = &str> {
450 self.interner.iter()
451 }
452
453 pub fn accounts_with_prefix<'a>(&'a self, prefix: &'a str) -> impl Iterator<Item = &'a str> {
455 self.interner.iter().filter(move |s| s.starts_with(prefix))
456 }
457}
458
459#[derive(Debug, Default)]
463pub struct CurrencyInterner {
464 interner: StringInterner,
465}
466
467impl CurrencyInterner {
468 pub fn new() -> Self {
470 Self {
471 interner: StringInterner::new(),
472 }
473 }
474
475 pub fn intern(&mut self, currency: &str) -> InternedStr {
477 self.interner.intern(currency)
478 }
479
480 pub fn len(&self) -> usize {
482 self.interner.len()
483 }
484
485 pub fn is_empty(&self) -> bool {
487 self.interner.is_empty()
488 }
489
490 pub fn currencies(&self) -> impl Iterator<Item = &str> {
492 self.interner.iter()
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499
500 #[test]
501 fn test_interned_str_equality() {
502 let s1 = InternedStr::new("hello");
503 let s2 = InternedStr::new("hello");
504 let s3 = InternedStr::new("world");
505
506 assert_eq!(s1, s2);
507 assert_ne!(s1, s3);
508 assert_eq!(s1, "hello");
509 assert_eq!(s1, "hello".to_string());
510 }
511
512 #[test]
513 fn test_interner_deduplication() {
514 let mut interner = StringInterner::new();
515
516 let s1 = interner.intern("Expenses:Food");
517 let s2 = interner.intern("Expenses:Food");
518 let s3 = interner.intern("Assets:Bank");
519
520 assert!(s1.ptr_eq(&s2));
522
523 assert!(!s1.ptr_eq(&s3));
525
526 assert_eq!(interner.len(), 2);
528 }
529
530 #[test]
531 fn test_interner_contains() {
532 let mut interner = StringInterner::new();
533
534 interner.intern("hello");
535
536 assert!(interner.contains("hello"));
537 assert!(!interner.contains("world"));
538 }
539
540 #[test]
541 fn test_account_interner() {
542 let mut interner = AccountInterner::new();
543
544 interner.intern("Expenses:Food:Coffee");
545 interner.intern("Expenses:Food:Groceries");
546 interner.intern("Assets:Bank:Checking");
547
548 assert_eq!(interner.len(), 3);
549
550 assert_eq!(interner.accounts_with_prefix("Expenses:").count(), 2);
551 }
552
553 #[test]
554 fn test_currency_interner() {
555 let mut interner = CurrencyInterner::new();
556
557 let usd1 = interner.intern("USD");
558 let usd2 = interner.intern("USD");
559 let eur = interner.intern("EUR");
560
561 assert!(usd1.ptr_eq(&usd2));
562 assert!(!usd1.ptr_eq(&eur));
563 assert_eq!(interner.len(), 2);
564 }
565
566 #[test]
567 fn test_interned_str_hash() {
568 use std::collections::HashMap;
569
570 let s1 = InternedStr::new("key");
571 let s2 = InternedStr::new("key");
572
573 let mut map = HashMap::new();
574 map.insert(s1, 1);
575
576 assert_eq!(map.get(&s2), Some(&1));
578 }
579}
580
581#[cfg(feature = "rkyv")]
583pub use rkyv_decimal::AsDecimal;
584
585#[cfg(feature = "rkyv")]
586mod rkyv_decimal {
587 use rkyv::Place;
588 use rkyv::rancor::Fallible;
589 use rkyv::with::{ArchiveWith, DeserializeWith, SerializeWith};
590 use rust_decimal::Decimal;
591
592 pub struct AsDecimal;
595
596 impl ArchiveWith<Decimal> for AsDecimal {
597 type Archived = [u8; 16];
598 type Resolver = [(); 16];
599
600 fn resolve_with(field: &Decimal, resolver: Self::Resolver, out: Place<Self::Archived>) {
601 let bytes = field.serialize();
602 rkyv::Archive::resolve(&bytes, resolver, out);
604 }
605 }
606
607 impl<S> SerializeWith<Decimal, S> for AsDecimal
608 where
609 S: Fallible + ?Sized,
610 {
611 fn serialize_with(
612 _field: &Decimal,
613 _serializer: &mut S,
614 ) -> Result<Self::Resolver, S::Error> {
615 Ok([(); 16])
617 }
618 }
619
620 impl<D> DeserializeWith<[u8; 16], Decimal, D> for AsDecimal
621 where
622 D: Fallible + ?Sized,
623 {
624 fn deserialize_with(field: &[u8; 16], _deserializer: &mut D) -> Result<Decimal, D::Error> {
625 Ok(Decimal::deserialize(*field))
626 }
627 }
628}
629
630#[cfg(feature = "rkyv")]
633pub use rkyv_date::AsNaiveDate;
634
635#[cfg(feature = "rkyv")]
636mod rkyv_date {
637 use crate::NaiveDate;
638 use rkyv::Place;
639 use rkyv::rancor::Fallible;
640 use rkyv::with::{ArchiveWith, DeserializeWith, SerializeWith};
641
642 pub struct AsNaiveDate;
645
646 const UNIX_EPOCH: NaiveDate = jiff::civil::date(1970, 1, 1);
647
648 impl ArchiveWith<NaiveDate> for AsNaiveDate {
649 type Archived = rkyv::Archived<i32>;
650 type Resolver = ();
651
652 fn resolve_with(field: &NaiveDate, _resolver: Self::Resolver, out: Place<Self::Archived>) {
653 let days = field.since(UNIX_EPOCH).unwrap_or_default().get_days();
654 rkyv::Archive::resolve(&days, (), out);
655 }
656 }
657
658 impl<S> SerializeWith<NaiveDate, S> for AsNaiveDate
659 where
660 S: Fallible + ?Sized,
661 {
662 fn serialize_with(
663 _field: &NaiveDate,
664 _serializer: &mut S,
665 ) -> Result<Self::Resolver, S::Error> {
666 Ok(())
667 }
668 }
669
670 impl<D> DeserializeWith<rkyv::Archived<i32>, NaiveDate, D> for AsNaiveDate
671 where
672 D: Fallible + ?Sized,
673 {
674 fn deserialize_with(
675 field: &rkyv::Archived<i32>,
676 _deserializer: &mut D,
677 ) -> Result<NaiveDate, D::Error> {
678 let days = field.to_native();
679 Ok(UNIX_EPOCH
680 .checked_add(jiff::Span::new().days(i64::from(days)))
681 .expect("valid date"))
682 }
683 }
684}