mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 20:24:27 -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.
|
/// Internal filter used for keyset paginations to resume random sorts.
|
||||||
/// The pseudo sql is md5(seed || table.id) = md5(seed || 'hardCodedId')
|
/// The pseudo sql is md5(seed || table.id) = md5(seed || 'hardCodedId')
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record EqRandom(string Seed, Guid ReferenceId) : Filter<T>;
|
public record CmpRandom(string cmp, string Seed, Guid ReferenceId) : Filter<T>;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal filter used only in EF with hard coded lamdas (used for relations).
|
/// 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
|
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")
|
if (key == "kind")
|
||||||
return "kind";
|
return "kind";
|
||||||
@ -56,15 +56,15 @@ public static class DapperHelper
|
|||||||
string ret = sort switch
|
string ret = sort switch
|
||||||
{
|
{
|
||||||
Sort<T>.Default(var value) => ProcessSort(value, reverse, config, true),
|
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>.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>.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))),
|
Sort<T>.Conglomerate(var list) => string.Join(", ", list.Select(x => ProcessSort(x, reverse, config, true))),
|
||||||
_ => throw new SwitchExpressionException(),
|
_ => throw new SwitchExpressionException(),
|
||||||
};
|
};
|
||||||
if (recurse)
|
if (recurse)
|
||||||
return ret;
|
return ret;
|
||||||
// always end query by an id sort.
|
// 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 (
|
public static (
|
||||||
@ -89,7 +89,7 @@ public static class DapperHelper
|
|||||||
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
||||||
types.Add(type);
|
types.Add(type);
|
||||||
projection.AppendLine($", r{relation}.* -- {type.Name} as r{relation}");
|
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;
|
break;
|
||||||
case Include.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;
|
||||||
@ -173,8 +173,8 @@ public static class DapperHelper
|
|||||||
Filter<T>.Ge(var property, var value) => Format(property, $">= {P(value)}"),
|
Filter<T>.Ge(var property, var value) => Format(property, $">= {P(value)}"),
|
||||||
Filter<T>.Lt(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>.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>.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>.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(),
|
Filter<T>.Lambda(var lambda) => throw new NotSupportedException(),
|
||||||
_ => throw new NotImplementedException(),
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
@ -200,7 +200,7 @@ public static class DapperHelper
|
|||||||
Include<T>? include,
|
Include<T>? include,
|
||||||
Filter<T>? filter,
|
Filter<T>? filter,
|
||||||
Sort<T>? sort,
|
Sort<T>? sort,
|
||||||
Pagination limit)
|
Pagination? limit)
|
||||||
where T : class, IResource, IQuery
|
where T : class, IResource, IQuery
|
||||||
{
|
{
|
||||||
InterpolatedSql.Dapper.SqlBuilders.SqlBuilder query = new(db, command);
|
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.");
|
throw new ArgumentException("Missing '/* includes */' placeholder in top level sql select to support includes.");
|
||||||
|
|
||||||
// Handle pagination, orders and filter.
|
// Handle pagination, orders and filter.
|
||||||
if (limit.AfterID != null)
|
if (limit?.AfterID != null)
|
||||||
{
|
{
|
||||||
T reference = await get(limit.AfterID.Value);
|
T reference = await get(limit.AfterID.Value);
|
||||||
Filter<T>? keysetFilter = RepositoryHelper.KeysetPaginate(sort, reference, !limit.Reverse);
|
Filter<T>? keysetFilter = RepositoryHelper.KeysetPaginate(sort, reference, !limit.Reverse);
|
||||||
@ -223,8 +223,9 @@ public static class DapperHelper
|
|||||||
if (filter != null)
|
if (filter != null)
|
||||||
query += ProcessFilter(filter, config);
|
query += ProcessFilter(filter, config);
|
||||||
if (sort != null)
|
if (sort != null)
|
||||||
query += $"\norder by {ProcessSort(sort, limit.Reverse, config):raw}";
|
query += $"\norder by {ProcessSort(sort, limit?.Reverse ?? false, config):raw}";
|
||||||
query += $"\nlimit {limit.Limit}";
|
if (limit != null)
|
||||||
|
query += $"\nlimit {limit.Limit}";
|
||||||
|
|
||||||
// Build query and prepare to do the query/projections
|
// Build query and prepare to do the query/projections
|
||||||
IDapperSqlCommand cmd = query.Build();
|
IDapperSqlCommand cmd = query.Build();
|
||||||
@ -281,7 +282,7 @@ public static class DapperHelper
|
|||||||
ParametersDictionary.LoadFrom(cmd),
|
ParametersDictionary.LoadFrom(cmd),
|
||||||
splitOn: string.Join(',', types.Select(x => x == typeof(Image) ? "source" : "id"))
|
splitOn: string.Join(',', types.Select(x => x == typeof(Image) ? "source" : "id"))
|
||||||
);
|
);
|
||||||
if (limit.Reverse)
|
if (limit?.Reverse == true)
|
||||||
data = data.Reverse();
|
data = data.Reverse();
|
||||||
return data.ToList();
|
return data.ToList();
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
using Kyoo.Abstractions.Models.Utils;
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using static Kyoo.Core.Controllers.DapperHelper;
|
||||||
|
|
||||||
namespace Kyoo.Core.Controllers;
|
namespace Kyoo.Core.Controllers;
|
||||||
|
|
||||||
@ -74,9 +76,20 @@ public abstract class DapperRepository<T> : IRepository<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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 />
|
/// <inheritdoc />
|
||||||
|
@ -119,14 +119,20 @@ namespace Kyoo.Core.Controllers
|
|||||||
|
|
||||||
ParameterExpression x = Expression.Parameter(typeof(T), "x");
|
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) })!;
|
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 id = Expression.Call(Expression.Property(x, "ID"), nameof(Guid.ToString), null);
|
||||||
Expression xrng = Expression.Call(concat, Expression.Constant(seed), id);
|
Expression xrng = Expression.Call(concat, Expression.Constant(seed), id);
|
||||||
Expression left = Expression.Call(typeof(DatabaseContext), nameof(DatabaseContext.MD5), null, xrng);
|
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}"));
|
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(
|
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>.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>.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>.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),
|
Filter<T>.Lambda(var lambda) => ExpressionArgumentReplacer.ReplaceParams(lambda.Body, lambda.Parameters, x),
|
||||||
_ => throw new NotImplementedException(),
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
|
@ -90,7 +90,7 @@ public class RepositoryHelper
|
|||||||
{
|
{
|
||||||
Filter<T> pEquals = pSeed == null
|
Filter<T> pEquals = pSeed == null
|
||||||
? new Filter<T>.Eq(pKey, reference.GetType().GetProperty(pKey)?.GetValue(reference))
|
? 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);
|
equals = Filter.And(equals, pEquals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ public class RepositoryHelper
|
|||||||
: (prop, val) => new Filter<T>.Lt(prop, val);
|
: (prop, val) => new Filter<T>.Lt(prop, val);
|
||||||
Filter<T> last = seed == null
|
Filter<T> last = seed == null
|
||||||
? comparer(key, value!)
|
? comparer(key, value!)
|
||||||
: new Filter<T>.EqRandom(seed, reference.Id);
|
: new Filter<T>.CmpRandom(greaterThan ? ">" : "<", seed, reference.Id);
|
||||||
|
|
||||||
if (key != "random")
|
if (key != "random")
|
||||||
{
|
{
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user