1use std::{pin::pin, sync::LazyLock};
2use tank::{
3 Driver, DynQuery, 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::parse_str("21c90df5-00db-4062-9f5a-bcfa2e759e78").unwrap(),
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 if let Some(radio_log) = RadioLog::find_one(
76 executor,
77 expr!(RadioLog::unit_callsign == "Alpha-%" as LIKE),
78 )
79 .await?
80 {
81 log::debug!("Found radio log: {:?}", radio_log.id);
82 }
83
84 {
85 let mut stream = pin!(RadioLog::find_many(
86 executor,
87 expr!(RadioLog::signal_strength >= 40),
88 Some(100)
89 ));
90 while let Some(radio_log) = stream.try_next().await? {
91 log::debug!("Found radio log: {:?}", radio_log.id);
92 }
93 }
95
96 let mut operator = operator;
98 operator.callsign = "SteelHammerX".into();
99 operator.save(executor).await?;
100
101 let mut log = RadioLog::find_one(executor, expr!(RadioLog::message == "Ping #2"))
102 .await?
103 .expect("Missing log");
104 log.message = "Ping #2 ACK".into();
105 log.save(executor).await?;
106
107 RadioLog::delete_many(executor, log.primary_key_expr()).await?;
109
110 let operator_id = operator.id;
111 RadioLog::delete_many(executor, expr!(RadioLog::operator == #operator_id)).await?;
112
113 operator.delete(executor).await?;
114
115 let mut query =
117 RadioLog::prepare_find(executor, expr!(RadioLog::signal_strength > ?), None).await?;
118 query.bind(40)?;
119 let _messages: Vec<_> = executor
120 .fetch(query)
121 .map_ok(|row| row.values[0].clone())
122 .try_collect()
123 .await?;
124
125 #[cfg(not(feature = "disable-multiple-statements"))]
127 {
128 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_expr(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
165 Ok(())
166}
167
168pub async fn advanced_operations<E: Executor>(executor: &mut E) -> Result<()> {
169 let _lock = MUTEX.lock().await;
170
171 RadioLog::drop_table(executor, true, false).await?;
172 Operator::drop_table(executor, true, false).await?;
173
174 Operator::create_table(executor, false, true).await?;
175 RadioLog::create_table(executor, false, false).await?;
176
177 let operators = vec![
178 Operator {
179 id: Uuid::new_v4(),
180 callsign: "SteelHammer".into(),
181 service_rank: "Major".into(),
182 enlisted: date!(2015 - 06 - 20),
183 is_certified: true,
184 },
185 Operator {
186 id: Uuid::new_v4(),
187 callsign: "Viper".into(),
188 service_rank: "Sgt".into(),
189 enlisted: date!(2019 - 11 - 01),
190 is_certified: true,
191 },
192 Operator {
193 id: Uuid::new_v4(),
194 callsign: "Rook".into(),
195 service_rank: "Pvt".into(),
196 enlisted: date!(2023 - 01 - 15),
197 is_certified: false,
198 },
199 ];
200 let radio_logs = vec![
201 RadioLog {
202 id: Uuid::new_v4(),
203 operator: operators[0].id,
204 message: "Radio check, channel 3. How copy?".into(),
205 unit_callsign: "Alpha-1".into(),
206 transmission_time: OffsetDateTime::new_in_offset(
207 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
208 Time::from_hms(19, 45, 21).unwrap(),
209 UtcOffset::from_hms(1, 0, 0).unwrap(),
210 ),
211 signal_strength: -42,
212 },
213 RadioLog {
214 id: Uuid::new_v4(),
215 operator: operators[0].id,
216 message: "Target acquired. Requesting coordinates.".into(),
217 unit_callsign: "Alpha-1".into(),
218 transmission_time: OffsetDateTime::new_in_offset(
219 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
220 Time::from_hms(19, 54, 12).unwrap(),
221 UtcOffset::from_hms(1, 0, 0).unwrap(),
222 ),
223 signal_strength: -55,
224 },
225 RadioLog {
226 id: Uuid::new_v4(),
227 operator: operators[0].id,
228 message: "Heavy armor spotted, grid 4C.".into(),
229 unit_callsign: "Alpha-1".into(),
230 transmission_time: OffsetDateTime::new_in_offset(
231 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
232 Time::from_hms(19, 51, 9).unwrap(),
233 UtcOffset::from_hms(1, 0, 0).unwrap(),
234 ),
235 signal_strength: -52,
236 },
237 RadioLog {
238 id: Uuid::new_v4(),
239 operator: operators[1].id,
240 message: "Perimeter secure. All clear.".into(),
241 unit_callsign: "Bravo-2".into(),
242 transmission_time: OffsetDateTime::new_in_offset(
243 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
244 Time::from_hms(19, 51, 9).unwrap(),
245 UtcOffset::from_hms(1, 0, 0).unwrap(),
246 ),
247 signal_strength: -68,
248 },
249 RadioLog {
250 id: Uuid::new_v4(),
251 operator: operators[2].id,
252 message: "Radio check, grid 1A. Over.".into(),
253 unit_callsign: "Charlie-3".into(),
254 transmission_time: OffsetDateTime::new_in_offset(
255 Date::from_calendar_date(2025, Month::November, 4).unwrap(),
256 Time::from_hms(18, 59, 11).unwrap(),
257 UtcOffset::from_hms(2, 0, 0).unwrap(),
258 ),
259 signal_strength: -41,
260 },
261 RadioLog {
262 id: Uuid::new_v4(),
263 operator: operators[0].id,
264 message: "Affirmative, engaging.".into(),
265 unit_callsign: "Alpha-1".into(),
266 transmission_time: OffsetDateTime::new_in_offset(
267 Date::from_calendar_date(2025, Month::November, 3).unwrap(),
268 Time::from_hms(23, 11, 54).unwrap(),
269 UtcOffset::from_hms(0, 0, 0).unwrap(),
270 ),
271 signal_strength: -54,
272 },
273 ];
274 Operator::insert_many(executor, &operators)
275 .await
276 .expect("Could not insert operators");
277 RadioLog::insert_many(executor, &radio_logs)
278 .await
279 .expect("Could not insert radio logs");
280
281 #[cfg(not(feature = "disable-joins"))]
282 {
283 let messages = executor
284 .fetch(
285 QueryBuilder::new()
286 .select(cols!(
287 RadioLog::signal_strength as strength,
288 Operator::callsign,
289 RadioLog::message,
290 ))
291 .from(join!(Operator JOIN RadioLog ON Operator::id == RadioLog::operator))
292 .where_expr(expr!(
293 Operator::is_certified && RadioLog::message != "Radio check%" as LIKE
295 ))
296 .order_by(cols!(RadioLog::signal_strength DESC, Operator::callsign ASC))
297 .limit(Some(100))
298 .build(&executor.driver()),
299 )
300 .map(|row| {
301 row.and_then(|row| {
302 #[derive(Entity)]
303 struct Row {
304 message: String,
305 callsign: String,
306 }
307 Row::from_row(row).and_then(|row| Ok((row.message, row.callsign)))
308 })
309 })
310 .try_collect::<Vec<_>>()
311 .await?;
312 assert!(
313 messages.iter().map(|(a, b)| (a.as_str(), b.as_str())).eq([
314 ("Heavy armor spotted, grid 4C.", "SteelHammer"),
315 ("Affirmative, engaging.", "SteelHammer"),
316 ("Target acquired. Requesting coordinates.", "SteelHammer"),
317 ("Perimeter secure. All clear.", "Viper"),
318 ]
319 .into_iter())
320 );
321 }
322 Ok(())
323}