surrealdb_core/dbs/
node.rs

1use crate::sql::statements::info::InfoStructure;
2use crate::sql::Value;
3use revision::revisioned;
4use revision::Error;
5use serde::{Deserialize, Serialize};
6use std::fmt::{self, Display};
7use std::ops::{Add, Sub};
8use std::time::Duration;
9use uuid::Uuid;
10
11#[revisioned(revision = 2)]
12#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
13#[non_exhaustive]
14pub struct Node {
15	#[revision(start = 2, default_fn = "default_id")]
16	pub id: Uuid,
17	#[revision(start = 2, default_fn = "default_hb")]
18	pub hb: Timestamp,
19	#[revision(start = 2, default_fn = "default_gc")]
20	pub gc: bool,
21	#[revision(end = 2, convert_fn = "convert_name")]
22	pub name: String,
23	#[revision(end = 2, convert_fn = "convert_heartbeat")]
24	pub heartbeat: Timestamp,
25}
26
27impl Node {
28	/// Create a new Node entry
29	pub fn new(id: Uuid, hb: Timestamp, gc: bool) -> Self {
30		Self {
31			id,
32			hb,
33			gc,
34			..Default::default()
35		}
36	}
37	/// Mark this node as archived
38	pub fn archive(&self) -> Self {
39		Node {
40			gc: true,
41			..self.to_owned()
42		}
43	}
44	/// Check if this node is active
45	pub fn id(&self) -> Uuid {
46		self.id
47	}
48	/// Check if this node is active
49	pub fn is_active(&self) -> bool {
50		!self.gc
51	}
52	/// Check if this node is archived
53	pub fn is_archived(&self) -> bool {
54		self.gc
55	}
56	// Return the node id if archived
57	pub fn archived(&self) -> Option<Uuid> {
58		match self.is_archived() {
59			true => Some(self.id),
60			false => None,
61		}
62	}
63	// Sets the default gc value for old nodes
64	fn default_id(_revision: u16) -> Result<Uuid, Error> {
65		Ok(Uuid::default())
66	}
67	// Sets the default gc value for old nodes
68	fn default_hb(_revision: u16) -> Result<Timestamp, Error> {
69		Ok(Timestamp::default())
70	}
71	// Sets the default gc value for old nodes
72	fn default_gc(_revision: u16) -> Result<bool, Error> {
73		Ok(true)
74	}
75	// Sets the default gc value for old nodes
76	fn convert_name(&mut self, _revision: u16, value: String) -> Result<(), Error> {
77		self.id = Uuid::parse_str(&value).unwrap();
78		Ok(())
79	}
80	// Sets the default gc value for old nodes
81	fn convert_heartbeat(&mut self, _revision: u16, value: Timestamp) -> Result<(), Error> {
82		self.hb = value;
83		Ok(())
84	}
85}
86
87impl Display for Node {
88	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89		write!(f, "NODE {} SEEN {}", self.id, self.hb)?;
90		match self.gc {
91			true => write!(f, " ARCHIVED")?,
92			false => write!(f, " ACTIVE")?,
93		};
94		Ok(())
95	}
96}
97
98impl InfoStructure for Node {
99	fn structure(self) -> Value {
100		Value::from(map! {
101			"id".to_string() => Value::from(self.id),
102			"seen".to_string() => self.hb.structure(),
103			"active".to_string() => Value::from(!self.gc),
104		})
105	}
106}
107
108// This struct is meant to represent a timestamp that can be used to partially order
109// events in a cluster. It should be derived from a timestamp oracle, such as the
110// one available in TiKV via the client `TimestampExt` implementation.
111#[revisioned(revision = 1)]
112#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, PartialOrd, Deserialize, Serialize, Hash)]
113#[non_exhaustive]
114pub struct Timestamp {
115	pub value: u64,
116}
117
118impl From<u64> for Timestamp {
119	fn from(value: u64) -> Self {
120		Timestamp {
121			value,
122		}
123	}
124}
125
126impl Add<Duration> for Timestamp {
127	type Output = Timestamp;
128	fn add(self, rhs: Duration) -> Self::Output {
129		Timestamp {
130			value: self.value.wrapping_add(rhs.as_millis() as u64),
131		}
132	}
133}
134
135impl Sub<Duration> for Timestamp {
136	type Output = Timestamp;
137	fn sub(self, rhs: Duration) -> Self::Output {
138		Timestamp {
139			value: self.value.wrapping_sub(rhs.as_millis() as u64),
140		}
141	}
142}
143
144impl Display for Timestamp {
145	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146		write!(f, "{}", self.value)
147	}
148}
149
150impl InfoStructure for Timestamp {
151	fn structure(self) -> Value {
152		self.value.into()
153	}
154}
155
156#[cfg(test)]
157mod test {
158	use crate::dbs::node::Timestamp;
159	use chrono::prelude::Utc;
160	use chrono::TimeZone;
161	use std::time::Duration;
162
163	#[test]
164	fn timestamps_can_be_added_duration() {
165		let t = Utc.with_ymd_and_hms(2000, 1, 1, 12, 30, 0).unwrap();
166		let ts = Timestamp {
167			value: t.timestamp_millis() as u64,
168		};
169
170		let hour = Duration::from_secs(60 * 60);
171		let ts = ts + hour;
172		let ts = ts + hour;
173		let ts = ts + hour;
174
175		let end_time = Utc.timestamp_millis_opt(ts.value as i64).unwrap();
176		let expected_end_time = Utc.with_ymd_and_hms(2000, 1, 1, 15, 30, 0).unwrap();
177		assert_eq!(end_time, expected_end_time);
178	}
179
180	#[test]
181	fn timestamps_can_be_subtracted_duration() {
182		let t = Utc.with_ymd_and_hms(2000, 1, 1, 12, 30, 0).unwrap();
183		let ts = Timestamp {
184			value: t.timestamp_millis() as u64,
185		};
186
187		let hour = Duration::from_secs(60 * 60);
188		let ts = ts - hour;
189		let ts = ts - hour;
190		let ts = ts - hour;
191
192		let end_time = Utc.timestamp_millis_opt(ts.value as i64).unwrap();
193		let expected_end_time = Utc.with_ymd_and_hms(2000, 1, 1, 9, 30, 0).unwrap();
194		assert_eq!(end_time, expected_end_time);
195	}
196}