1use anyhow::{ensure, Context, Result};
7use mmap_rs::Mmap;
8use value_traits::slices::SliceByValue;
9
10use super::suffixes::*;
11use super::*;
12use crate::graph::NodeId;
13
14pub trait MaybeTimestamps {}
17impl<T: OptTimestamps> MaybeTimestamps for T {}
18
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct NoTimestamps;
22impl MaybeTimestamps for NoTimestamps {}
23
24#[diagnostic::on_unimplemented(
25 label = "does not have Timestamp properties loaded",
26 note = "Use `let graph = graph.load_properties(|props| props.load_timestamps()).unwrap()` to load them",
27 note = "Or replace `graph.init_properties()` with `graph.load_all_properties::<DynMphf>().unwrap()` to load all properties"
28)]
29pub trait OptTimestamps: MaybeTimestamps + PropertiesBackend {
31 fn author_timestamp(&self, node: NodeId) -> PropertiesResult<'_, Option<i64>, Self>;
33 fn author_timestamp_offset(&self, node: NodeId) -> PropertiesResult<'_, Option<i16>, Self>;
35 fn committer_timestamp(&self, node: NodeId) -> PropertiesResult<'_, Option<i64>, Self>;
37 fn committer_timestamp_offset(&self, node: NodeId) -> PropertiesResult<'_, Option<i16>, Self>;
39}
40
41#[diagnostic::on_unimplemented(
42 label = "does not have Timestamp properties loaded",
43 note = "Use `let graph = graph.load_properties(|props| props.load_timestamps()).unwrap()` to load them",
44 note = "Or replace `graph.init_properties()` with `graph.load_all_properties::<DynMphf>().unwrap()` to load all properties"
45)]
46pub trait Timestamps: OptTimestamps<DataFilesAvailability = GuaranteedDataFiles> {}
48impl<T: OptTimestamps<DataFilesAvailability = GuaranteedDataFiles>> Timestamps for T {}
49
50pub struct OptMappedTimestamps {
51 author_timestamp: Result<NumberMmap<BigEndian, i64, Mmap>, UnavailableProperty>,
52 author_timestamp_offset: Result<NumberMmap<BigEndian, i16, Mmap>, UnavailableProperty>,
53 committer_timestamp: Result<NumberMmap<BigEndian, i64, Mmap>, UnavailableProperty>,
54 committer_timestamp_offset: Result<NumberMmap<BigEndian, i16, Mmap>, UnavailableProperty>,
55}
56impl PropertiesBackend for OptMappedTimestamps {
57 type DataFilesAvailability = OptionalDataFiles;
58}
59impl OptTimestamps for OptMappedTimestamps {
60 #[inline(always)]
61 fn author_timestamp(&self, node: NodeId) -> PropertiesResult<'_, Option<i64>, Self> {
62 self.author_timestamp
63 .as_ref()
64 .map(|author_timestamps| author_timestamps.get_value(node))
65 }
66 #[inline(always)]
67 fn author_timestamp_offset(&self, node: NodeId) -> PropertiesResult<'_, Option<i16>, Self> {
68 self.author_timestamp_offset
69 .as_ref()
70 .map(|author_timestamp_offsets| author_timestamp_offsets.get_value(node))
71 }
72 #[inline(always)]
73 fn committer_timestamp(&self, node: NodeId) -> PropertiesResult<'_, Option<i64>, Self> {
74 self.committer_timestamp
75 .as_ref()
76 .map(|committer_timestamps| committer_timestamps.get_value(node))
77 }
78 #[inline(always)]
79 fn committer_timestamp_offset(&self, node: NodeId) -> PropertiesResult<'_, Option<i16>, Self> {
80 self.committer_timestamp_offset
81 .as_ref()
82 .map(|committer_timestamp_offsets| committer_timestamp_offsets.get_value(node))
83 }
84}
85
86pub struct MappedTimestamps {
87 author_timestamp: NumberMmap<BigEndian, i64, Mmap>,
88 author_timestamp_offset: NumberMmap<BigEndian, i16, Mmap>,
89 committer_timestamp: NumberMmap<BigEndian, i64, Mmap>,
90 committer_timestamp_offset: NumberMmap<BigEndian, i16, Mmap>,
91}
92impl PropertiesBackend for MappedTimestamps {
93 type DataFilesAvailability = GuaranteedDataFiles;
94}
95
96impl OptTimestamps for MappedTimestamps {
97 #[inline(always)]
98 fn author_timestamp(&self, node: NodeId) -> Option<i64> {
99 self.author_timestamp.get_value(node)
100 }
101 #[inline(always)]
102 fn author_timestamp_offset(&self, node: NodeId) -> Option<i16> {
103 self.author_timestamp_offset.get_value(node)
104 }
105 #[inline(always)]
106 fn committer_timestamp(&self, node: NodeId) -> Option<i64> {
107 self.committer_timestamp.get_value(node)
108 }
109 #[inline(always)]
110 fn committer_timestamp_offset(&self, node: NodeId) -> Option<i16> {
111 self.committer_timestamp_offset.get_value(node)
112 }
113}
114
115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
116pub struct VecTimestamps {
117 author_timestamp: Vec<i64>,
118 author_timestamp_offset: Vec<i16>,
119 committer_timestamp: Vec<i64>,
120 committer_timestamp_offset: Vec<i16>,
121}
122
123impl VecTimestamps {
124 #[allow(clippy::type_complexity)]
127 pub fn new(
128 timestamps: Vec<(Option<i64>, Option<i16>, Option<i64>, Option<i16>)>,
129 ) -> Result<Self> {
130 let mut author_timestamp = Vec::with_capacity(timestamps.len());
131 let mut author_timestamp_offset = Vec::with_capacity(timestamps.len());
132 let mut committer_timestamp = Vec::with_capacity(timestamps.len());
133 let mut committer_timestamp_offset = Vec::with_capacity(timestamps.len());
134 for (a_ts, a_ts_o, c_ts, c_ts_o) in timestamps {
135 ensure!(
136 a_ts != Some(i64::MIN),
137 "author timestamp may not be {}",
138 i64::MIN
139 );
140 ensure!(
141 a_ts_o != Some(i16::MIN),
142 "author timestamp offset may not be {}",
143 i16::MIN
144 );
145 ensure!(
146 c_ts != Some(i64::MIN),
147 "committer timestamp may not be {}",
148 i64::MIN
149 );
150 ensure!(
151 c_ts_o != Some(i16::MIN),
152 "committer timestamp offset may not be {}",
153 i16::MIN
154 );
155 author_timestamp.push(a_ts.unwrap_or(i64::MIN));
156 author_timestamp_offset.push(a_ts_o.unwrap_or(i16::MIN));
157 committer_timestamp.push(c_ts.unwrap_or(i64::MIN));
158 committer_timestamp_offset.push(c_ts_o.unwrap_or(i16::MIN));
159 }
160 Ok(VecTimestamps {
161 author_timestamp,
162 author_timestamp_offset,
163 committer_timestamp,
164 committer_timestamp_offset,
165 })
166 }
167}
168
169impl PropertiesBackend for VecTimestamps {
170 type DataFilesAvailability = GuaranteedDataFiles;
171}
172impl OptTimestamps for VecTimestamps {
173 #[inline(always)]
174 fn author_timestamp(&self, node: NodeId) -> Option<i64> {
175 self.author_timestamp.get_value(node)
176 }
177 #[inline(always)]
178 fn author_timestamp_offset(&self, node: NodeId) -> Option<i16> {
179 self.author_timestamp_offset.get_value(node)
180 }
181 #[inline(always)]
182 fn committer_timestamp(&self, node: NodeId) -> Option<i64> {
183 self.committer_timestamp.get_value(node)
184 }
185 #[inline(always)]
186 fn committer_timestamp_offset(&self, node: NodeId) -> Option<i16> {
187 self.committer_timestamp_offset.get_value(node)
188 }
189}
190
191impl<
192 MAPS: MaybeMaps,
193 PERSONS: MaybePersons,
194 CONTENTS: MaybeContents,
195 STRINGS: MaybeStrings,
196 LABELNAMES: MaybeLabelNames,
197 > SwhGraphProperties<MAPS, NoTimestamps, PERSONS, CONTENTS, STRINGS, LABELNAMES>
198{
199 pub fn load_timestamps(
207 self,
208 ) -> Result<SwhGraphProperties<MAPS, MappedTimestamps, PERSONS, CONTENTS, STRINGS, LABELNAMES>>
209 {
210 let OptMappedTimestamps {
211 author_timestamp,
212 author_timestamp_offset,
213 committer_timestamp,
214 committer_timestamp_offset,
215 } = self.get_timestamps()?;
216 let timestamps = MappedTimestamps {
217 author_timestamp: author_timestamp?,
218 author_timestamp_offset: author_timestamp_offset?,
219 committer_timestamp: committer_timestamp?,
220 committer_timestamp_offset: committer_timestamp_offset?,
221 };
222 self.with_timestamps(timestamps)
223 }
224
225 pub fn opt_load_timestamps(
227 self,
228 ) -> Result<SwhGraphProperties<MAPS, OptMappedTimestamps, PERSONS, CONTENTS, STRINGS, LABELNAMES>>
229 {
230 let timestamps = self.get_timestamps()?;
231 self.with_timestamps(timestamps)
232 }
233
234 fn get_timestamps(&self) -> Result<OptMappedTimestamps> {
235 Ok(OptMappedTimestamps {
236 author_timestamp: load_if_exists(&self.path, AUTHOR_TIMESTAMP, |path| {
237 NumberMmap::new(path, self.num_nodes).context("Could not load author_timestamp")
238 })?,
239 author_timestamp_offset: load_if_exists(&self.path, AUTHOR_TIMESTAMP_OFFSET, |path| {
240 NumberMmap::new(path, self.num_nodes)
241 .context("Could not load author_timestamp_offset")
242 })?,
243 committer_timestamp: load_if_exists(&self.path, COMMITTER_TIMESTAMP, |path| {
244 NumberMmap::new(path, self.num_nodes).context("Could not load committer_timestamp")
245 })?,
246 committer_timestamp_offset: load_if_exists(
247 &self.path,
248 COMMITTER_TIMESTAMP_OFFSET,
249 |path| {
250 NumberMmap::new(path, self.num_nodes)
251 .context("Could not load committer_timestamp_offset")
252 },
253 )?,
254 })
255 }
256
257 pub fn with_timestamps<TIMESTAMPS: MaybeTimestamps>(
260 self,
261 timestamps: TIMESTAMPS,
262 ) -> Result<SwhGraphProperties<MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>> {
263 Ok(SwhGraphProperties {
264 maps: self.maps,
265 timestamps,
266 persons: self.persons,
267 contents: self.contents,
268 strings: self.strings,
269 label_names: self.label_names,
270 path: self.path,
271 num_nodes: self.num_nodes,
272 label_names_are_in_base64_order: self.label_names_are_in_base64_order,
273 })
274 }
275}
276
277impl<
282 MAPS: MaybeMaps,
283 TIMESTAMPS: OptTimestamps,
284 PERSONS: MaybePersons,
285 CONTENTS: MaybeContents,
286 STRINGS: MaybeStrings,
287 LABELNAMES: MaybeLabelNames,
288 > SwhGraphProperties<MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
289{
290 #[inline]
297 pub fn author_timestamp(
298 &self,
299 node_id: NodeId,
300 ) -> PropertiesResult<'_, Option<i64>, TIMESTAMPS> {
301 TIMESTAMPS::map_if_available(self.try_author_timestamp(node_id), |author_timestamp| {
302 author_timestamp.unwrap_or_else(|e| panic!("Cannot get author timestamp: {e}"))
303 })
304 }
305
306 #[inline]
312 pub fn try_author_timestamp(
313 &self,
314 node_id: NodeId,
315 ) -> PropertiesResult<'_, Result<Option<i64>, OutOfBoundError>, TIMESTAMPS> {
316 TIMESTAMPS::map_if_available(
317 self.timestamps.author_timestamp(node_id),
318 |author_timestamp| match author_timestamp {
319 None => Err(OutOfBoundError {
320 index: node_id,
321 len: self.num_nodes,
322 }),
323 Some(i64::MIN) => Ok(None),
324 Some(ts) => Ok(Some(ts)),
325 },
326 )
327 }
328
329 #[inline]
335 pub fn author_timestamp_offset(
336 &self,
337 node_id: NodeId,
338 ) -> PropertiesResult<'_, Option<i16>, TIMESTAMPS> {
339 TIMESTAMPS::map_if_available(
340 self.try_author_timestamp_offset(node_id),
341 |author_timestamp_offset| {
342 author_timestamp_offset
343 .unwrap_or_else(|e| panic!("Cannot get author timestamp offset: {e}"))
344 },
345 )
346 }
347
348 #[inline]
353 pub fn try_author_timestamp_offset(
354 &self,
355 node_id: NodeId,
356 ) -> PropertiesResult<'_, Result<Option<i16>, OutOfBoundError>, TIMESTAMPS> {
357 TIMESTAMPS::map_if_available(
358 self.timestamps.author_timestamp_offset(node_id),
359 |author_timestamp_offset| match author_timestamp_offset {
360 None => Err(OutOfBoundError {
361 index: node_id,
362 len: self.num_nodes,
363 }),
364 Some(i16::MIN) => Ok(None),
365 Some(offset) => Ok(Some(offset)),
366 },
367 )
368 }
369
370 #[inline]
376 pub fn committer_timestamp(
377 &self,
378 node_id: NodeId,
379 ) -> PropertiesResult<'_, Option<i64>, TIMESTAMPS> {
380 TIMESTAMPS::map_if_available(
381 self.try_committer_timestamp(node_id),
382 |committer_timestamp| {
383 committer_timestamp
384 .unwrap_or_else(|e| panic!("Cannot get committer timestamp: {e}"))
385 },
386 )
387 }
388
389 #[inline]
394 pub fn try_committer_timestamp(
395 &self,
396 node_id: NodeId,
397 ) -> PropertiesResult<'_, Result<Option<i64>, OutOfBoundError>, TIMESTAMPS> {
398 TIMESTAMPS::map_if_available(
399 self.timestamps.committer_timestamp(node_id),
400 |committer_timestamp| match committer_timestamp {
401 None => Err(OutOfBoundError {
402 index: node_id,
403 len: self.num_nodes,
404 }),
405 Some(i64::MIN) => Ok(None),
406 Some(ts) => Ok(Some(ts)),
407 },
408 )
409 }
410
411 #[inline]
417 pub fn committer_timestamp_offset(
418 &self,
419 node_id: NodeId,
420 ) -> PropertiesResult<'_, Option<i16>, TIMESTAMPS> {
421 TIMESTAMPS::map_if_available(
422 self.try_committer_timestamp_offset(node_id),
423 |committer_timestamp_offset| {
424 committer_timestamp_offset
425 .unwrap_or_else(|e| panic!("Cannot get committer timestamp: {e}"))
426 },
427 )
428 }
429
430 #[inline]
435 pub fn try_committer_timestamp_offset(
436 &self,
437 node_id: NodeId,
438 ) -> PropertiesResult<'_, Result<Option<i16>, OutOfBoundError>, TIMESTAMPS> {
439 TIMESTAMPS::map_if_available(
440 self.timestamps.committer_timestamp_offset(node_id),
441 |committer_timestamp_offset| match committer_timestamp_offset {
442 None => Err(OutOfBoundError {
443 index: node_id,
444 len: self.num_nodes,
445 }),
446 Some(i16::MIN) => Ok(None),
447 Some(offset) => Ok(Some(offset)),
448 },
449 )
450 }
451}