From ab01f2e213152076ebd84c376fd38cc67df3693f Mon Sep 17 00:00:00 2001 From: mitteneer Date: Wed, 19 Feb 2025 21:19:43 -0500 Subject: [PATCH] Add associative tables You can't run from it --- src/app/database/Schema.zig | 265 ++++++++++++------ ...025-02-19_18-03-51_create_albumartists.zig | 20 ++ ...025-02-19_18-04-22_create_songsartists.zig | 20 ++ .../2025-02-19_18-46-48_create_albumsongs.zig | 20 ++ src/app/jobs/process_scrobbles.zig | 50 +++- 5 files changed, 287 insertions(+), 88 deletions(-) create mode 100644 src/app/database/migrations/2025-02-19_18-03-51_create_albumartists.zig create mode 100644 src/app/database/migrations/2025-02-19_18-04-22_create_songsartists.zig create mode 100644 src/app/database/migrations/2025-02-19_18-46-48_create_albumsongs.zig diff --git a/src/app/database/Schema.zig b/src/app/database/Schema.zig index 2b31313..e4ba97a 100644 --- a/src/app/database/Schema.zig +++ b/src/app/database/Schema.zig @@ -1,95 +1,200 @@ const jetquery = @import("jetzig").jetquery; -pub const Album = jetquery.Model(@This(), "albums", struct { - id: i32, - name: []const u8, - length: ?f32, - created_at: jetquery.DateTime, - updated_at: jetquery.DateTime, -}, .{ .relations = .{ - .scrobbles = jetquery.hasMany(.Scrobble, .{}), - .ratings = jetquery.hasMany(.Rating, .{}), - .aliases = jetquery.hasMany(.Alias, .{}), - .songs = jetquery.hasMany(.Song, .{}), - .artists = jetquery.hasMany(.Artist, .{}), -} }); +pub const Album = jetquery.Model( + @This(), + "albums", + struct { + id: i32, + name: []const u8, + length: ?f32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .masteralbum = jetquery.belongsTo(.Masteralbum, .{}), + .scrobbles = jetquery.hasMany(.Scrobble, .{}), + .ratings = jetquery.hasMany(.Rating, .{}), + .aliases = jetquery.hasMany(.Alias, .{}), + .albumsongs = jetquery.hasMany(.Albumsong, .{}), + .albumartists = jetquery.hasMany(.Albumartist, .{}), + }, + }, +); -pub const Alias = jetquery.Model(@This(), "aliases", struct { - id: i32, - reference_id: i32, - alias: []const u8, - created_at: jetquery.DateTime, - updated_at: jetquery.DateTime, -}, .{}); +pub const Alias = jetquery.Model( + @This(), + "aliases", + struct { + id: i32, + reference_id: i32, + alias: []const u8, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); -pub const Artist = jetquery.Model(@This(), "artists", struct { - id: i32, - name: []const u8, - descriptive_string: []const u8, - created_at: jetquery.DateTime, - updated_at: jetquery.DateTime, -}, .{ .relations = .{ - .scrobbles = jetquery.hasMany(.Scrobble, .{}), - .aliases = jetquery.hasMany(.Alias, .{}), - .songs = jetquery.hasMany(.Song, .{}), - .albums = jetquery.hasMany(.Album, .{}), -} }); +pub const Artist = jetquery.Model( + @This(), + "artists", + struct { + id: i32, + name: []const u8, + descriptive_string: []const u8, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .scrobbles = jetquery.hasMany(.Scrobble, .{}), + .aliases = jetquery.hasMany(.Alias, .{}), + .artistsongs = jetquery.hasMany(.Songartist, .{}), + .mastersongs = jetquery.hasMany(.Mastersong, .{}), + .artistalbums = jetquery.hasMany(.Albumartist, .{}), + .masteralbums = jetquery.hasMany(.Masteralbum, .{}), + }, + }, +); -pub const Masteralbum = jetquery.Model(@This(), "masteralbums", struct { - id: i32, - name: []const u8, - created_at: jetquery.DateTime, - updated_at: jetquery.DateTime, -}, .{ .relations = .{ - .albums = jetquery.hasMany(.Album, .{}), -} }); +pub const Masteralbum = jetquery.Model( + @This(), + "masteralbums", + struct { + id: i32, + name: []const u8, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .albums = jetquery.hasMany(.Album, .{}), + }, + }, +); -pub const Mastersong = jetquery.Model(@This(), "mastersongs", struct { - id: i32, - name: []const u8, - created_at: jetquery.DateTime, - updated_at: jetquery.DateTime, -}, .{ .relations = .{ - .songs = jetquery.hasMany(.Song, .{}), -} }); +pub const Mastersong = jetquery.Model( + @This(), + "mastersongs", + struct { + id: i32, + name: []const u8, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .songs = jetquery.hasMany(.Song, .{}), + }, + }, +); -pub const Rating = jetquery.Model(@This(), "ratings", struct { - id: i32, - reference_id: i32, - score: f32, - date: jetquery.DateTime, - created_at: jetquery.DateTime, - updated_at: jetquery.DateTime, -}, .{ .relations = .{ - .song = jetquery.belongsTo(.Song, .{}), - .album = jetquery.belongsTo(.Album, .{}), - .artist = jetquery.belongsTo(.Artist, .{}), -} }); +pub const Rating = jetquery.Model( + @This(), + "ratings", + struct { + id: i32, + reference_id: i32, + score: f32, + date: jetquery.DateTime, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .song = jetquery.belongsTo(.Song, .{}), + .album = jetquery.belongsTo(.Album, .{}), + .artist = jetquery.belongsTo(.Artist, .{}), + }, + }, +); -pub const Scrobble = jetquery.Model(@This(), "scrobbles", struct { +pub const Scrobble = jetquery.Model( + @This(), + "scrobbles", + struct { + id: i32, + song_id: i32, + album_id: ?i32, + date: jetquery.DateTime, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .song = jetquery.belongsTo(.Song, .{}), + .album = jetquery.belongsTo(.Album, .{}), + .artists = jetquery.hasMany(.Artist, .{}), + }, + }, +); + +pub const Song = jetquery.Model( + @This(), + "songs", + struct { + id: i32, + name: []const u8, + length: ?f32, + hidden: bool, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .mastersong = jetquery.belongsTo(.Mastersong, .{}), + .scrobbles = jetquery.hasMany(.Scrobble, .{}), + .ratings = jetquery.hasMany(.Rating, .{}), + .aliases = jetquery.hasMany(.Alias, .{}), + .songartists = jetquery.hasMany(.Songartist, .{}), + .albumsongs = jetquery.hasMany(.Albumsong, .{}), + }, + }, +); + +pub const Albumartist = jetquery.Model( + @This(), + "Albumartists", + struct { + id: i32, + album_id: i32, + artist_id: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .album = jetquery.belongsTo(.Album, .{}), + .artist = jetquery.belongsTo(.Artist, .{}), + }, + }, +); + +pub const Songartist = jetquery.Model( + @This(), + "Songartists", + struct { + id: i32, + song_id: i32, + artist_id: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .song = jetquery.belongsTo(.Song, .{}), + .artist = jetquery.belongsTo(.Artist, .{}), + }, + }, +); + +pub const Albumsong = jetquery.Model(@This(), "Albumsongs", struct { id: i32, + album_id: i32, song_id: i32, - album_id: ?i32, - date: jetquery.DateTime, created_at: jetquery.DateTime, updated_at: jetquery.DateTime, }, .{ .relations = .{ - .song = jetquery.belongsTo(.Song, .{}), .album = jetquery.belongsTo(.Album, .{}), - .artists = jetquery.hasMany(.Artist, .{}), -} }); - -pub const Song = jetquery.Model(@This(), "songs", struct { - id: i32, - name: []const u8, - length: ?f32, - hidden: bool, - created_at: jetquery.DateTime, - updated_at: jetquery.DateTime, -}, .{ .relations = .{ - .scrobbles = jetquery.hasMany(.Scrobble, .{}), - .ratings = jetquery.hasMany(.Rating, .{}), - .aliases = jetquery.hasMany(.Alias, .{}), - .artists = jetquery.hasMany(.Artist, .{}), - .albums = jetquery.hasMany(.Album, .{}), + .song = jetquery.belongsTo(.Song, .{}), } }); diff --git a/src/app/database/migrations/2025-02-19_18-03-51_create_albumartists.zig b/src/app/database/migrations/2025-02-19_18-03-51_create_albumartists.zig new file mode 100644 index 0000000..b0e4f54 --- /dev/null +++ b/src/app/database/migrations/2025-02-19_18-03-51_create_albumartists.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "Albumartists", + &.{ + t.primaryKey("id", .{}), + t.column("album_id", .integer, .{}), + t.column("artist_id", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("Albumartists", .{}); +} diff --git a/src/app/database/migrations/2025-02-19_18-04-22_create_songsartists.zig b/src/app/database/migrations/2025-02-19_18-04-22_create_songsartists.zig new file mode 100644 index 0000000..a509d7a --- /dev/null +++ b/src/app/database/migrations/2025-02-19_18-04-22_create_songsartists.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "Songartists", + &.{ + t.primaryKey("id", .{}), + t.column("song_id", .integer, .{}), + t.column("artist_id", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("Songartists", .{}); +} diff --git a/src/app/database/migrations/2025-02-19_18-46-48_create_albumsongs.zig b/src/app/database/migrations/2025-02-19_18-46-48_create_albumsongs.zig new file mode 100644 index 0000000..1865c2e --- /dev/null +++ b/src/app/database/migrations/2025-02-19_18-46-48_create_albumsongs.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "Albumsongs", + &.{ + t.primaryKey("id", .{}), + t.column("album_id", .integer, .{}), + t.column("song_id", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("Albumsongs", .{}); +} diff --git a/src/app/jobs/process_scrobbles.zig b/src/app/jobs/process_scrobbles.zig index e77c310..f02b94f 100644 --- a/src/app/jobs/process_scrobbles.zig +++ b/src/app/jobs/process_scrobbles.zig @@ -22,8 +22,6 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig //const fixed_date: u32 = @as(u32, item.getT(.integer, "date").?); const scrobble: Scrobble = .{ .track = item.getT(.string, "track").?, .artist = item.getT(.string, "artist").?, .album = item.getT(.string, "album") orelse "empty", .date = @as(u64, @bitCast(@as(i64, @truncate(@divTrunc(item.getT(.integer, "date").?, 1000))))) }; - //std.log.debug("{s}", .{scrobble.track}); - // Make hashes const album_hash = @as(i32, @bitCast(std.hash.Fnv1a_32.hash(scrobble.album))); const artist_hash = @as(i32, @bitCast(std.hash.Fnv1a_32.hash(scrobble.artist))); @@ -63,16 +61,52 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig const song_insert = jetzig.database.Query(.Song).insert(.{ .id = song_id, .name = scrobble.track, .length = null, .hidden = false }); // Checks - const album_check = try jetzig.database.Query(.Album).where(.{.{ .id = album_id }}).count().execute(env.repo); - const artist_check = try jetzig.database.Query(.Artist).where(.{.{ .id = artist_id }}).count().execute(env.repo); - const song_check = try jetzig.database.Query(.Song).where(.{.{ .id = song_id }}).count().execute(env.repo); + const album_check = try jetzig.database.Query(.Album).find(album_id).execute(env.repo); + const artist_check = try jetzig.database.Query(.Artist).find(artist_id).execute(env.repo); + const song_check = try jetzig.database.Query(.Song).find(song_id).execute(env.repo); - if (album_check == 0) try env.repo.execute(album_insert); - if (artist_check == 0) try env.repo.execute(artist_insert); - if (song_check == 0) try env.repo.execute(song_insert); + var associative_table_flags: [3]bool = [3]bool{ true, true, true }; + + if (album_check == null) { + try env.repo.execute(album_insert); + try jetzig.database.Query(.Albumartist).insert(.{ .album_id = album_id, .artist_id = artist_id }).execute(env.repo); + associative_table_flags[0] = false; + try jetzig.database.Query(.Albumsong).insert(.{ .album_id = album_id, .song_id = song_id }).execute(env.repo); + associative_table_flags[1] = false; + } + + if (artist_check == null) { + try env.repo.execute(artist_insert); + if (associative_table_flags[0]) try jetzig.database.Query(.Albumartist).insert(.{ .album_id = album_id, .artist_id = artist_id }).execute(env.repo); + try jetzig.database.Query(.Songartist).insert(.{ .song_id = song_id, .artist_id = artist_id }).execute(env.repo); + associative_table_flags[2] = false; + } + + if (song_check == null) { + try env.repo.execute(song_insert); + if (associative_table_flags[1]) try jetzig.database.Query(.Albumsong).insert(.{ .album_id = album_id, .song_id = song_id }).execute(env.repo); + if (associative_table_flags[2]) try jetzig.database.Query(.Songartist).insert(.{ .song_id = song_id, .artist_id = artist_id }).execute(env.repo); + } + + //try env.repo.execute(album_insert); + //try env.repo.execute(song_insert); + // Checks + + // if (album_check == 0) try env.repo.execute(album_insert); + // if (artist_check == 0) try env.repo.execute(artist_insert); + // if (song_check == 0) try env.repo.execute(song_insert); //const scrobble_offset = try jetzig.database.Query(.Scrobble).select(.{}).count().execute(env.repo) orelse unreachable; //try jetzig.database.Query(.Scrobble).insert(.{ .id = scrobble_offset + 1, .song_id = song_id, .album_id = album_id, .artist_id = artist_id, .date = scrobble.date }).execute(env.repo); } } + + const query = jetzig.database.Query(.Artist).include(.artistalbums, .{}); + const results = try env.repo.all(query); + defer env.repo.free(results); + for (results) |result| { + for (result.artistalbums) |artistalbum| { + std.log.debug("{s}: {any}", .{ result.name, artistalbum.album_id }); + } + } }