Skip to main content

tano_database/
builders.rs

1use sqlx::{QueryBuilder, Sqlite};
2
3use crate::{
4    album::CreateAlbum,
5    artist::{ArtistRole, CreateArtist},
6    bulk_builder::BulkBuilder,
7    local_song::CreateLocalSong,
8    song::CreateSong,
9};
10
11pub struct UpsertArtistsBuilder {
12    qb: QueryBuilder<Sqlite>,
13    has_items: bool,
14}
15
16impl UpsertArtistsBuilder {
17    pub fn new() -> Self {
18        Self {
19            qb: QueryBuilder::new("INSERT INTO artists (provider_id, name) VALUES "),
20            has_items: false,
21        }
22    }
23}
24
25impl Default for UpsertArtistsBuilder {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl BulkBuilder for UpsertArtistsBuilder {
32    type Item<'a> = &'a CreateArtist;
33
34    fn push(&mut self, artist: Self::Item<'_>) {
35        if self.has_items {
36            self.qb.push(", ");
37        } else {
38            self.has_items = true;
39        }
40        self.qb
41            .push("(")
42            .push_bind(artist.provider_id)
43            .push(", ")
44            .push_bind(&artist.name)
45            .push(")");
46    }
47
48    fn build(mut self) -> QueryBuilder<Sqlite> {
49        self.qb.push(" ON CONFLICT (provider_id, name) DO UPDATE SET name = excluded.name RETURNING id, name");
50        self.qb
51    }
52}
53
54pub struct UpsertAlbumsBuilder {
55    qb: QueryBuilder<Sqlite>,
56    has_items: bool,
57}
58
59impl UpsertAlbumsBuilder {
60    pub fn new() -> Self {
61        Self {
62            qb: QueryBuilder::new("INSERT INTO albums (provider_id, title) VALUES "),
63            has_items: false,
64        }
65    }
66}
67
68impl Default for UpsertAlbumsBuilder {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl BulkBuilder for UpsertAlbumsBuilder {
75    type Item<'a> = &'a CreateAlbum;
76
77    fn push(&mut self, album: Self::Item<'_>) {
78        if self.has_items {
79            self.qb.push(", ");
80        } else {
81            self.has_items = true;
82        }
83        self.qb
84            .push("(")
85            .push_bind(album.provider_id)
86            .push(", ")
87            .push_bind(&album.title)
88            .push(")");
89    }
90
91    fn build(mut self) -> QueryBuilder<Sqlite> {
92        self.qb.push(" ON CONFLICT (provider_id, title) DO UPDATE SET title = excluded.title RETURNING id, title");
93        self.qb
94    }
95}
96
97pub struct InsertSongsBuilder {
98    qb: QueryBuilder<Sqlite>,
99    has_items: bool,
100}
101
102impl InsertSongsBuilder {
103    pub fn new() -> Self {
104        Self {
105            qb: QueryBuilder::new(
106                "INSERT INTO songs (provider_id, album_id, title, track_number, duration, year) VALUES ",
107            ),
108            has_items: false,
109        }
110    }
111}
112
113impl Default for InsertSongsBuilder {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl BulkBuilder for InsertSongsBuilder {
120    type Item<'a> = &'a CreateSong;
121
122    fn push(&mut self, song: Self::Item<'_>) {
123        if self.has_items {
124            self.qb.push(", ");
125        } else {
126            self.has_items = true;
127        }
128        self.qb
129            .push("(")
130            .push_bind(song.provider_id)
131            .push(", ")
132            .push_bind(song.album_id)
133            .push(", ")
134            .push_bind(&song.title)
135            .push(", ")
136            .push_bind(song.track_number)
137            .push(", ")
138            .push_bind(song.duration)
139            .push(", ")
140            .push_bind(song.year)
141            .push(")");
142    }
143
144    fn build(mut self) -> QueryBuilder<Sqlite> {
145        self.qb.push(" RETURNING id");
146        self.qb
147    }
148}
149
150pub struct InsertLocalSongsBuilder {
151    qb: QueryBuilder<Sqlite>,
152    has_items: bool,
153}
154
155impl InsertLocalSongsBuilder {
156    pub fn new() -> Self {
157        Self {
158            qb: QueryBuilder::new(
159                "INSERT INTO local_songs (song_id, path, inode, mtime, size, format) VALUES ",
160            ),
161            has_items: false,
162        }
163    }
164}
165
166impl Default for InsertLocalSongsBuilder {
167    fn default() -> Self {
168        Self::new()
169    }
170}
171
172impl BulkBuilder for InsertLocalSongsBuilder {
173    type Item<'a> = (i64, &'a CreateLocalSong);
174
175    fn push(&mut self, (song_id, song): Self::Item<'_>) {
176        if self.has_items {
177            self.qb.push(", ");
178        } else {
179            self.has_items = true;
180        }
181        self.qb
182            .push("(")
183            .push_bind(song_id)
184            .push(", ")
185            .push_bind(&song.path)
186            .push(", ")
187            .push_bind(song.inode)
188            .push(", ")
189            .push_bind(song.mtime)
190            .push(", ")
191            .push_bind(song.size)
192            .push(", ")
193            .push_bind(&song.format)
194            .push(")");
195    }
196
197    fn build(self) -> QueryBuilder<Sqlite> {
198        self.qb
199    }
200}
201
202pub struct UpdateSongsBuilder {
203    qb: QueryBuilder<Sqlite>,
204    has_items: bool,
205}
206
207impl UpdateSongsBuilder {
208    pub fn new() -> Self {
209        Self {
210            qb: QueryBuilder::new(
211                "WITH tmp(id, album_id, title, track_number, duration, year) AS (VALUES ",
212            ),
213            has_items: false,
214        }
215    }
216}
217
218impl Default for UpdateSongsBuilder {
219    fn default() -> Self {
220        Self::new()
221    }
222}
223
224impl BulkBuilder for UpdateSongsBuilder {
225    type Item<'a> = (i64, &'a CreateSong);
226
227    fn push(&mut self, (id, song): Self::Item<'_>) {
228        if self.has_items {
229            self.qb.push(", ");
230        } else {
231            self.has_items = true;
232        }
233        self.qb
234            .push("(")
235            .push_bind(id)
236            .push(", ")
237            .push_bind(song.album_id)
238            .push(", ")
239            .push_bind(&song.title)
240            .push(", ")
241            .push_bind(song.track_number)
242            .push(", ")
243            .push_bind(song.duration)
244            .push(", ")
245            .push_bind(song.year)
246            .push(")");
247    }
248
249    fn build(mut self) -> QueryBuilder<Sqlite> {
250        self.qb
251            .push(") UPDATE songs SET album_id = tmp.album_id, title = tmp.title, track_number = tmp.track_number, duration = tmp.duration, year = tmp.year FROM tmp WHERE songs.id = tmp.id");
252        self.qb
253    }
254}
255
256pub struct UpdateLocalSongsBuilder {
257    qb: QueryBuilder<Sqlite>,
258    has_items: bool,
259}
260
261impl UpdateLocalSongsBuilder {
262    pub fn new() -> Self {
263        Self {
264            qb: QueryBuilder::new("WITH tmp(id, inode, mtime, size, format) AS (VALUES "),
265            has_items: false,
266        }
267    }
268}
269
270impl Default for UpdateLocalSongsBuilder {
271    fn default() -> Self {
272        Self::new()
273    }
274}
275
276impl BulkBuilder for UpdateLocalSongsBuilder {
277    type Item<'a> = (i64, &'a CreateLocalSong);
278
279    fn push(&mut self, (id, song): Self::Item<'_>) {
280        if self.has_items {
281            self.qb.push(", ");
282        } else {
283            self.has_items = true;
284        }
285        self.qb
286            .push("(")
287            .push_bind(id)
288            .push(", ")
289            .push_bind(song.inode)
290            .push(", ")
291            .push_bind(song.mtime)
292            .push(", ")
293            .push_bind(song.size)
294            .push(", ")
295            .push_bind(&song.format)
296            .push(")");
297    }
298
299    fn build(mut self) -> QueryBuilder<Sqlite> {
300        self.qb
301            .push(") UPDATE local_songs SET inode = tmp.inode, mtime = tmp.mtime, size = tmp.size, format = tmp.format FROM tmp WHERE local_songs.song_id = tmp.id");
302        self.qb
303    }
304}
305
306pub struct InsertSongArtistsBuilder {
307    qb: QueryBuilder<Sqlite>,
308    has_items: bool,
309}
310
311impl InsertSongArtistsBuilder {
312    pub fn new() -> Self {
313        Self {
314            qb: QueryBuilder::new(
315                "INSERT OR IGNORE INTO song_artists (song_id, artist_id, role) VALUES ",
316            ),
317            has_items: false,
318        }
319    }
320}
321
322impl Default for InsertSongArtistsBuilder {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328impl BulkBuilder for InsertSongArtistsBuilder {
329    type Item<'a> = (i64, i64, ArtistRole);
330
331    fn push(&mut self, (song_id, artist_id, role): Self::Item<'_>) {
332        if self.has_items {
333            self.qb.push(", ");
334        } else {
335            self.has_items = true;
336        }
337        self.qb
338            .push("(")
339            .push_bind(song_id)
340            .push(", ")
341            .push_bind(artist_id)
342            .push(", ")
343            .push_bind(role as i64)
344            .push(")");
345    }
346
347    fn build(self) -> QueryBuilder<Sqlite> {
348        self.qb
349    }
350}
351
352pub struct UpdateLocalSongsPathBuilder {
353    qb: QueryBuilder<Sqlite>,
354    has_items: bool,
355}
356
357impl UpdateLocalSongsPathBuilder {
358    pub fn new() -> Self {
359        Self {
360            qb: QueryBuilder::new("WITH tmp(id, path) AS (VALUES "),
361            has_items: false,
362        }
363    }
364}
365
366impl Default for UpdateLocalSongsPathBuilder {
367    fn default() -> Self {
368        Self::new()
369    }
370}
371
372impl BulkBuilder for UpdateLocalSongsPathBuilder {
373    type Item<'a> = (i64, &'a str);
374
375    fn push(&mut self, (id, path): Self::Item<'_>) {
376        if self.has_items {
377            self.qb.push(", ");
378        } else {
379            self.has_items = true;
380        }
381        self.qb
382            .push("(")
383            .push_bind(id)
384            .push(", ")
385            .push_bind(path)
386            .push(")");
387    }
388
389    fn build(mut self) -> QueryBuilder<Sqlite> {
390        self.qb.push(
391            ") UPDATE local_songs SET path = tmp.path FROM tmp WHERE local_songs.song_id = tmp.id",
392        );
393        self.qb
394    }
395}
396
397pub struct DeleteSongArtistsBuilder {
398    qb: QueryBuilder<Sqlite>,
399    has_items: bool,
400}
401
402impl DeleteSongArtistsBuilder {
403    pub fn new() -> Self {
404        Self {
405            qb: QueryBuilder::new("DELETE FROM song_artists WHERE song_id IN ("),
406            has_items: false,
407        }
408    }
409}
410
411impl Default for DeleteSongArtistsBuilder {
412    fn default() -> Self {
413        Self::new()
414    }
415}
416
417impl BulkBuilder for DeleteSongArtistsBuilder {
418    type Item<'a> = i64;
419
420    fn push(&mut self, id: Self::Item<'_>) {
421        if self.has_items {
422            self.qb.push(", ");
423        } else {
424            self.has_items = true;
425        }
426        self.qb.push_bind(id);
427    }
428
429    fn build(mut self) -> QueryBuilder<Sqlite> {
430        self.qb.push(")");
431        self.qb
432    }
433}
434
435pub struct DeleteSongsBuilder {
436    qb: QueryBuilder<Sqlite>,
437    has_items: bool,
438}
439
440impl DeleteSongsBuilder {
441    pub fn new() -> Self {
442        Self {
443            qb: QueryBuilder::new("DELETE FROM songs WHERE id IN ("),
444            has_items: false,
445        }
446    }
447}
448
449impl Default for DeleteSongsBuilder {
450    fn default() -> Self {
451        Self::new()
452    }
453}
454
455impl BulkBuilder for DeleteSongsBuilder {
456    type Item<'a> = i64;
457
458    fn push(&mut self, id: Self::Item<'_>) {
459        if self.has_items {
460            self.qb.push(", ");
461        } else {
462            self.has_items = true;
463        }
464        self.qb.push_bind(id);
465    }
466
467    fn build(mut self) -> QueryBuilder<Sqlite> {
468        self.qb.push(")");
469        self.qb
470    }
471}