diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Filter.cs b/back/src/Kyoo.Abstractions/Models/Utils/Filter.cs index 634c5777..a9952a63 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Filter.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Filter.cs @@ -100,7 +100,7 @@ public abstract record Filter : Filter /// Internal filter used for keyset paginations to resume random sorts. /// The pseudo sql is md5(seed || table.id) = md5(seed || 'hardCodedId') /// - public record EqRandom(string Seed, Guid ReferenceId) : Filter; + public record CmpRandom(string cmp, string Seed, Guid ReferenceId) : Filter; /// /// Internal filter used only in EF with hard coded lamdas (used for relations). diff --git a/back/src/Kyoo.Core/Controllers/Repositories/DapperHelper.cs b/back/src/Kyoo.Core/Controllers/Repositories/DapperHelper.cs index 39a30f11..e0d55f50 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/DapperHelper.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/DapperHelper.cs @@ -37,7 +37,7 @@ namespace Kyoo.Core.Controllers; public static class DapperHelper { - private static string _Property(string key, Dictionary config) + public static string Property(string key, Dictionary config) { if (key == "kind") return "kind"; @@ -56,15 +56,15 @@ public static class DapperHelper string ret = sort switch { Sort.Default(var value) => ProcessSort(value, reverse, config, true), - Sort.By(string key, bool desc) => $"{_Property(key, config)} {(desc ^ reverse ? "desc" : "asc")}", - Sort.Random(var seed) => $"md5('{seed}' || {_Property("id", config)}) {(reverse ? "desc" : "asc")}", + Sort.By(string key, bool desc) => $"{Property(key, config)} {(desc ^ reverse ? "desc" : "asc")}", + Sort.Random(var seed) => $"md5('{seed}' || {Property("id", config)}) {(reverse ? "desc" : "asc")}", Sort.Conglomerate(var list) => string.Join(", ", list.Select(x => ProcessSort(x, reverse, config, true))), _ => throw new SwitchExpressionException(), }; if (recurse) return ret; // always end query by an id sort. - return $"{ret}, {_Property("id", config)} {(reverse ? "desc" : "asc")}"; + return $"{ret}, {Property("id", config)} {(reverse ? "desc" : "asc")}"; } public static ( @@ -89,7 +89,7 @@ public static class DapperHelper string tableName = type.GetCustomAttribute()?.Name ?? $"{type.Name.ToSnakeCase()}s"; types.Add(type); projection.AppendLine($", r{relation}.* -- {type.Name} as r{relation}"); - join.Append($"\nleft join {tableName} as r{relation} on r{relation}.id = {_Property(rid, config)}"); + join.Append($"\nleft join {tableName} as r{relation} on r{relation}.id = {Property(rid, config)}"); break; case Include.CustomRelation(var name, var type, var sql, var on, var declaring): string owner = config.First(x => x.Value == declaring).Key; @@ -173,8 +173,8 @@ public static class DapperHelper Filter.Ge(var property, var value) => Format(property, $">= {P(value)}"), Filter.Lt(var property, var value) => Format(property, $"< {P(value)}"), Filter.Le(var property, var value) => Format(property, $"> {P(value)}"), - Filter.Has(var property, var value) => $"{P(value)} = any({_Property(property, config):raw})", - Filter.EqRandom(var seed, var id) => $"md5({seed} || coalesce({string.Join(", ", config.Select(x => $"{x.Key}.id")):raw})) = md5({seed} || {id.ToString()})", + Filter.Has(var property, var value) => $"{P(value)} = any({Property(property, config):raw})", + Filter.CmpRandom(var op, var seed, var id) => $"md5({seed} || coalesce({string.Join(", ", config.Select(x => $"{x.Key}.id")):raw})) {op:raw} md5({seed} || {id.ToString()})", Filter.Lambda(var lambda) => throw new NotSupportedException(), _ => throw new NotImplementedException(), }; @@ -200,7 +200,7 @@ public static class DapperHelper Include? include, Filter? filter, Sort? sort, - Pagination limit) + Pagination? limit) where T : class, IResource, IQuery { InterpolatedSql.Dapper.SqlBuilders.SqlBuilder query = new(db, command); @@ -214,7 +214,7 @@ public static class DapperHelper throw new ArgumentException("Missing '/* includes */' placeholder in top level sql select to support includes."); // Handle pagination, orders and filter. - if (limit.AfterID != null) + if (limit?.AfterID != null) { T reference = await get(limit.AfterID.Value); Filter? keysetFilter = RepositoryHelper.KeysetPaginate(sort, reference, !limit.Reverse); @@ -223,8 +223,9 @@ public static class DapperHelper if (filter != null) query += ProcessFilter(filter, config); if (sort != null) - query += $"\norder by {ProcessSort(sort, limit.Reverse, config):raw}"; - query += $"\nlimit {limit.Limit}"; + query += $"\norder by {ProcessSort(sort, limit?.Reverse ?? false, config):raw}"; + if (limit != null) + query += $"\nlimit {limit.Limit}"; // Build query and prepare to do the query/projections IDapperSqlCommand cmd = query.Build(); @@ -281,7 +282,7 @@ public static class DapperHelper ParametersDictionary.LoadFrom(cmd), splitOn: string.Join(',', types.Select(x => x == typeof(Image) ? "source" : "id")) ); - if (limit.Reverse) + if (limit?.Reverse == true) data = data.Reverse(); return data.ToList(); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/DapperRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/DapperRepository.cs index 364876e0..9de53105 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/DapperRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/DapperRepository.cs @@ -19,11 +19,13 @@ using System; using System.Collections.Generic; using System.Data.Common; +using System.Linq; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Utils; +using static Kyoo.Core.Controllers.DapperHelper; namespace Kyoo.Core.Controllers; @@ -74,9 +76,20 @@ public abstract class DapperRepository : IRepository } /// - public Task> FromIds(IList ids, Include? include = null) + public async Task> FromIds(IList ids, Include? include = null) { - throw new NotImplementedException(); + return (await Database.Query( + Sql, + Config, + Mapper, + (id) => Get(id), + include, + Filter.Or(ids.Select(x => new Filter.Eq("id", x)).ToArray()), + sort: null, + limit: null + )) + .OrderBy(x => ids.IndexOf(x.Id)) + .ToList(); } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 64b76b5c..187c9ede 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -119,14 +119,20 @@ namespace Kyoo.Core.Controllers ParameterExpression x = Expression.Parameter(typeof(T), "x"); - Expression EqRandomHandler(string seed, Guid refId) + Expression CmpRandomHandler(string cmp, string seed, Guid refId) { MethodInfo concat = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) })!; Expression id = Expression.Call(Expression.Property(x, "ID"), nameof(Guid.ToString), null); Expression xrng = Expression.Call(concat, Expression.Constant(seed), id); Expression left = Expression.Call(typeof(DatabaseContext), nameof(DatabaseContext.MD5), null, xrng); Expression right = Expression.Call(typeof(DatabaseContext), nameof(DatabaseContext.MD5), null, Expression.Constant($"{seed}{refId}")); - return Expression.Equal(left, right); + return cmp switch + { + "=" => Expression.Equal(left, right), + "<" => Expression.GreaterThan(left, right), + ">" => Expression.LessThan(left, right), + _ => throw new NotImplementedException() + }; } BinaryExpression StringCompatibleExpression( @@ -154,7 +160,7 @@ namespace Kyoo.Core.Controllers Filter.Lt(var property, var value) => StringCompatibleExpression(Expression.LessThan, Expression.Property(x, property), Expression.Constant(value)), Filter.Le(var property, var value) => StringCompatibleExpression(Expression.LessThanOrEqual, Expression.Property(x, property), Expression.Constant(value)), Filter.Has(var property, var value) => Expression.Call(typeof(Enumerable), "Contains", new[] { value.GetType() }, Expression.Property(x, property), Expression.Constant(value)), - Filter.EqRandom(var seed, var refId) => EqRandomHandler(seed, refId), + Filter.CmpRandom(var op, var seed, var refId) => CmpRandomHandler(op, seed, refId), Filter.Lambda(var lambda) => ExpressionArgumentReplacer.ReplaceParams(lambda.Body, lambda.Parameters, x), _ => throw new NotImplementedException(), }; diff --git a/back/src/Kyoo.Core/Controllers/Repositories/RepositoryHelper.cs b/back/src/Kyoo.Core/Controllers/Repositories/RepositoryHelper.cs index e33af89e..3dd867ef 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/RepositoryHelper.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/RepositoryHelper.cs @@ -90,7 +90,7 @@ public class RepositoryHelper { Filter pEquals = pSeed == null ? new Filter.Eq(pKey, reference.GetType().GetProperty(pKey)?.GetValue(reference)) - : new Filter.EqRandom(pSeed, reference.Id); + : new Filter.CmpRandom("=", pSeed, reference.Id); equals = Filter.And(equals, pEquals); } @@ -100,7 +100,7 @@ public class RepositoryHelper : (prop, val) => new Filter.Lt(prop, val); Filter last = seed == null ? comparer(key, value!) - : new Filter.EqRandom(seed, reference.Id); + : new Filter.CmpRandom(greaterThan ? ">" : "<", seed, reference.Id); if (key != "random") { diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 19cd6e82..d97af360 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq;