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) =
76 RadioLog::find_one(executor, expr!(RadioLog::unit_callsign == "Alpha-1")).await?
77 {
78 log::debug!("Found radio log: {:?}", radio_log.id);
79 }
80
81 {
82 let mut stream = pin!(RadioLog::find_many(
83 executor,
84 expr!(RadioLog::signal_strength >= 40),
85 Some(100)
86 ));
87 while let Some(radio_log) = stream.try_next().await? {
88 log::debug!("Found radio log: {:?}", radio_log.id);
89 }
90 }
92
93 let mut operator = operator;
95 operator.callsign = "SteelHammerX".into();
96 operator.save(executor).await?;
97
98 let mut log = RadioLog::find_one(executor, expr!(RadioLog::message == "Ping #2"))
99 .await?
100 .expect("Missing log");
101 log.message = "Ping #2 ACK".into();
102 log.save(executor).await?;
103
104 RadioLog::delete_many(executor, log.primary_key_expr()).await?;
106
107 let operator_id = operator.id;
108 RadioLog::delete_many(executor, expr!(RadioLog::operator == #operator_id)).await?;
109
110 operator.delete(executor).await?;
111
112 let mut query =
114 RadioLog::prepare_find(executor, expr!(RadioLog::signal_strength > ?), None).await?;
115 query.bind(40)?;
116 let _messages: Vec<_> = executor
117 .fetch(query)
118 .map_ok(|row| row.values[0].clone())
119 .try_collect()
120 .await?;
121
122 #[cfg(not(feature = "disable-multiple-statements"))]
124 {
125 let writer = executor.driver().sql_writer();
126 let mut query = DynQuery::default();
127 writer.write_delete::<RadioLog>(&mut query, expr!(RadioLog::signal_strength < 10));
128 writer.write_insert(&mut query, [&operator], false);
129 writer.write_insert(
130 &mut query,
131 [&RadioLog {
132 id: Uuid::new_v4(),
133 operator: operator.id,
134 message: "Status report".into(),
135 unit_callsign: "Alpha-1".into(),
136 transmission_time: OffsetDateTime::now_utc(),
137 signal_strength: 55,
138 }],
139 false,
140 );
141 writer.write_select(
142 &mut query,
143 &QueryBuilder::new()
144 .select(RadioLog::columns())
145 .from(RadioLog::table())
146 .where_expr(true)
147 .limit(Some(50)),
148 );
149 {
150 let mut stream = pin!(executor.run(query));
151 while let Some(result) = stream.try_next().await? {
152 match result {
153 QueryResult::Row(row) => log::debug!("Row: {row:?}"),
154 QueryResult::Affected(RowsAffected { rows_affected, .. }) => {
155 log::debug!("Affected rows: {rows_affected:?}")
156 }
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 #[cfg(not(feature = "disable-joins"))]
279 {
280 let messages = executor
281 .fetch(
282 QueryBuilder::new()
283 .select(cols!(
284 RadioLog::signal_strength as strength,
285 Operator::callsign,
286 RadioLog::message,
287 ))
288 .from(join!(Operator JOIN RadioLog ON Operator::id == RadioLog::operator))
289 .where_expr(expr!(
290 Operator::is_certified && RadioLog::message != "Radio check%" as LIKE
292 ))
293 .order_by(cols!(RadioLog::signal_strength DESC, Operator::callsign ASC))
294 .limit(Some(100))
295 .build(&executor.driver()),
296 )
297 .map(|row| {
298 row.and_then(|row| {
299 #[derive(Entity)]
300 struct Row {
301 message: String,
302 callsign: String,
303 }
304 Row::from_row(row).and_then(|row| Ok((row.message, row.callsign)))
305 })
306 })
307 .try_collect::<Vec<_>>()
308 .await?;
309 assert!(
310 messages.iter().map(|(a, b)| (a.as_str(), b.as_str())).eq([
311 ("Heavy armor spotted, grid 4C.", "SteelHammer"),
312 ("Affirmative, engaging.", "SteelHammer"),
313 ("Target acquired. Requesting coordinates.", "SteelHammer"),
314 ("Perimeter secure. All clear.", "Viper"),
315 ]
316 .into_iter())
317 );
318 }
319 Ok(())
320}