Skip to main content

diskann_disk/data_model/
graph_layout_version.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::{cmp::Ordering, fmt, io::Cursor};
7
8use byteorder::{LittleEndian, ReadBytesExt};
9use diskann::{ANNError, ANNResult};
10
11/// Graph layout version. In the format of `major.minor`.
12#[derive(Debug, PartialEq, Eq, Clone)]
13pub struct GraphLayoutVersion {
14    pub major: u32,
15    pub minor: u32,
16}
17
18impl GraphLayoutVersion {
19    pub const fn new(major: u32, minor: u32) -> Self {
20        Self { major, minor }
21    }
22
23    pub fn major_version(&self) -> u32 {
24        self.major
25    }
26
27    pub fn minor_version(&self) -> u32 {
28        self.minor
29    }
30
31    /// Serialize the `GraphLayoutVersion` object to a byte vector with 8 bytes.
32    /// Layout:
33    /// | MajorVersion (4 bytes) | MinorVersion (4 bytes) |
34    /// The layout_version contains two parts. The first 32 bits are the major version number in u32 format, and the last 32 bits are the minor version number in u32 format.
35    /// Backward incompatible layout changes should increment the major version number while backward compatible changes increments the minor version number.
36    pub fn to_bytes(&self) -> Vec<u8> {
37        let mut buffer = Vec::with_capacity(8);
38        buffer.extend_from_slice(self.major.to_le_bytes().as_ref());
39        buffer.extend_from_slice(self.minor.to_le_bytes().as_ref());
40        buffer
41    }
42}
43
44impl PartialOrd for GraphLayoutVersion {
45    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
46        Some(self.cmp(other))
47    }
48}
49
50impl Ord for GraphLayoutVersion {
51    fn cmp(&self, other: &Self) -> Ordering {
52        match self.major.cmp(&other.major) {
53            Ordering::Equal => self.minor.cmp(&other.minor),
54            major_ordering => major_ordering,
55        }
56    }
57}
58
59impl fmt::Display for GraphLayoutVersion {
60    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(formatter, "{}.{}", self.major, self.minor)
62    }
63}
64
65impl<'a> TryFrom<&'a [u8]> for GraphLayoutVersion {
66    type Error = ANNError;
67    /// Try creating a new `GraphLayoutVersion` object from a byte slice. The try_from syntax is used here instead of from because this operation can fail.
68    /// Layout:
69    /// | MajorVersion (4 bytes) | MinorVersion (4 bytes) |
70    fn try_from(value: &'a [u8]) -> ANNResult<Self> {
71        if value.len() < std::mem::size_of::<GraphLayoutVersion>() {
72            Err(ANNError::log_parse_slice_error(
73                "&[u8]".to_string(),
74                "GraphLayoutVersion".to_string(),
75                "The given bytes are not long enough to create a valid graph layout version."
76                    .to_string(),
77            ))
78        } else {
79            let mut cursor = Cursor::new(&value);
80            let major = cursor.read_u32::<LittleEndian>()?;
81            let minor = cursor.read_u32::<LittleEndian>()?;
82
83            Ok(Self::new(major, minor))
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::cmp::Ordering;
91
92    use super::GraphLayoutVersion;
93
94    #[test]
95    fn test_graph_layout_version_creation() {
96        let version = GraphLayoutVersion::new(1, 0);
97        assert_eq!(version.major_version(), 1);
98        assert_eq!(version.minor_version(), 0);
99    }
100
101    #[test]
102    fn test_graph_layout_version_comparison() {
103        let v1 = GraphLayoutVersion::new(1, 0);
104        let v2 = GraphLayoutVersion::new(1, 1);
105        let v3 = GraphLayoutVersion::new(2, 0);
106        let v4 = GraphLayoutVersion::new(2, 1);
107        let v5 = GraphLayoutVersion::new(2, 1);
108
109        assert_eq!(v1.partial_cmp(&v2), Some(Ordering::Less));
110        assert_eq!(v2.partial_cmp(&v3), Some(Ordering::Less));
111        assert_eq!(v3.partial_cmp(&v4), Some(Ordering::Less));
112        assert_eq!(v4.partial_cmp(&v5), Some(Ordering::Equal));
113    }
114
115    #[test]
116    fn test_graph_layout_version_ordering() {
117        let v1 = GraphLayoutVersion::new(1, 0);
118        let v2 = GraphLayoutVersion::new(1, 1);
119        let v3 = GraphLayoutVersion::new(2, 0);
120        let v4 = GraphLayoutVersion::new(2, 1);
121
122        assert_eq!(v1.cmp(&v2), Ordering::Less);
123        assert_eq!(v2.cmp(&v3), Ordering::Less);
124        assert_eq!(v3.cmp(&v4), Ordering::Less);
125        assert_eq!(v4.cmp(&v4), Ordering::Equal);
126    }
127
128    #[test]
129    fn test_graph_layout_version() {
130        // happy case
131        let version_bytes = [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00];
132        let version = GraphLayoutVersion::try_from(&version_bytes[..]).unwrap();
133        assert_eq!(version.major_version(), 1);
134        assert_eq!(version.minor_version(), 2);
135
136        let bytes = vec![3; std::mem::size_of::<GraphLayoutVersion>() - 1]; // bytes are too short to create a valid graph layout version
137        let result = GraphLayoutVersion::try_from(&bytes[..]);
138        assert!(result.is_err());
139    }
140}