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