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}