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