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