don't pass all ids as params

instead, only pass ids to delete and chunk those by the param limit
This commit is contained in:
austinried 2023-05-06 10:51:18 +09:00
parent 0fe52494d0
commit 7f83204b24
7 changed files with 152 additions and 58 deletions

View File

@ -17,6 +17,10 @@ import 'converters.dart';
part 'database.g.dart'; part 'database.g.dart';
// don't exceed SQLITE_MAX_VARIABLE_NUMBER (32766 for version >= 3.32.0)
// https://www.sqlite.org/limits.html
const kSqliteMaxVariableNumber = 32766;
@DriftDatabase(include: {'tables.drift'}) @DriftDatabase(include: {'tables.drift'})
class SubtracksDatabase extends _$SubtracksDatabase { class SubtracksDatabase extends _$SubtracksDatabase {
SubtracksDatabase() : super(_openConnection()); SubtracksDatabase() : super(_openConnection());
@ -169,12 +173,27 @@ class SubtracksDatabase extends _$SubtracksDatabase {
}); });
} }
Future<void> deleteArtistsNotIn(int sourceId, Iterable<String> ids) async { Future<void> deleteArtistsNotIn(int sourceId, Set<String> ids) {
await (delete(artists) return transaction(() async {
..where( final allIds = (await (selectOnly(artists)
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(ids), ..addColumns([artists.id])
)) ..where(artists.sourceId.equals(sourceId)))
.go(); .map((row) => row.read(artists.id))
.get())
.whereNotNull()
.toSet();
final downloadIds = (await artistIdsWithDownloadStatus(sourceId).get())
.whereNotNull()
.toSet();
final diff = allIds.difference(downloadIds).difference(ids);
for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
await (delete(artists)
..where(
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice)))
.go();
}
});
} }
Future<void> saveAlbums(Iterable<AlbumsCompanion> albums) async { Future<void> saveAlbums(Iterable<AlbumsCompanion> albums) async {
@ -183,15 +202,27 @@ class SubtracksDatabase extends _$SubtracksDatabase {
}); });
} }
Future<void> deleteAlbumsNotIn(int sourceId, Iterable<String> ids) async { Future<void> deleteAlbumsNotIn(int sourceId, Set<String> ids) {
final alsoKeep = (await albumIdsWithDownloaded(sourceId).get()).toSet(); return transaction(() async {
final allIds = (await (selectOnly(albums)
..addColumns([albums.id])
..where(albums.sourceId.equals(sourceId)))
.map((row) => row.read(albums.id))
.get())
.whereNotNull()
.toSet();
final downloadIds = (await albumIdsWithDownloadStatus(sourceId).get())
.whereNotNull()
.toSet();
ids = ids.toList()..addAll(alsoKeep); final diff = allIds.difference(downloadIds).difference(ids);
await (delete(albums) for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
..where( await (delete(albums)
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(ids), ..where(
)) (tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice)))
.go(); .go();
}
});
} }
Future<void> savePlaylists( Future<void> savePlaylists(
@ -215,18 +246,31 @@ class SubtracksDatabase extends _$SubtracksDatabase {
}); });
} }
Future<void> deletePlaylistsNotIn(int sourceId, Iterable<String> ids) async { Future<void> deletePlaylistsNotIn(int sourceId, Set<String> ids) {
await (delete(playlists) return transaction(() async {
..where( final allIds = (await (selectOnly(playlists)
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(ids), ..addColumns([playlists.id])
)) ..where(playlists.sourceId.equals(sourceId)))
.go(); .map((row) => row.read(playlists.id))
await (delete(playlistSongs) .get())
..where( .whereNotNull()
(tbl) => .toSet();
tbl.sourceId.equals(sourceId) & tbl.playlistId.isNotIn(ids), final downloadIds = (await playlistIdsWithDownloadStatus(sourceId).get())
)) .whereNotNull()
.go(); .toSet();
final diff = allIds.difference(downloadIds).difference(ids);
for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
await (delete(playlists)
..where(
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice)))
.go();
await (delete(playlistSongs)
..where((tbl) =>
tbl.sourceId.equals(sourceId) & tbl.playlistId.isIn(slice)))
.go();
}
});
} }
Future<void> savePlaylistSongs( Future<void> savePlaylistSongs(
@ -250,29 +294,33 @@ class SubtracksDatabase extends _$SubtracksDatabase {
}); });
} }
Future<void> deleteSongsNotIn(int sourceId, Iterable<String> ids) async { Future<void> deleteSongsNotIn(int sourceId, Set<String> ids) {
await (delete(songs) return transaction(() async {
..where( final allIds = (await (selectOnly(songs)
(tbl) => ..addColumns([songs.id])
tbl.sourceId.equals(sourceId) & ..where(
tbl.id.isNotIn(ids) & songs.sourceId.equals(sourceId) &
tbl.downloadFilePath.isNull() & songs.downloadFilePath.isNull() &
tbl.downloadTaskId.isNull(), songs.downloadTaskId.isNull(),
)) ))
.go(); .map((row) => row.read(songs.id))
final remainingIds = (await (selectOnly(songs) .get())
..addColumns([songs.id]) .whereNotNull()
..where(songs.sourceId.equals(sourceId))) .toSet();
.map((row) => row.read(songs.id))
.get()) final diff = allIds.difference(ids);
.whereNotNull(); for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
await (delete(playlistSongs) await (delete(songs)
..where( ..where(
(tbl) => (tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice)))
tbl.sourceId.equals(sourceId) & .go();
tbl.songId.isNotIn(remainingIds), await (delete(playlistSongs)
)) ..where(
.go(); (tbl) => tbl.sourceId.equals(sourceId) & tbl.songId.isIn(slice),
))
.go();
}
});
} }
Selectable<LastBottomNavStateData> getLastBottomNavState() { Selectable<LastBottomNavStateData> getLastBottomNavState() {

View File

@ -4596,7 +4596,7 @@ abstract class _$SubtracksDatabase extends GeneratedDatabase {
)); ));
} }
Selectable<String> albumIdsWithDownloaded(int sourceId) { Selectable<String> albumIdsWithDownloadStatus(int sourceId) {
return customSelect( return customSelect(
'SELECT albums.id FROM albums JOIN songs ON songs.source_id = albums.source_id AND songs.album_id = albums.id WHERE albums.source_id = ?1 AND(songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL)GROUP BY albums.id', 'SELECT albums.id FROM albums JOIN songs ON songs.source_id = albums.source_id AND songs.album_id = albums.id WHERE albums.source_id = ?1 AND(songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL)GROUP BY albums.id',
variables: [ variables: [
@ -4608,6 +4608,32 @@ abstract class _$SubtracksDatabase extends GeneratedDatabase {
}).map((QueryRow row) => row.read<String>('id')); }).map((QueryRow row) => row.read<String>('id'));
} }
Selectable<String> artistIdsWithDownloadStatus(int sourceId) {
return customSelect(
'SELECT artists.id FROM artists LEFT JOIN albums ON artists.source_id = albums.source_id AND artists.id = albums.artist_id LEFT JOIN songs ON albums.source_id = songs.source_id AND albums.id = songs.album_id WHERE artists.source_id = ?1 AND(songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL)GROUP BY artists.id',
variables: [
Variable<int>(sourceId)
],
readsFrom: {
artists,
albums,
songs,
}).map((QueryRow row) => row.read<String>('id'));
}
Selectable<String> playlistIdsWithDownloadStatus(int sourceId) {
return customSelect(
'SELECT playlists.id FROM playlists LEFT JOIN playlist_songs ON playlist_songs.source_id = playlists.source_id AND playlist_songs.playlist_id = playlists.id LEFT JOIN songs ON playlist_songs.source_id = songs.source_id AND playlist_songs.song_id = songs.id WHERE playlists.source_id = ?1 AND(songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL)GROUP BY playlists.id',
variables: [
Variable<int>(sourceId)
],
readsFrom: {
playlists,
playlistSongs,
songs,
}).map((QueryRow row) => row.read<String>('id'));
}
Selectable<int> searchArtists(String query, int limit, int offset) { Selectable<int> searchArtists(String query, int limit, int offset) {
return customSelect( return customSelect(
'SELECT "rowid" FROM artists_fts WHERE artists_fts MATCH ?1 ORDER BY rank LIMIT ?2 OFFSET ?3', 'SELECT "rowid" FROM artists_fts WHERE artists_fts MATCH ?1 ORDER BY rank LIMIT ?2 OFFSET ?3',

View File

@ -244,7 +244,7 @@ allSubsonicSources WITH SubsonicSettings:
FROM sources FROM sources
JOIN subsonic_sources ON subsonic_sources.source_id = sources.id; JOIN subsonic_sources ON subsonic_sources.source_id = sources.id;
albumIdsWithDownloaded: albumIdsWithDownloadStatus:
SELECT albums.id SELECT albums.id
FROM albums FROM albums
JOIN songs on songs.source_id = albums.source_id AND songs.album_id = albums.id JOIN songs on songs.source_id = albums.source_id AND songs.album_id = albums.id
@ -253,6 +253,26 @@ albumIdsWithDownloaded:
AND (songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL) AND (songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL)
GROUP BY albums.id; GROUP BY albums.id;
artistIdsWithDownloadStatus:
SELECT artists.id
FROM artists
LEFT JOIN albums ON artists.source_id = albums.source_id AND artists.id = albums.artist_id
LEFT JOIN songs ON albums.source_id = songs.source_id AND albums.id = songs.album_id
WHERE
artists.source_id = :source_id
AND (songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL)
GROUP BY artists.id;
playlistIdsWithDownloadStatus:
SELECT playlists.id
FROM playlists
LEFT JOIN playlist_songs ON playlist_songs.source_id = playlists.source_id AND playlist_songs.playlist_id = playlists.id
LEFT JOIN songs ON playlist_songs.source_id = songs.source_id AND playlist_songs.song_id = songs.id
WHERE
playlists.source_id = :source_id
AND (songs.download_file_path IS NOT NULL OR songs.download_task_id IS NOT NULL)
GROUP BY playlists.id;
searchArtists: searchArtists:
SELECT rowid SELECT rowid
FROM artists_fts FROM artists_fts

View File

@ -214,7 +214,7 @@ class DownloadService extends _$DownloadService {
Future<void> deleteAll(int sourceId) async { Future<void> deleteAll(int sourceId) async {
final db = ref.read(databaseProvider); final db = ref.read(databaseProvider);
final albumIds = await db.albumIdsWithDownloaded(sourceId).get(); final albumIds = await db.albumIdsWithDownloadStatus(sourceId).get();
for (var id in albumIds) { for (var id in albumIds) {
await deleteAlbum(await (db.albumById(sourceId, id)).getSingle()); await deleteAlbum(await (db.albumById(sourceId, id)).getSingle());
} }

View File

@ -6,7 +6,7 @@ part of 'download_service.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$downloadServiceHash() => r'92e963b5c070f4d1edb0cd81899b16393c2b9a70'; String _$downloadServiceHash() => r'c72c49f980e307f3013467e76b6564d14a34a736';
/// See also [DownloadService]. /// See also [DownloadService].
@ProviderFor(DownloadService) @ProviderFor(DownloadService)

View File

@ -31,7 +31,7 @@ class SyncService extends _$SyncService {
final source = ref.read(musicSourceProvider); final source = ref.read(musicSourceProvider);
final db = ref.read(databaseProvider); final db = ref.read(databaseProvider);
final ids = <String>[]; final ids = <String>{};
await for (var artists in source.allArtists()) { await for (var artists in source.allArtists()) {
ids.addAll(artists.map((e) => e.id.value)); ids.addAll(artists.map((e) => e.id.value));
await db.saveArtists(artists); await db.saveArtists(artists);
@ -44,7 +44,7 @@ class SyncService extends _$SyncService {
final source = ref.read(musicSourceProvider); final source = ref.read(musicSourceProvider);
final db = ref.read(databaseProvider); final db = ref.read(databaseProvider);
final ids = <String>[]; final ids = <String>{};
await for (var albums in source.allAlbums()) { await for (var albums in source.allAlbums()) {
ids.addAll(albums.map((e) => e.id.value)); ids.addAll(albums.map((e) => e.id.value));
await db.saveAlbums(albums); await db.saveAlbums(albums);
@ -57,7 +57,7 @@ class SyncService extends _$SyncService {
final source = ref.read(musicSourceProvider); final source = ref.read(musicSourceProvider);
final db = ref.read(databaseProvider); final db = ref.read(databaseProvider);
final ids = <String>[]; final ids = <String>{};
await for (var playlists in source.allPlaylists()) { await for (var playlists in source.allPlaylists()) {
ids.addAll(playlists.map((e) => e.playist.id.value)); ids.addAll(playlists.map((e) => e.playist.id.value));
await db.savePlaylists(playlists); await db.savePlaylists(playlists);
@ -70,7 +70,7 @@ class SyncService extends _$SyncService {
final source = ref.read(musicSourceProvider); final source = ref.read(musicSourceProvider);
final db = ref.read(databaseProvider); final db = ref.read(databaseProvider);
final ids = <String>[]; final ids = <String>{};
await for (var songs in source.allSongs()) { await for (var songs in source.allSongs()) {
ids.addAll(songs.map((e) => e.id.value)); ids.addAll(songs.map((e) => e.id.value));
await db.saveSongs(songs); await db.saveSongs(songs);

View File

@ -6,7 +6,7 @@ part of 'sync_service.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$syncServiceHash() => r'2b8da374c3143bc56f17115440d57bc70468a17e'; String _$syncServiceHash() => r'58ebee4e6f055b64ee6789ae43d63c0e15c679e0';
/// See also [SyncService]. /// See also [SyncService].
@ProviderFor(SyncService) @ProviderFor(SyncService)