mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Fix random sort after id and implement dapper from ids
This commit is contained in:
parent
29d846a944
commit
0ed35e0354
@ -100,7 +100,7 @@ public abstract record Filter<T> : Filter
|
||||
/// Internal filter used for keyset paginations to resume random sorts.
|
||||
/// The pseudo sql is md5(seed || table.id) = md5(seed || 'hardCodedId')
|
||||
/// </summary>
|
||||
public record EqRandom(string Seed, Guid ReferenceId) : Filter<T>;
|
||||
public record CmpRandom(string cmp, string Seed, Guid ReferenceId) : Filter<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Internal filter used only in EF with hard coded lamdas (used for relations).
|
||||
|
@ -37,7 +37,7 @@ namespace Kyoo.Core.Controllers;
|
||||
|
||||
public static class DapperHelper
|
||||
{
|
||||
private static string _Property(string key, Dictionary<string, Type> config)
|
||||
public static string Property(string key, Dictionary<string, Type> config)
|
||||
{
|
||||
if (key == "kind")
|
||||
return "kind";
|
||||
@ -56,15 +56,15 @@ public static class DapperHelper
|
||||
string ret = sort switch
|
||||
{
|
||||
Sort<T>.Default(var value) => ProcessSort(value, reverse, config, true),
|
||||
Sort<T>.By(string key, bool desc) => $"{_Property(key, config)} {(desc ^ reverse ? "desc" : "asc")}",
|
||||
Sort<T>.Random(var seed) => $"md5('{seed}' || {_Property("id", config)}) {(reverse ? "desc" : "asc")}",
|
||||
Sort<T>.By(string key, bool desc) => $"{Property(key, config)} {(desc ^ reverse ? "desc" : "asc")}",
|
||||
Sort<T>.Random(var seed) => $"md5('{seed}' || {Property("id", config)}) {(reverse ? "desc" : "asc")}",
|
||||
Sort<T>.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<TableAttribute>()?.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<T>.Ge(var property, var value) => Format(property, $">= {P(value)}"),
|
||||
Filter<T>.Lt(var property, var value) => Format(property, $"< {P(value)}"),
|
||||
Filter<T>.Le(var property, var value) => Format(property, $"> {P(value)}"),
|
||||
Filter<T>.Has(var property, var value) => $"{P(value)} = any({_Property(property, config):raw})",
|
||||
Filter<T>.EqRandom(var seed, var id) => $"md5({seed} || coalesce({string.Join(", ", config.Select(x => $"{x.Key}.id")):raw})) = md5({seed} || {id.ToString()})",
|
||||
Filter<T>.Has(var property, var value) => $"{P(value)} = any({Property(property, config):raw})",
|
||||
Filter<T>.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<T>.Lambda(var lambda) => throw new NotSupportedException(),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
@ -200,7 +200,7 @@ public static class DapperHelper
|
||||
Include<T>? include,
|
||||
Filter<T>? filter,
|
||||
Sort<T>? 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<T>? 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();
|
||||
}
|
||||
|
@ -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<T> : IRepository<T>
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<T>> FromIds(IList<Guid> ids, Include<T>? include = null)
|
||||
public async Task<ICollection<T>> FromIds(IList<Guid> ids, Include<T>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return (await Database.Query<T>(
|
||||
Sql,
|
||||
Config,
|
||||
Mapper,
|
||||
(id) => Get(id),
|
||||
include,
|
||||
Filter.Or(ids.Select(x => new Filter<T>.Eq("id", x)).ToArray()),
|
||||
sort: null,
|
||||
limit: null
|
||||
))
|
||||
.OrderBy(x => ids.IndexOf(x.Id))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -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<T>.Lt(var property, var value) => StringCompatibleExpression(Expression.LessThan, Expression.Property(x, property), Expression.Constant(value)),
|
||||
Filter<T>.Le(var property, var value) => StringCompatibleExpression(Expression.LessThanOrEqual, Expression.Property(x, property), Expression.Constant(value)),
|
||||
Filter<T>.Has(var property, var value) => Expression.Call(typeof(Enumerable), "Contains", new[] { value.GetType() }, Expression.Property(x, property), Expression.Constant(value)),
|
||||
Filter<T>.EqRandom(var seed, var refId) => EqRandomHandler(seed, refId),
|
||||
Filter<T>.CmpRandom(var op, var seed, var refId) => CmpRandomHandler(op, seed, refId),
|
||||
Filter<T>.Lambda(var lambda) => ExpressionArgumentReplacer.ReplaceParams(lambda.Body, lambda.Parameters, x),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
@ -90,7 +90,7 @@ public class RepositoryHelper
|
||||
{
|
||||
Filter<T> pEquals = pSeed == null
|
||||
? new Filter<T>.Eq(pKey, reference.GetType().GetProperty(pKey)?.GetValue(reference))
|
||||
: new Filter<T>.EqRandom(pSeed, reference.Id);
|
||||
: new Filter<T>.CmpRandom("=", pSeed, reference.Id);
|
||||
equals = Filter.And(equals, pEquals);
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ public class RepositoryHelper
|
||||
: (prop, val) => new Filter<T>.Lt(prop, val);
|
||||
Filter<T> last = seed == null
|
||||
? comparer(key, value!)
|
||||
: new Filter<T>.EqRandom(seed, reference.Id);
|
||||
: new Filter<T>.CmpRandom(greaterThan ? ">" : "<", seed, reference.Id);
|
||||
|
||||
if (key != "random")
|
||||
{
|
||||
|
@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
Loading…
x
Reference in New Issue
Block a user