Skip to main content

convert_invert/internals/database/
model.rs

1use std::io::Write;
2
3use diesel::{
4    AsExpression, Associations, FromSqlRow, Identifiable, Insertable, Queryable, Selectable,
5    deserialize::{self, FromSql},
6    pg::{Pg, PgValue},
7    serialize::{self, IsNull, Output, ToSql},
8};
9use serde::{Deserialize, Serialize};
10
11use crate::internals::{
12    context::context_manager::{
13        DownloadedFile, RejectReason as RuntimeRejectReason, RejectedTrack, RetryRequest,
14    },
15    database::schema::{self, sql_types},
16    search::search_manager::{
17        DownloadableFile as RuntimeDownloadableFile, JudgeSubmission as RuntimeJudgeSubmission,
18        SearchItem as RuntimeSearchItem,
19    },
20};
21
22#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
23#[diesel(table_name = schema::search_items)]
24pub struct SearchItemRow {
25    pub id: i32,
26    pub track_id: String,
27    pub track: String,
28    pub artist: String,
29    pub album: String,
30}
31
32#[derive(Debug, Clone, Insertable)]
33#[diesel(table_name = schema::search_items)]
34pub struct NewSearchItemRow {
35    pub track_id: String,
36    pub track: String,
37    pub artist: String,
38    pub album: String,
39}
40
41impl From<&RuntimeSearchItem> for NewSearchItemRow {
42    fn from(value: &RuntimeSearchItem) -> Self {
43        Self {
44            track_id: value.track_id.clone(),
45            track: value.track.clone(),
46            artist: value.artist.clone(),
47            album: value.album.clone(),
48        }
49    }
50}
51
52impl From<RuntimeSearchItem> for NewSearchItemRow {
53    fn from(value: RuntimeSearchItem) -> Self {
54        Self::from(&value)
55    }
56}
57
58impl From<SearchItemRow> for RuntimeSearchItem {
59    fn from(value: SearchItemRow) -> Self {
60        Self {
61            track_id: value.track_id,
62            track: value.track,
63            artist: value.artist,
64            album: value.album,
65        }
66    }
67}
68
69#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
70#[diesel(table_name = schema::downloadable_files)]
71pub struct DownloadableFileRow {
72    pub id: i32,
73    pub filename: String,
74    pub username: String,
75    pub size: i64,
76}
77
78#[derive(Debug, Clone, Insertable)]
79#[diesel(table_name = schema::downloadable_files)]
80pub struct NewDownloadableFileRow {
81    pub filename: String,
82    pub username: String,
83    pub size: i64,
84}
85
86impl From<&RuntimeDownloadableFile> for NewDownloadableFileRow {
87    fn from(value: &RuntimeDownloadableFile) -> Self {
88        Self {
89            filename: value.filename.clone(),
90            username: value.username.clone(),
91            size: value.size,
92        }
93    }
94}
95
96impl From<RuntimeDownloadableFile> for NewDownloadableFileRow {
97    fn from(value: RuntimeDownloadableFile) -> Self {
98        Self::from(&value)
99    }
100}
101
102impl From<DownloadableFileRow> for RuntimeDownloadableFile {
103    fn from(value: DownloadableFileRow) -> Self {
104        Self {
105            filename: value.filename,
106            username: value.username,
107            size: value.size,
108        }
109    }
110}
111
112#[derive(Debug, Clone, Queryable, Selectable, Identifiable, Associations)]
113#[diesel(table_name = schema::judge_submissions)]
114#[diesel(belongs_to(SearchItemRow, foreign_key = track))]
115#[diesel(belongs_to(DownloadableFileRow, foreign_key = query))]
116pub struct JudgeSubmissionRow {
117    pub id: i32,
118    pub track: i32,
119    pub query: i32,
120    pub score: Option<f32>,
121}
122
123#[derive(Debug, Clone, Insertable)]
124#[diesel(table_name = schema::judge_submissions)]
125pub struct NewJudgeSubmissionRow {
126    pub track: i32,
127    pub query: i32,
128    pub score: Option<f32>,
129}
130
131#[derive(Debug, Clone)]
132pub struct JudgeSubmissionJoined {
133    pub row: JudgeSubmissionRow,
134    pub track: SearchItemRow,
135    pub query: DownloadableFileRow,
136}
137
138impl From<JudgeSubmissionJoined> for RuntimeJudgeSubmission {
139    fn from(value: JudgeSubmissionJoined) -> Self {
140        Self {
141            track: value.track.into(),
142            query: value.query.into(),
143            score: None,
144        }
145    }
146}
147
148#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
149#[diesel(table_name = schema::downloaded_file)]
150pub struct DownloadedFileRow {
151    pub id: i32,
152    pub filename: String,
153    pub track: Option<i32>,
154}
155
156#[derive(Debug, Clone, Insertable)]
157#[diesel(table_name = schema::downloaded_file)]
158pub struct NewDownloadedFileRow {
159    pub filename: String,
160    pub track: Option<i32>,
161}
162
163impl NewDownloadedFileRow {
164    pub fn from_runtime(value: &DownloadedFile, track: Option<i32>) -> Self {
165        Self {
166            filename: value.filename.clone(),
167            track,
168        }
169    }
170}
171
172#[derive(Debug, Clone, Queryable, Selectable, Identifiable, Associations)]
173#[diesel(table_name = schema::retry_request)]
174#[diesel(belongs_to(JudgeSubmissionRow, foreign_key = request))]
175#[diesel(belongs_to(DownloadableFileRow, foreign_key = failed_download_result))]
176pub struct RetryRequestRow {
177    pub id: i32,
178    pub request: i32,
179    pub retry_attempts: i32,
180    pub failed_download_result: i32,
181}
182
183#[derive(Debug, Clone, Insertable)]
184#[diesel(table_name = schema::retry_request)]
185pub struct NewRetryRequestRow {
186    pub request: i32,
187    pub retry_attempts: i32,
188    pub failed_download_result: i32,
189}
190
191#[derive(Debug, Clone)]
192pub struct RetryRequestJoined {
193    pub row: RetryRequestRow,
194    pub request: JudgeSubmissionJoined,
195    pub failed_download_result: DownloadableFileRow,
196}
197
198impl From<RetryRequestJoined> for RetryRequest {
199    fn from(value: RetryRequestJoined) -> Self {
200        Self {
201            request: value.request.into(),
202            retry_attempts: value.row.retry_attempts as u8,
203            failed_download_result: value.failed_download_result.into(),
204        }
205    }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, AsExpression, FromSqlRow)]
209#[diesel(sql_type = sql_types::RejectReason)]
210pub enum RejectReasonRow {
211    AlreadyDownloaded,
212    LowScore,
213    NotMusic,
214    Banned,
215    AbandonedAttemptingSearch,
216}
217
218impl From<&RuntimeRejectReason> for RejectReasonRow {
219    fn from(value: &RuntimeRejectReason) -> Self {
220        match value {
221            RuntimeRejectReason::AlreadyDownloaded => Self::AlreadyDownloaded,
222            RuntimeRejectReason::LowScore(_) => Self::LowScore,
223            RuntimeRejectReason::NotMusic(_) => Self::NotMusic,
224            RuntimeRejectReason::Banned(_) => Self::Banned,
225            RuntimeRejectReason::AbandonedAttemptingSearch => Self::AbandonedAttemptingSearch,
226        }
227    }
228}
229
230impl From<RuntimeRejectReason> for RejectReasonRow {
231    fn from(value: RuntimeRejectReason) -> Self {
232        Self::from(&value)
233    }
234}
235
236impl From<RejectReasonRow> for RuntimeRejectReason {
237    fn from(value: RejectReasonRow) -> Self {
238        match value {
239            RejectReasonRow::AlreadyDownloaded => RuntimeRejectReason::AlreadyDownloaded,
240            // Database enum intentionally drops payload details.
241            RejectReasonRow::LowScore => RuntimeRejectReason::LowScore(0.0),
242            RejectReasonRow::NotMusic => RuntimeRejectReason::NotMusic(String::new()),
243            RejectReasonRow::Banned => RuntimeRejectReason::Banned(String::new()),
244            RejectReasonRow::AbandonedAttemptingSearch => {
245                RuntimeRejectReason::AbandonedAttemptingSearch
246            }
247        }
248    }
249}
250
251impl ToSql<sql_types::RejectReason, Pg> for RejectReasonRow {
252    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
253        let value = match self {
254            RejectReasonRow::AlreadyDownloaded => b"already_downloaded".as_slice(),
255            RejectReasonRow::LowScore => b"low_score".as_slice(),
256            RejectReasonRow::NotMusic => b"not_music".as_slice(),
257            RejectReasonRow::Banned => b"banned".as_slice(),
258            RejectReasonRow::AbandonedAttemptingSearch => b"abandoned_attempting_search".as_slice(),
259        };
260        out.write_all(value)?;
261        Ok(IsNull::No)
262    }
263}
264
265impl FromSql<sql_types::RejectReason, Pg> for RejectReasonRow {
266    fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
267        match bytes.as_bytes() {
268            b"already_downloaded" => Ok(Self::AlreadyDownloaded),
269            b"low_score" => Ok(Self::LowScore),
270            b"not_music" => Ok(Self::NotMusic),
271            b"banned" => Ok(Self::Banned),
272            b"abandoned_attempting_search" => Ok(Self::AbandonedAttemptingSearch),
273            unknown => Err(format!(
274                "Unrecognized reject_reason value: {}",
275                String::from_utf8_lossy(unknown)
276            )
277            .into()),
278        }
279    }
280}
281
282#[derive(Debug, Clone, Queryable, Selectable, Identifiable, Associations)]
283#[diesel(table_name = schema::rejected_track)]
284#[diesel(belongs_to(JudgeSubmissionRow, foreign_key = track))]
285pub struct RejectedTrackRow {
286    pub id: i32,
287    pub track: i32,
288    pub reason: RejectReasonRow,
289}
290
291#[derive(Debug, Clone, Insertable)]
292#[diesel(table_name = schema::rejected_track)]
293pub struct NewRejectedTrackRow {
294    pub track: i32,
295    pub reason: RejectReasonRow,
296    pub value: Option<String>,
297}
298
299impl NewRejectedTrackRow {
300    pub fn from_runtime(track_id: i32, value: &RejectedTrack) -> Self {
301        let (_, reason) = value.parts();
302        Self {
303            track: track_id,
304            reason: reason.into(),
305            value: match reason {
306                RuntimeRejectReason::AlreadyDownloaded
307                | RuntimeRejectReason::AbandonedAttemptingSearch => None,
308                RuntimeRejectReason::LowScore(score) => Some(format!("{score}")),
309                RuntimeRejectReason::NotMusic(filename) => Some(filename.to_owned()),
310                RuntimeRejectReason::Banned(track_id) => Some(track_id.to_owned()),
311            },
312        }
313    }
314}
315
316#[derive(Debug, Clone)]
317pub struct RejectedTrackJoined {
318    pub row: RejectedTrackRow,
319    pub track: JudgeSubmissionJoined,
320}
321
322impl From<RejectedTrackJoined> for RejectedTrack {
323    fn from(value: RejectedTrackJoined) -> Self {
324        RejectedTrack::new(value.track.into(), value.row.reason.into())
325    }
326}