Skip to main content

ic_canister_kit/functions/
record.rs

1use candid::CandidType;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    identity::CallerId,
6    types::{PageData, QueryPage, QueryPageError},
7};
8
9/// 记录 id
10#[derive(CandidType, Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
11pub struct RecordId(u64);
12
13impl From<u64> for RecordId {
14    fn from(value: u64) -> Self {
15        Self(value)
16    }
17}
18
19impl RecordId {
20    /// 取出内部数据
21    pub fn into_inner(&self) -> u64 {
22        self.0
23    }
24    /// 下一个 id
25    pub fn next(self) -> Self {
26        Self(self.0 + 1)
27    }
28}
29
30/// 迁移内容
31#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
32pub struct MigratedRecords<Record> {
33    /// 一共被删除的记录个数
34    pub removed: u64,
35    /// 当前记录个数
36    pub next_id: u64,
37    /// 本次迁移的记录
38    pub records: Vec<Record>,
39}
40
41/// 查询
42pub trait Searchable<Record> {
43    /// 查询
44    fn test(&self, record: &Record) -> bool;
45}
46
47/// 可以记录的操作
48pub trait Recordable<Record, RecordTopic, Search: Searchable<Record>> {
49    // 查询
50    /// 查询所有
51    fn record_find_all(&self) -> &[Record];
52
53    // 修改
54    /// 插入记录
55    fn record_push(&mut self, caller: CallerId, topic: RecordTopic, content: String) -> RecordId;
56    /// 更新记录
57    fn record_update(&mut self, record_id: RecordId, done: String);
58    /// 迁移
59    fn record_migrate(&mut self, max: u32) -> MigratedRecords<Record>;
60
61    /// 分页查询
62    fn record_find_by_page(
63        &self,
64        page: &QueryPage,
65        max: u32,
66        search: &Option<Search>,
67    ) -> Result<PageData<&Record>, QueryPageError> {
68        let list = self.record_find_all();
69        if let Some(search) = search {
70            return page.query_desc_by_list_and_filter(list, max, |item| search.test(item));
71        }
72        page.query_desc_by_list(list, max)
73    }
74}
75
76// ================== 简单实现 ==================
77
78/// 记录功能简单实现
79pub mod basic {
80    use std::collections::HashSet;
81
82    use candid::CandidType;
83    use serde::{Deserialize, Serialize};
84
85    use crate::{
86        functions::{
87            record::MigratedRecords,
88            types::{RecordId, Recordable, Searchable},
89        },
90        identity::CallerId,
91        types::TimestampNanos,
92    };
93
94    /// 记录主题
95    pub type RecordTopic = u8;
96
97    /// 每条记录
98    #[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
99    pub struct Record {
100        /// 记录 id
101        pub id: RecordId,
102        /// 创建时间戳 纳秒
103        pub created: TimestampNanos,
104        /// 调用人
105        pub caller: CallerId,
106        /// 记录主题
107        pub topic: RecordTopic,
108        /// 记录内容
109        pub content: String,
110        /// 完成时间 完成结果
111        pub done: Option<(TimestampNanos, String)>,
112    }
113
114    impl Record {
115        #[inline]
116        fn same(&self, id: &RecordId) -> bool {
117            self.id == *id
118        }
119
120        #[inline]
121        fn update(&mut self, done: String) {
122            self.done = Some((crate::times::now(), done));
123        }
124    }
125
126    /// 记录检索
127    #[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
128    pub struct RecordSearch {
129        /// id 过滤
130        pub id: Option<(Option<RecordId>, Option<RecordId>)>,
131        /// 创建时间过滤
132        pub created: Option<(Option<TimestampNanos>, Option<TimestampNanos>)>,
133        /// 调用人过滤
134        pub caller: Option<HashSet<CallerId>>,
135        /// 主题过滤
136        pub topic: Option<HashSet<RecordTopic>>,
137        /// 内容过滤
138        pub content: Option<String>,
139    }
140
141    impl Searchable<Record> for RecordSearch {
142        #[allow(unused)]
143        #[inline]
144        fn test(&self, record: &Record) -> bool {
145            if let Some((id_min, id_max)) = &self.id {
146                if let Some(id_min) = &id_min
147                    && record.id < *id_min
148                {
149                    return false;
150                }
151                if let Some(id_max) = &id_max
152                    && *id_max < record.id
153                {
154                    return false;
155                }
156            }
157            if let Some(created) = self.created {
158                let (created_min, created_max) = created;
159                if let Some(created_min) = created_min
160                    && record.created < created_min
161                {
162                    return false;
163                }
164                if let Some(created_max) = created_max
165                    && created_max < record.created
166                {
167                    return false;
168                }
169            }
170            if let Some(caller) = &self.caller
171                && !caller.contains(&record.caller)
172            {
173                return false;
174            }
175            if let Some(topic) = &self.topic
176                && !topic.contains(&record.topic)
177            {
178                return false;
179            }
180            if let Some(content) = &self.content
181                && !record.content.contains(content)
182            {
183                return false;
184            }
185            true
186        }
187    }
188
189    /// 持久化的记录对象
190    #[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
191    pub struct Records {
192        /// 最大保存个数
193        pub max: u64,
194        /// 删除的个数
195        pub removed: u64,
196        /// 下一个未使用的 id
197        pub next_id: RecordId,
198        /// 当前保存的个数
199        pub records: Vec<Record>,
200    }
201
202    impl Default for Records {
203        fn default() -> Self {
204            Self {
205                max: 1024 * 64, // 假设一条占用 1KB 则最大 64MB 记录
206                removed: Default::default(),
207                next_id: Default::default(),
208                records: Default::default(),
209            }
210        }
211    }
212
213    impl Recordable<Record, RecordTopic, RecordSearch> for Records {
214        // 查询
215
216        // 查询所有 正序
217        fn record_find_all(&self) -> &[Record] {
218            &self.records
219        }
220
221        // 修改
222        fn record_push(&mut self, caller: CallerId, topic: RecordTopic, content: String) -> RecordId {
223            // 判断最大个数
224            if self.max <= self.records.len() as u64 {
225                let (_migrated, left) = self.records.split_at(1);
226                self.records = left.to_owned();
227                self.removed += 1;
228            }
229
230            let id = self.next_id;
231
232            self.next_id = self.next_id.next();
233
234            self.records.push(Record {
235                id,
236                created: crate::times::now(),
237                caller,
238                topic,
239                content,
240                done: None,
241            });
242
243            id
244        }
245
246        /// 更新记录
247        fn record_update(&mut self, record_id: RecordId, done: String) {
248            let list = &mut self.records;
249            let mut index = list.len();
250            loop {
251                index -= 1;
252                if let Some(item) = list.get_mut(index) {
253                    if item.same(&record_id) {
254                        item.update(done);
255                        break;
256                    }
257                } else {
258                    break;
259                }
260                if index == 0 {
261                    break;
262                }
263            }
264        }
265
266        // 迁移
267        fn record_migrate(&mut self, max: u32) -> MigratedRecords<Record> {
268            let removed = self.removed;
269            let next_id = self.next_id.into_inner();
270            let records = if self.records.len() < max as usize {
271                std::mem::take(&mut self.records) // 全部取走
272            } else {
273                let (migrated, left) = self.records.split_at(max as usize);
274                let migrated = migrated.to_owned();
275                self.records = left.to_owned();
276                migrated
277            };
278            MigratedRecords {
279                removed,
280                next_id,
281                records,
282            }
283        }
284    }
285
286    /// 记录检索
287    #[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
288    pub struct RecordSearchArg {
289        /// id 过滤
290        pub id: Option<(Option<u64>, Option<u64>)>,
291        /// 创建时间过滤
292        pub created: Option<(Option<u64>, Option<u64>)>,
293        /// 调用人过滤
294        pub caller: Option<HashSet<CallerId>>,
295        /// 主题过滤
296        pub topic: Option<HashSet<String>>,
297        /// 内容过滤
298        pub content: Option<String>,
299    }
300
301    impl RecordSearchArg {
302        /// 参数转变
303        pub fn into<E, F: Fn(&str) -> Result<RecordTopic, E>>(self, f: F) -> Result<RecordSearch, E> {
304            Ok(RecordSearch {
305                id: self.id.map(|(a, b)| (a.map(|a| a.into()), b.map(|b| b.into()))),
306                created: self
307                    .created
308                    .map(|(a, b)| (a.map(|a| (a as i128).into()), b.map(|b| (b as i128).into()))),
309                caller: self.caller,
310                topic: self
311                    .topic
312                    .map(|topic| topic.iter().map(|t| f(t)).collect::<Result<HashSet<_>, _>>())
313                    .transpose()?,
314                content: self.content,
315            })
316        }
317    }
318}