1use std::fmt::{self, Display};
2use std::ops::{Add, Sub};
3use std::time::Duration;
4
5use revision::revisioned;
6use serde::{Deserialize, Serialize};
7use surrealdb_types::{SqlFormat, ToSql, write_sql};
8use uuid::Uuid;
9
10use crate::expr::statements::info::InfoStructure;
11use crate::kvs::impl_kv_value_revisioned;
12use crate::val::{Object, Value};
13
14#[revisioned(revision = 2)]
16#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
17pub struct Node {
18 pub id: Uuid,
20 pub heartbeat: Timestamp,
22 pub gc: bool,
24 #[revision(start = 2)]
28 pub http_endpoint: Option<String>,
29}
30
31impl_kv_value_revisioned!(Node);
32
33impl Node {
34 pub fn new(id: Uuid, hb: Timestamp, gc: bool) -> Self {
36 Self {
37 id,
38 heartbeat: hb,
39 gc,
40 http_endpoint: None,
41 }
42 }
43
44 pub fn new_with_endpoint(
47 id: Uuid,
48 hb: Timestamp,
49 gc: bool,
50 http_endpoint: Option<String>,
51 ) -> Self {
52 Self {
53 id,
54 heartbeat: hb,
55 gc,
56 http_endpoint,
57 }
58 }
59 pub fn archive(&self) -> Self {
61 Node {
62 gc: true,
63 ..self.to_owned()
64 }
65 }
66 pub fn id(&self) -> Uuid {
68 self.id
69 }
70 pub fn is_active(&self) -> bool {
72 !self.gc
73 }
74 pub fn is_archived(&self) -> bool {
76 self.gc
77 }
78 pub fn archived(&self) -> Option<Uuid> {
80 self.is_archived().then_some(self.id)
81 }
82}
83
84impl ToSql for Node {
85 fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
86 write_sql!(f, sql_fmt, "NODE {} SEEN {}", self.id, self.heartbeat);
87 if self.gc {
88 write_sql!(f, sql_fmt, " ARCHIVED");
89 } else {
90 write_sql!(f, sql_fmt, " ACTIVE");
91 }
92 }
93}
94
95impl InfoStructure for Node {
96 fn structure(self) -> Value {
97 let object = map! {
98 "id" => Value::Uuid(self.id.into()),
99 "seen" => self.heartbeat.structure(),
100 "active" => Value::Bool(!self.gc),
101 };
102 Value::Object(Object::from(object))
103 }
104}
105
106#[revisioned(revision = 1)]
110#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, PartialOrd, Deserialize, Serialize, Hash)]
111pub struct Timestamp {
112 pub value: u64,
113}
114
115impl From<u64> for Timestamp {
116 fn from(value: u64) -> Self {
117 Timestamp {
118 value,
119 }
120 }
121}
122
123impl Add<Duration> for Timestamp {
124 type Output = Timestamp;
125 fn add(self, rhs: Duration) -> Self::Output {
126 Timestamp {
127 value: self.value.wrapping_add(rhs.as_millis() as u64),
128 }
129 }
130}
131
132impl Sub<Duration> for Timestamp {
133 type Output = Timestamp;
134 fn sub(self, rhs: Duration) -> Self::Output {
135 Timestamp {
136 value: self.value.wrapping_sub(rhs.as_millis() as u64),
137 }
138 }
139}
140
141impl Display for Timestamp {
142 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143 write!(f, "{}", self.value)
144 }
145}
146
147impl ToSql for Timestamp {
148 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
149 write_sql!(f, fmt, "{}", self.value)
150 }
151}
152
153impl InfoStructure for Timestamp {
154 fn structure(self) -> Value {
155 self.value.into()
156 }
157}
158
159#[cfg(test)]
160mod test {
161 use std::time::Duration;
162
163 use chrono::TimeZone;
164 use chrono::prelude::Utc;
165
166 use crate::dbs::node::Timestamp;
167
168 #[test]
169 fn timestamps_can_be_added_duration() {
170 let t = Utc.with_ymd_and_hms(2000, 1, 1, 12, 30, 0).unwrap();
171 let ts = Timestamp {
172 value: t.timestamp_millis() as u64,
173 };
174
175 let hour = Duration::from_secs(60 * 60);
176 let ts = ts + hour;
177 let ts = ts + hour;
178 let ts = ts + hour;
179
180 let end_time = Utc.timestamp_millis_opt(ts.value as i64).unwrap();
181 let expected_end_time = Utc.with_ymd_and_hms(2000, 1, 1, 15, 30, 0).unwrap();
182 assert_eq!(end_time, expected_end_time);
183 }
184
185 #[test]
186 fn timestamps_can_be_subtracted_duration() {
187 let t = Utc.with_ymd_and_hms(2000, 1, 1, 12, 30, 0).unwrap();
188 let ts = Timestamp {
189 value: t.timestamp_millis() as u64,
190 };
191
192 let hour = Duration::from_secs(60 * 60);
193 let ts = ts - hour;
194 let ts = ts - hour;
195 let ts = ts - hour;
196
197 let end_time = Utc.timestamp_millis_opt(ts.value as i64).unwrap();
198 let expected_end_time = Utc.with_ymd_and_hms(2000, 1, 1, 9, 30, 0).unwrap();
199 assert_eq!(end_time, expected_end_time);
200 }
201}