Fix random sort after id and implement dapper from ids

This commit is contained in:
Zoe Roux 2023-11-29 01:46:27 +01:00
parent 29d846a944
commit 0ed35e0354
6 changed files with 41 additions and 20 deletions

View File

@ -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).

View File

@ -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();
}

View File

@ -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 />

View File

@ -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(),
};

View File

@ -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")
{

View File

@ -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;