mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add projected relations
This commit is contained in:
parent
0ff03fb413
commit
e8351e960d
@ -35,6 +35,8 @@ namespace Kyoo.Abstractions.Models.Attributes
|
||||
|
||||
public string? On { get; set; }
|
||||
|
||||
public string? Projected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LoadableRelationAttribute"/>.
|
||||
/// </summary>
|
||||
|
@ -126,8 +126,20 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The number of episodes in this season.
|
||||
/// </summary>
|
||||
[Projectable(UseMemberBody = nameof(_EpisodesCount))]
|
||||
[Projectable(UseMemberBody = nameof(_EpisodesCount), OnlyOnInclude = true)]
|
||||
[NotMapped]
|
||||
[LoadableRelation(
|
||||
// language=PostgreSQL
|
||||
Projected = """
|
||||
(
|
||||
select
|
||||
count(*)::int
|
||||
from
|
||||
episodes as e
|
||||
where
|
||||
e.season_id = id) as episode_count
|
||||
"""
|
||||
)]
|
||||
public int EpisodesCount { get; set; }
|
||||
|
||||
private int _EpisodesCount => Episodes!.Count;
|
||||
|
@ -19,6 +19,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using EntityFrameworkCore.Projectables;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
@ -111,6 +112,7 @@ namespace Kyoo.Abstractions.Models
|
||||
public string? Trailer { get; set; }
|
||||
|
||||
[SerializeIgnore]
|
||||
[Column("start_air")]
|
||||
public DateTime? AirDate => StartAir;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -25,17 +25,28 @@ using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// The aditional fields to include in the result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type related to the new fields</typeparam>
|
||||
public class Include<T>
|
||||
public class Include
|
||||
{
|
||||
/// <summary>
|
||||
/// The aditional fields to include in the result.
|
||||
/// </summary>
|
||||
public ICollection<Metadata> Metadatas { get; private init; } = ArraySegment<Metadata>.Empty;
|
||||
public ICollection<Metadata> Metadatas { get; init; } = ArraySegment<Metadata>.Empty;
|
||||
|
||||
public abstract record Metadata(string Name);
|
||||
|
||||
public record SingleRelation(string Name, Type type, string RelationIdName) : Metadata(Name);
|
||||
|
||||
public record CustomRelation(string Name, Type type, string Sql, string? On, Type Declaring) : Metadata(Name);
|
||||
|
||||
public record ProjectedRelation(string Name, string Sql) : Metadata(Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The aditional fields to include in the result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type related to the new fields</typeparam>
|
||||
public class Include<T> : Include
|
||||
{
|
||||
/// <summary>
|
||||
/// The aditional fields names to include in the result.
|
||||
/// </summary>
|
||||
@ -79,16 +90,12 @@ public class Include<T>
|
||||
// }
|
||||
if (attr.Sql != null)
|
||||
return new CustomRelation(prop.Name, prop.PropertyType, attr.Sql, attr.On, prop.DeclaringType!);
|
||||
if (attr.Projected != null)
|
||||
return new ProjectedRelation(prop.Name, attr.Projected);
|
||||
throw new NotImplementedException();
|
||||
})
|
||||
.Distinct();
|
||||
}).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public abstract record Metadata(string Name);
|
||||
|
||||
public record SingleRelation(string Name, Type type, string RelationIdName) : Metadata(Name);
|
||||
|
||||
public record CustomRelation(string Name, Type type, string Sql, string? On, Type Declaring) : Metadata(Name);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ using InterpolatedSql.Dapper;
|
||||
using InterpolatedSql.SqlBuilders;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Utils;
|
||||
@ -103,7 +104,7 @@ namespace Kyoo.Core.Controllers
|
||||
|
||||
IEnumerable<string> keys = config
|
||||
.Where(x => key == "id" || x.Value.GetProperty(key) != null)
|
||||
.Select(x => $"{x.Key}.{key.ToSnakeCase()}");
|
||||
.Select(x => $"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}");
|
||||
return $"coalesce({string.Join(", ", keys)})";
|
||||
}
|
||||
|
||||
@ -140,12 +141,12 @@ namespace Kyoo.Core.Controllers
|
||||
relation++;
|
||||
switch (metadata)
|
||||
{
|
||||
case Include<T>.SingleRelation(var name, var type, var rid):
|
||||
case Include.SingleRelation(var name, var type, var rid):
|
||||
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
||||
retConfig.Add($"r{relation}", type);
|
||||
join.AppendLine($"left join {tableName} as r{relation} on r{relation}.id = {_Property(rid, config)}");
|
||||
break;
|
||||
case Include<T>.CustomRelation(var name, var type, var sql, var on, var declaring):
|
||||
case Include.CustomRelation(var name, var type, var sql, var on, var declaring):
|
||||
string owner = config.First(x => x.Value == declaring).Key;
|
||||
string lateral = sql.Contains("\"this\"") ? " lateral" : string.Empty;
|
||||
sql = sql.Replace("\"this\"", owner);
|
||||
@ -153,6 +154,8 @@ namespace Kyoo.Core.Controllers
|
||||
retConfig.Add($"r{relation}", type);
|
||||
join.AppendLine($"left join{lateral} ({sql}) as r{relation} on r{relation}.{on}");
|
||||
break;
|
||||
case Include.ProjectedRelation:
|
||||
continue;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@ -174,6 +177,17 @@ namespace Kyoo.Core.Controllers
|
||||
return (retConfig, join.ToString(), Map);
|
||||
}
|
||||
|
||||
public static string ExpendProjections<T>(string? prefix, Include include)
|
||||
{
|
||||
prefix = prefix != null ? $"{prefix}." : string.Empty;
|
||||
IEnumerable<string> projections = include.Metadatas
|
||||
.Select(x => x is Include.ProjectedRelation(var name, var sql) ? sql : null!)
|
||||
.Where(x => x != null)
|
||||
.Select(x => x.Replace("\"this\".", prefix));
|
||||
string projStr = string.Join(string.Empty, projections.Select(x => $", {x}"));
|
||||
return $"{prefix}*" + projStr;
|
||||
}
|
||||
|
||||
public async Task<ICollection<ILibraryItem>> GetAll(
|
||||
Expression<Func<ILibraryItem, bool>>? where = null,
|
||||
Sort<ILibraryItem>? sort = null,
|
||||
@ -191,7 +205,7 @@ namespace Kyoo.Core.Controllers
|
||||
// language=PostgreSQL
|
||||
IDapperSqlCommand query = _database.SqlBuilder($"""
|
||||
select
|
||||
s.*,
|
||||
{ExpendProjections<Show>("s", include):raw},
|
||||
m.*,
|
||||
c.*
|
||||
{string.Join(string.Empty, includeConfig.Select(x => $", {x.Key}.*")):raw}
|
||||
@ -199,12 +213,12 @@ namespace Kyoo.Core.Controllers
|
||||
shows as s
|
||||
full outer join (
|
||||
select
|
||||
*
|
||||
{ExpendProjections<Movie>(null, include):raw}
|
||||
from
|
||||
movies) as m on false
|
||||
full outer join (
|
||||
full outer join (
|
||||
select
|
||||
*
|
||||
{ExpendProjections<Collection>(null, include):raw}
|
||||
from
|
||||
collections) as c on false
|
||||
{includeJoin:raw}
|
||||
|
@ -245,7 +245,8 @@ namespace Kyoo.Postgresql
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Show>()
|
||||
.Ignore(x => x.FirstEpisode);
|
||||
.Ignore(x => x.FirstEpisode)
|
||||
.Ignore(x => x.AirDate);
|
||||
modelBuilder.Entity<Episode>()
|
||||
.Ignore(x => x.PreviousEpisode)
|
||||
.Ignore(x => x.NextEpisode);
|
||||
|
@ -94,6 +94,7 @@ SeasonHeader.query = (slug: string): QueryIdentifier<Season, SeasonProcessed> =>
|
||||
params: {
|
||||
// Fetch all seasons at one, there won't be hundred of thems anyways.
|
||||
limit: 0,
|
||||
fields: ["episodesCount"],
|
||||
},
|
||||
infinite: {
|
||||
value: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user