1use std::{collections::HashSet, sync::Arc};
2
3use anyhow::Context;
4use gix::actor::SignatureRef;
5use rkyv::{Archive, Serialize};
6use yoke::{Yoke, Yokeable};
7
8use crate::database::schema::{
9 Yoked,
10 commit::{ArchivedAuthor, Author},
11 prefixes::TAG_FAMILY,
12 repository::RepositoryId,
13};
14
15#[derive(Serialize, Archive, Debug, Yokeable)]
16pub struct Tag {
17 pub tagger: Option<Author>,
18 pub tree_id: Option<u64>,
19}
20
21impl Tag {
22 pub fn new(
23 tagger: Option<SignatureRef<'_>>,
24 tree_id: Option<u64>,
25 ) -> Result<Self, anyhow::Error> {
26 Ok(Self {
27 tagger: tagger.map(TryFrom::try_from).transpose()?,
28 tree_id,
29 })
30 }
31
32 pub fn insert(&self, batch: &TagTree, name: &str) -> Result<(), anyhow::Error> {
33 batch.insert(name, self)
34 }
35}
36
37pub struct TagTree {
38 pub db: Arc<rocksdb::DB>,
39 prefix: RepositoryId,
40}
41
42pub type YokedString = Yoked<&'static str>;
43pub type YokedTag = Yoked<&'static <Tag as Archive>::Archived>;
44
45impl TagTree {
46 pub(super) fn new(db: Arc<rocksdb::DB>, prefix: RepositoryId) -> Self {
47 Self { db, prefix }
48 }
49
50 pub fn insert(&self, name: &str, value: &Tag) -> anyhow::Result<()> {
51 let cf = self
52 .db
53 .cf_handle(TAG_FAMILY)
54 .context("missing tag column family")?;
55
56 let mut db_name = self.prefix.to_be_bytes().to_vec();
57 db_name.extend_from_slice(name.as_ref());
58
59 self.db
60 .put_cf(cf, db_name, rkyv::to_bytes::<rkyv::rancor::Error>(value)?)?;
61
62 Ok(())
63 }
64
65 pub fn remove(&self, name: &str) -> anyhow::Result<()> {
66 let cf = self
67 .db
68 .cf_handle(TAG_FAMILY)
69 .context("missing tag column family")?;
70
71 let mut db_name = self.prefix.to_be_bytes().to_vec();
72 db_name.extend_from_slice(name.as_ref());
73 self.db.delete_cf(cf, db_name)?;
74
75 Ok(())
76 }
77
78 pub fn list(&self) -> anyhow::Result<HashSet<String>> {
79 let cf = self
80 .db
81 .cf_handle(TAG_FAMILY)
82 .context("missing tag column family")?;
83
84 Ok(self
85 .db
86 .prefix_iterator_cf(cf, self.prefix.to_be_bytes())
87 .filter_map(Result::ok)
88 .filter_map(|(k, _)| {
89 Some(
90 String::from_utf8_lossy(k.strip_prefix(&self.prefix.to_be_bytes())?)
91 .to_string(),
92 )
93 })
94 .collect())
95 }
96
97 pub fn fetch_all(&self) -> anyhow::Result<Vec<(YokedString, YokedTag)>> {
98 let cf = self
99 .db
100 .cf_handle(TAG_FAMILY)
101 .context("missing tag column family")?;
102
103 let mut res = self
104 .db
105 .prefix_iterator_cf(cf, self.prefix.to_be_bytes())
106 .filter_map(Result::ok)
107 .filter_map(|(name, value)| {
108 let name = Yoke::try_attach_to_cart(name, |data| {
109 let data = data
110 .strip_prefix(&self.prefix.to_be_bytes())
111 .ok_or(())?
112 .strip_prefix(b"refs/tags/")
113 .ok_or(())?;
114 simdutf8::basic::from_utf8(data).map_err(|_| ())
115 })
116 .ok()?;
117
118 Some((name, value))
119 })
120 .map(|(name, value)| {
121 let value = Yoke::try_attach_to_cart(value, |data| {
122 rkyv::access::<_, rkyv::rancor::Error>(data)
123 })?;
124 Ok((name, value))
125 })
126 .collect::<anyhow::Result<Vec<(YokedString, YokedTag)>>>()?;
127
128 res.sort_unstable_by(|a, b| {
129 let a_tagger = a.1.get().tagger.as_ref().map(ArchivedAuthor::time);
130 let b_tagger = b.1.get().tagger.as_ref().map(ArchivedAuthor::time);
131 b_tagger.cmp(&a_tagger)
132 });
133
134 Ok(res)
135 }
136}