1use std::{pin::pin, sync::LazyLock};
2use tank::{
3 DataSet, Driver, Entity, Executor, QueryResult, Result, RowsAffected, SqlWriter, cols, expr,
4 join,
5 stream::{StreamExt, TryStreamExt},
6};
7use time::{Date, Month, OffsetDateTime, Time, UtcOffset, macros::date};
8use tokio::sync::Mutex;
9use uuid::Uuid;
10
11#[derive(Entity)]
12#[tank(schema = "operations", name = "radio_operator")]
13pub struct Operator {
14 #[tank(primary_key)]
15 pub id: Uuid,
16 pub callsign: String,
17 #[tank(name = "rank")]
18 pub service_rank: String,
19 #[tank(name = "enlistment_date")]
20 pub enlisted: Date,
21 pub is_certified: bool,
22}
23
24#[derive(Entity)]
25#[tank(schema = "operations")]
26pub struct RadioLog {
27 #[tank(primary_key)]
28 pub id: Uuid,
29 #[tank(references = Operator::id)]
30 pub operator: Uuid,
31 pub message: String,
32 pub unit_callsign: String,
33 #[tank(name = "tx_time")]
34 pub transmission_time: OffsetDateTime,
35 #[tank(name = "rssi")]
36 pub signal_strength: i8,
37}
38static MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
39
40pub async fn operations<E: Executor>(executor: &mut E) -> Result<()> {
41 let _lock = MUTEX.lock().await;
42
43 RadioLog::drop_table(executor, true, false).await?;
45 Operator::drop_table(executor, true, false).await?;
46
47 Operator::create_table(executor, false, true).await?;
48 RadioLog::create_table(executor, false, false).await?;
49
50 let operator = Operator {
52 id: Uuid::new_v4(),
53 callsign: "SteelHammer".into(),
54 service_rank: "Major".into(),
55 enlisted: date!(2015 - 06 - 20),
56 is_certified: true,
57 };
58 Operator::insert_one(executor, &operator).await?;
59
60 let op_id = operator.id;
61 let logs: Vec<RadioLog> = (0..5)
62 .map(|i| RadioLog {
63 id: Uuid::new_v4(),
64 operator: op_id,
65 message: format!("Ping #{i}"),
66 unit_callsign: "Alpha-1".into(),
67 transmission_time: OffsetDateTime::now_utc(),
68 signal_strength: 42,
69 })
70 .collect();
71 RadioLog::insert_many(executor, &logs).await?;
72
73 let found = Operator::find_pk(executor, &operator.primary_key()).await?;
75 if let Some(op) = found {
76 log::debug!("Found operator: {:?}", op.callsign);
77 }
78
79 if let Some(radio_log) =
80 RadioLog::find_one(executor, &expr!(RadioLog::unit_callsign == "Alpha-1")).await?
81 {
82 log::debug!("Found radio log: {:?}", radio_log.id);
83 }
84
85 {
86 let mut stream = pin!(RadioLog::find_many(
87 executor,
88 expr!(RadioLog::signal_strength >= 40),
89 Some(100)
90 ));
91 while let Some(radio_log) = stream.try_next().await? {
92 log::debug!("Found radio log: {:?}", radio_log.id);
93 }
94 }
96
97 let mut operator = operator;
99 operator.callsign = "SteelHammerX".into();
100 operator.save(executor).await?;
101
102 let mut log = RadioLog::find_one(executor, &expr!(RadioLog::message == "Ping #2"))
103 .await?
104 .expect("Missing log");
105 log.message = "Ping #2 ACK".into();
106 log.save(executor).await?;
107
108 RadioLog::delete_one(executor, log.primary_key()).await?;
110
111 let operator_id = operator.id;
112 RadioLog::delete_many(executor, &expr!(RadioLog::operator == #operator_id)).await?;
113
114 operator.delete(executor).await?;
115
116 let mut query =
118 RadioLog::prepare_find(executor, &expr!(RadioLog::signal_strength > ?), None).await?;
119 query.bind(40)?;
120 let _messages: Vec<_> = executor
121 .fetch(query)
122 .map_ok(|row| row.values[0].clone())
123 .try_collect()
124 .await?;
125
126 let writer = executor.driver().sql_writer();
128 let mut sql = String::new();
129 writer.write_delete::<RadioLog>(&mut sql, &expr!(RadioLog::signal_strength < 10));
130 writer.write_insert(&mut sql, [&operator], false);
131 writer.write_insert(
132 &mut sql,
133 [&RadioLog {
134 id: Uuid::new_v4(),
135 operator: operator.id,
136 message: "Status report".into(),
137 unit_callsign: "Alpha-1".into(),
138 transmission_time: OffsetDateTime::now_utc(),
139 signal_strength: 55,
140 }],
141 false,
142 );
143 writer.write_select(
144 &mut sql,
145 RadioLog::columns(),
146 RadioLog::table(),
147 &expr!(true),
148 Some(50),
149 );
150 {
151 let mut stream = pin!(executor.run(sql));
152 while let Some(result) = stream.try_next().await? {
153 match result {
154 QueryResult::Row(row) => log::debug!("Row: {row:?}"),
155 QueryResult::Affected(RowsAffected { rows_affected, .. }) => {
156 log::debug!("Affected rows: {rows_affected:?}")
157 }
158 }
159 }
160 }
161
162 Ok(())
163}
164
165pub async fn advanced_operations<E: Executor>(executor: &mut E) -> Result<()> {
166 let _lock = MUTEX.lock().await;
167
168 RadioLog::drop_table(executor, true, false).await?;
169 Operator::drop_table(executor, true, false).await?;
170
171 Operator::create_table(executor, false, true).await?;
172 RadioLog::create_table(executor, false, false).await?;
173
174 let operators = vec![
175 Operator {
176 id: Uuid::new_v4(),
177 callsign: "SteelHammer".into(),
178 service_rank: "Major".into(),
179 enlisted: date!(2015 - 06 - 20),
180 is_certified: true,
181 },
182 Operator {
183 id: Uuid::new_v4(),
184 callsign: "Viper".into(),
185 service_rank: "Sgt".into(),
186 enlisted: date!(2019 - 11 - 01),
187 is_certified: true,
188 },
189 Operator {
190 id: Uuid::new_v4(),
191 callsign: "Rook".into(),
192 service_rank: "Pvt".into(),
193 enlisted: date!(2023 - 01 - 15),
194 is_certified: false,
195 },
196 ];
197 let radio_logs = vec![
198 RadioLog {
199 id: Uuid::new_v4(),
200 operator: operators[0].id,
201 message: "Radio check, channel 3. How copy?".into(),
202 unit_callsign: "Alpha-1".into(),
203 transmission_time: OffsetDateTime::new_in_offset(
204 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
205 Time::from_hms(19, 45, 21).unwrap(),
206 UtcOffset::from_hms(1, 0, 0).unwrap(),
207 ),
208 signal_strength: -42,
209 },
210 RadioLog {
211 id: Uuid::new_v4(),
212 operator: operators[0].id,
213 message: "Target acquired. Requesting coordinates.".into(),
214 unit_callsign: "Alpha-1".into(),
215 transmission_time: OffsetDateTime::new_in_offset(
216 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
217 Time::from_hms(19, 54, 12).unwrap(),
218 UtcOffset::from_hms(1, 0, 0).unwrap(),
219 ),
220 signal_strength: -55,
221 },
222 RadioLog {
223 id: Uuid::new_v4(),
224 operator: operators[0].id,
225 message: "Heavy armor spotted, grid 4C.".into(),
226 unit_callsign: "Alpha-1".into(),
227 transmission_time: OffsetDateTime::new_in_offset(
228 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
229 Time::from_hms(19, 51, 9).unwrap(),
230 UtcOffset::from_hms(1, 0, 0).unwrap(),
231 ),
232 signal_strength: -52,
233 },
234 RadioLog {
235 id: Uuid::new_v4(),
236 operator: operators[1].id,
237 message: "Perimeter secure. All clear.".into(),
238 unit_callsign: "Bravo-2".into(),
239 transmission_time: OffsetDateTime::new_in_offset(
240 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
241 Time::from_hms(19, 51, 9).unwrap(),
242 UtcOffset::from_hms(1, 0, 0).unwrap(),
243 ),
244 signal_strength: -68,
245 },
246 RadioLog {
247 id: Uuid::new_v4(),
248 operator: operators[2].id,
249 message: "Radio check, grid 1A. Over.".into(),
250 unit_callsign: "Charlie-3".into(),
251 transmission_time: OffsetDateTime::new_in_offset(
252 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
253 Time::from_hms(18, 59, 11).unwrap(),
254 UtcOffset::from_hms(2, 0, 0).unwrap(),
255 ),
256 signal_strength: -41,
257 },
258 RadioLog {
259 id: Uuid::new_v4(),
260 operator: operators[0].id,
261 message: "Affirmative, engaging.".into(),
262 unit_callsign: "Alpha-1".into(),
263 transmission_time: OffsetDateTime::new_in_offset(
264 Date::from_calendar_date(2025, Month::November, 3).unwrap(),
265 Time::from_hms(23, 11, 54).unwrap(),
266 UtcOffset::from_hms(0, 0, 0).unwrap(),
267 ),
268 signal_strength: -54,
269 },
270 ];
271 Operator::insert_many(executor, &operators)
272 .await
273 .expect("Could not insert operators");
274 RadioLog::insert_many(executor, &radio_logs)
275 .await
276 .expect("Could not insert radio logs");
277
278 let messages = join!(Operator JOIN RadioLog ON Operator::id == RadioLog::operator)
279 .select(
280 executor,
281 cols!(
282 RadioLog::signal_strength as strength DESC,
283 Operator::callsign ASC,
284 RadioLog::message,
285 ),
286 &expr!(Operator::is_certified && RadioLog::message != "Radio check%" as LIKE),
288 Some(100),
289 )
290 .map(|row| {
291 row.and_then(|row| {
292 #[derive(Entity)]
293 struct Row {
294 message: String,
295 callsign: String,
296 }
297 Row::from_row(row).and_then(|row| Ok((row.message, row.callsign)))
298 })
299 })
300 .try_collect::<Vec<_>>()
301 .await?;
302 assert!(
303 messages.iter().map(|(a, b)| (a.as_str(), b.as_str())).eq([
304 ("Heavy armor spotted, grid 4C.", "SteelHammer"),
305 ("Affirmative, engaging.", "SteelHammer"),
306 ("Target acquired. Requesting coordinates.", "SteelHammer"),
307 ("Perimeter secure. All clear.", "Viper"),
308 ]
309 .into_iter())
310 );
311 Ok(())
312}