Initial code commit
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Constants;
|
||||
|
||||
public static class Cache
|
||||
{
|
||||
public static class Duration
|
||||
{
|
||||
public static TimeSpan Short => TimeSpan.FromHours(1);
|
||||
|
||||
public static TimeSpan Long => TimeSpan.FromDays(7);
|
||||
}
|
||||
|
||||
public static class CacheKeys
|
||||
{
|
||||
public static class Msd
|
||||
{
|
||||
private const string Prefix = nameof(Msd);
|
||||
|
||||
public static string ByCode(string code)
|
||||
{
|
||||
return $"{Prefix}_{nameof(ByCode)}_{code}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class Tokenizer
|
||||
{
|
||||
private const string Prefix = nameof(Tokenizer);
|
||||
|
||||
public static string ByQuery(string query)
|
||||
{
|
||||
return $"{Prefix}_{nameof(ByQuery)}_{query}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Rsdo.Concordancer.Core.Constants;
|
||||
|
||||
public static class ConfigurationKey
|
||||
{
|
||||
public static class Database
|
||||
{
|
||||
public const string MasterConnectionString = "RSDO:Database:MasterConnectionString";
|
||||
}
|
||||
|
||||
public static class Search
|
||||
{
|
||||
public const string ElasticConnectionString = "RSDO:Elastic:ConnectionString";
|
||||
}
|
||||
|
||||
public static class Tokenizer
|
||||
{
|
||||
public const string ClasslaTokenizerUrl = "RSDO:Tokenizer:ClasslaUrl";
|
||||
}
|
||||
|
||||
public static class Web
|
||||
{
|
||||
public const string BaseAppPath = "RSDO:Web:BaseAppPath";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Rsdo.Concordancer.Core.Constants;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static class Export
|
||||
{
|
||||
public const string DefaultContentType = "text/plain";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class Corpus : Entity
|
||||
{
|
||||
public string Description { get; set; }
|
||||
|
||||
public CorpusStatus Status { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public abstract class Entity
|
||||
{
|
||||
public long AutoId { get; set; }
|
||||
|
||||
public DateTime CreatedDate { get; set; }
|
||||
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTime ModifiedDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class LemmaFormPair : Entity
|
||||
{
|
||||
public string Form { get; set; }
|
||||
|
||||
public string Lemma { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class Msd : Entity
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string EnglishDescription { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class Paragraph : Entity
|
||||
{
|
||||
public int RecordOrder { get; set; }
|
||||
|
||||
public List<Sentence> Sentences { get; set; }
|
||||
|
||||
public Text Text { get; set; }
|
||||
|
||||
public long TextAutoId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class Sentence : Entity
|
||||
{
|
||||
public Paragraph Paragraph { get; set; }
|
||||
|
||||
public long ParagraphAutoId { get; set; }
|
||||
|
||||
public int RecordOrder { get; set; }
|
||||
|
||||
public List<Token> Tokens { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class Term : Entity
|
||||
{
|
||||
public string Form { get; set; }
|
||||
|
||||
public int Frequency { get; set; }
|
||||
|
||||
public string Lemma { get; set; }
|
||||
|
||||
public string Msd { get; set; }
|
||||
|
||||
public TermList TermList { get; set; }
|
||||
|
||||
public long TermListAutoId { get; set; }
|
||||
|
||||
public decimal Weight { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class TermList : Entity
|
||||
{
|
||||
public string SourceFile { get; set; }
|
||||
|
||||
public ImportStatus Status { get; set; }
|
||||
|
||||
public List<Term> Terms { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class Text : Entity
|
||||
{
|
||||
public string Author { get; set; }
|
||||
|
||||
public List<Paragraph> Paragraphs { get; set; }
|
||||
|
||||
public string SourceFile { get; set; }
|
||||
|
||||
public ImportStatus Status { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public short? Year { get; set; }
|
||||
|
||||
public string DisplayFileName => Path.GetFileNameWithoutExtension(SourceFile);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
public class Token : Entity
|
||||
{
|
||||
public string Form { get; set; }
|
||||
|
||||
public string Lemma { get; set; }
|
||||
|
||||
public string Msd { get; set; }
|
||||
|
||||
public int RecordOrder { get; set; }
|
||||
|
||||
public Sentence Sentence { get; set; }
|
||||
|
||||
public long SentenceAutoId { get; set; }
|
||||
|
||||
public int TokenOrder { get; set; }
|
||||
|
||||
public TokenType Type { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Exceptions;
|
||||
|
||||
public static class Errors
|
||||
{
|
||||
public static class Forbidden
|
||||
{
|
||||
public static string CorpusStatusIsNotValid(CorpusStatus currentStatus, CorpusStatus requiredStatus)
|
||||
{
|
||||
return $"To perform selected action, corpus status must be {requiredStatus.ToString()}. Current status is {currentStatus.ToString()}.";
|
||||
}
|
||||
}
|
||||
|
||||
public static class NotFound
|
||||
{
|
||||
public static string EntityNotFound(EntityType entityType, Guid id)
|
||||
{
|
||||
return EntityNotFound(entityType, nameof(Entity.Id), id.ToString());
|
||||
}
|
||||
|
||||
public static string EntityNotFound(EntityType entityType, string columnName, string value)
|
||||
{
|
||||
return $"Entity {entityType.ToString()} with {columnName}={value} not found.";
|
||||
}
|
||||
|
||||
public static string FileNotFound(string path)
|
||||
{
|
||||
return $"$File {path} not found.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Exceptions;
|
||||
|
||||
public class XBadRequestException : Exception
|
||||
{
|
||||
public XBadRequestException()
|
||||
{
|
||||
}
|
||||
|
||||
public XBadRequestException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public XBadRequestException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Exceptions;
|
||||
|
||||
public class XForbiddenException : Exception
|
||||
{
|
||||
public XForbiddenException()
|
||||
{
|
||||
}
|
||||
|
||||
public XForbiddenException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public XForbiddenException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Exceptions;
|
||||
|
||||
public class XInternalErrorException : Exception
|
||||
{
|
||||
public XInternalErrorException()
|
||||
{
|
||||
}
|
||||
|
||||
public XInternalErrorException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public XInternalErrorException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Exceptions;
|
||||
|
||||
public class XNotFoundException : Exception
|
||||
{
|
||||
public XNotFoundException()
|
||||
{
|
||||
}
|
||||
|
||||
public XNotFoundException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public XNotFoundException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Extensions;
|
||||
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
public static void ForEach(this Array array, Action<Array, int[]> action)
|
||||
{
|
||||
if (array.LongLength == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var walker = new ArrayTraverse(array);
|
||||
do
|
||||
{
|
||||
action(array, walker.Position);
|
||||
}
|
||||
while (walker.Step());
|
||||
}
|
||||
}
|
||||
|
||||
internal class ArrayTraverse
|
||||
{
|
||||
public int[] Position;
|
||||
private readonly int[] maxLengths;
|
||||
|
||||
public ArrayTraverse(Array array)
|
||||
{
|
||||
maxLengths = new int[array.Rank];
|
||||
for (var i = 0; i < array.Rank; ++i)
|
||||
{
|
||||
maxLengths[i] = array.GetLength(i) - 1;
|
||||
}
|
||||
|
||||
Position = new int[array.Rank];
|
||||
}
|
||||
|
||||
public bool Step()
|
||||
{
|
||||
for (var i = 0; i < Position.Length; ++i)
|
||||
{
|
||||
if (Position[i] < maxLengths[i])
|
||||
{
|
||||
Position[i]++;
|
||||
for (var j = 0; j < i; j++)
|
||||
{
|
||||
Position[j] = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
using Rsdo.Concordancer.Core.Model;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Extensions;
|
||||
|
||||
public static class ConcordanceExtensions
|
||||
{
|
||||
public static void SetToken(this Concordance concordance, Token token, int position)
|
||||
{
|
||||
switch (position)
|
||||
{
|
||||
case -10:
|
||||
concordance.TokenLeft10 = token;
|
||||
break;
|
||||
case -9:
|
||||
concordance.TokenLeft9 = token;
|
||||
break;
|
||||
case -8:
|
||||
concordance.TokenLeft8 = token;
|
||||
break;
|
||||
case -7:
|
||||
concordance.TokenLeft7 = token;
|
||||
break;
|
||||
case -6:
|
||||
concordance.TokenLeft6 = token;
|
||||
break;
|
||||
case -5:
|
||||
concordance.TokenLeft5 = token;
|
||||
break;
|
||||
case -4:
|
||||
concordance.TokenLeft4 = token;
|
||||
break;
|
||||
case -3:
|
||||
concordance.TokenLeft3 = token;
|
||||
break;
|
||||
case -2:
|
||||
concordance.TokenLeft2 = token;
|
||||
break;
|
||||
case -1:
|
||||
concordance.TokenLeft1 = token;
|
||||
break;
|
||||
case 0:
|
||||
concordance.Token = token;
|
||||
break;
|
||||
case 1:
|
||||
concordance.TokenRight1 = token;
|
||||
break;
|
||||
case 2:
|
||||
concordance.TokenRight2 = token;
|
||||
break;
|
||||
case 3:
|
||||
concordance.TokenRight3 = token;
|
||||
break;
|
||||
case 4:
|
||||
concordance.TokenRight4 = token;
|
||||
break;
|
||||
case 5:
|
||||
concordance.TokenRight5 = token;
|
||||
break;
|
||||
case 6:
|
||||
concordance.TokenRight6 = token;
|
||||
break;
|
||||
case 7:
|
||||
concordance.TokenRight7 = token;
|
||||
break;
|
||||
case 8:
|
||||
concordance.TokenRight8 = token;
|
||||
break;
|
||||
case 9:
|
||||
concordance.TokenRight9 = token;
|
||||
break;
|
||||
case 10:
|
||||
concordance.TokenRight10 = token;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid position {position}. Position should be between -10 and 10.", nameof(position));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Extensions;
|
||||
|
||||
public static class EntityExtensions
|
||||
{
|
||||
public static TEntity ApplyCreateValues<TEntity>(this TEntity entity)
|
||||
where TEntity : Entity
|
||||
{
|
||||
entity.Id = Guid.NewGuid();
|
||||
entity.CreatedDate = DateTime.Now;
|
||||
entity.ModifiedDate = entity.CreatedDate;
|
||||
return entity;
|
||||
}
|
||||
|
||||
public static TEntity ApplyUpdateValues<TEntity>(this TEntity entity)
|
||||
where TEntity : Entity
|
||||
{
|
||||
entity.ModifiedDate = DateTime.Now;
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Extensions;
|
||||
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
|
||||
{
|
||||
IEnumerable<IEnumerable<T>> emptyProduct = new[]
|
||||
{
|
||||
Enumerable.Empty<T>(),
|
||||
};
|
||||
return sequences.Aggregate(
|
||||
emptyProduct,
|
||||
(accumulator, sequence) => from accseq in accumulator
|
||||
from item in sequence
|
||||
select accseq.Concat(
|
||||
new[]
|
||||
{
|
||||
item,
|
||||
}));
|
||||
}
|
||||
|
||||
public static bool IsNullOrEmpty<TSource>(this IEnumerable<TSource> collection)
|
||||
{
|
||||
return collection == null || !collection.Any();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Rsdo.Concordancer.ServiceModel.Shared;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Extensions;
|
||||
|
||||
public static class ExecutionResultExtensions
|
||||
{
|
||||
public static ExecutionResult WithEntityInfo(this ExecutionResult instance, EntityType entityType, Guid entityId)
|
||||
{
|
||||
instance.EntityInfo = new EntityInfo()
|
||||
{
|
||||
EntityType = entityType,
|
||||
Id = entityId,
|
||||
};
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Extensions;
|
||||
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
private static readonly MethodInfo CloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
public static object Copy(this object originalObject)
|
||||
{
|
||||
return InternalCopy(originalObject, new Dictionary<object, object>(new ReferenceEqualityComparer()));
|
||||
}
|
||||
|
||||
public static T Copy<T>(this T original)
|
||||
{
|
||||
return (T)Copy((object)original);
|
||||
}
|
||||
|
||||
public static bool IsPrimitive(this Type type)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return type.IsValueType & type.IsPrimitive;
|
||||
}
|
||||
|
||||
private static void CopyFields(
|
||||
object originalObject,
|
||||
IDictionary<object, object> visited,
|
||||
object cloneObject,
|
||||
Type typeToReflect,
|
||||
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy,
|
||||
Func<FieldInfo, bool> filter = null)
|
||||
{
|
||||
foreach (var fieldInfo in typeToReflect.GetFields(bindingFlags))
|
||||
{
|
||||
if (filter != null && filter(fieldInfo) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsPrimitive(fieldInfo.FieldType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var originalFieldValue = fieldInfo.GetValue(originalObject);
|
||||
var clonedFieldValue = InternalCopy(originalFieldValue, visited);
|
||||
fieldInfo.SetValue(cloneObject, clonedFieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static object InternalCopy(object originalObject, IDictionary<object, object> visited)
|
||||
{
|
||||
if (originalObject == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var typeToReflect = originalObject.GetType();
|
||||
if (IsPrimitive(typeToReflect))
|
||||
{
|
||||
return originalObject;
|
||||
}
|
||||
|
||||
if (visited.ContainsKey(originalObject))
|
||||
{
|
||||
return visited[originalObject];
|
||||
}
|
||||
|
||||
if (typeof(Delegate).IsAssignableFrom(typeToReflect))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var cloneObject = CloneMethod.Invoke(originalObject, null);
|
||||
if (typeToReflect.IsArray)
|
||||
{
|
||||
var arrayType = typeToReflect.GetElementType();
|
||||
if (IsPrimitive(arrayType) == false)
|
||||
{
|
||||
var clonedArray = (Array)cloneObject;
|
||||
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
|
||||
}
|
||||
}
|
||||
|
||||
visited.Add(originalObject, cloneObject);
|
||||
CopyFields(originalObject, visited, cloneObject, typeToReflect);
|
||||
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
|
||||
return cloneObject;
|
||||
}
|
||||
|
||||
private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
|
||||
{
|
||||
if (typeToReflect.BaseType != null)
|
||||
{
|
||||
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
|
||||
CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ReferenceEqualityComparer : EqualityComparer<object>
|
||||
{
|
||||
public override bool Equals(object x, object y)
|
||||
{
|
||||
return ReferenceEquals(x, y);
|
||||
}
|
||||
|
||||
public override int GetHashCode(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return obj.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
using Rsdo.Concordancer.ServiceModel.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Extensions;
|
||||
|
||||
public static class QueryExtensions
|
||||
{
|
||||
public static Query WithPageInfo(this Query query, IPagedSearch request)
|
||||
{
|
||||
query.From = request.From;
|
||||
query.Size = request.Size;
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Threading;
|
||||
using Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Framework;
|
||||
|
||||
public static class CurrentContext
|
||||
{
|
||||
private static readonly AsyncLocal<ICurrentContext> CurrentContextValue = new();
|
||||
|
||||
public static ICurrentContext Current
|
||||
{
|
||||
get => CurrentContextValue.Value;
|
||||
set => CurrentContextValue.Value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Framework.CurrentContexts;
|
||||
|
||||
public abstract class CurrentContextBase : ICurrentContext
|
||||
{
|
||||
public Guid CorpusId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Rsdo.Concordancer.Core.Framework.CurrentContexts;
|
||||
|
||||
public class DefaultCurrentContext : CurrentContextBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Rsdo.Concordancer.Core.Framework.CurrentContexts;
|
||||
|
||||
public class SystemCurrentContext : CurrentContextBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
public interface IConnectionStringProvider
|
||||
{
|
||||
string GetCorpusConnectionString();
|
||||
|
||||
string GetCorpusConnectionString(Guid corpusId);
|
||||
|
||||
string GetCorpusDatabaseName();
|
||||
|
||||
string GetMasterConnectionString();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
public interface ICurrentContext
|
||||
{
|
||||
Guid CorpusId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
public enum MigrationTag
|
||||
{
|
||||
Master,
|
||||
Corpus,
|
||||
}
|
||||
|
||||
public interface IDatabaseMigrationRunner
|
||||
{
|
||||
void MigrateUp(string connectionString, MigrationTag migrationTag);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Rsdo.Concordancer.ServiceModel.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
public interface IMediator
|
||||
{
|
||||
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
|
||||
|
||||
[DisplayName("{0}")]
|
||||
Task<TResponse> Send<TResponse>(string requestName, IRequest<TResponse> request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Rsdo.Concordancer.ServiceModel.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
public interface IRequestHandler<TRequest, TResponse>
|
||||
where TRequest : IRequest<TResponse>
|
||||
{
|
||||
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Rsdo.Concordancer.ServiceModel.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
public interface IServiceBus
|
||||
{
|
||||
Task Send<TResponse>(IRequest<TResponse> request);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Model;
|
||||
|
||||
public class Concordance
|
||||
{
|
||||
public Guid ParagraphId { get; set; }
|
||||
|
||||
public Guid TextId { get; set; }
|
||||
|
||||
public Token Token { get; set; }
|
||||
|
||||
public Token TokenLeft1 { get; set; }
|
||||
|
||||
public Token TokenLeft2 { get; set; }
|
||||
|
||||
public Token TokenLeft3 { get; set; }
|
||||
|
||||
public Token TokenLeft4 { get; set; }
|
||||
|
||||
public Token TokenLeft5 { get; set; }
|
||||
|
||||
public Token TokenLeft6 { get; set; }
|
||||
|
||||
public Token TokenLeft7 { get; set; }
|
||||
|
||||
public Token TokenLeft8 { get; set; }
|
||||
|
||||
public Token TokenLeft9 { get; set; }
|
||||
|
||||
public Token TokenLeft10 { get; set; }
|
||||
|
||||
public Token TokenRight1 { get; set; }
|
||||
|
||||
public Token TokenRight2 { get; set; }
|
||||
|
||||
public Token TokenRight3 { get; set; }
|
||||
|
||||
public Token TokenRight4 { get; set; }
|
||||
|
||||
public Token TokenRight5 { get; set; }
|
||||
|
||||
public Token TokenRight6 { get; set; }
|
||||
|
||||
public Token TokenRight7 { get; set; }
|
||||
|
||||
public Token TokenRight8 { get; set; }
|
||||
|
||||
public Token TokenRight9 { get; set; }
|
||||
|
||||
public Token TokenRight10 { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Rsdo.Concordancer.ServiceModel\Rsdo.Concordancer.ServiceModel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Aggregations;
|
||||
|
||||
public interface IAggregator
|
||||
{
|
||||
Task<IDictionary<string, long>> Get<TQuery>(TQuery query)
|
||||
where TQuery : Query;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Aggregations;
|
||||
|
||||
public interface IAggregatorFactory
|
||||
{
|
||||
IAggregator GetAggregator(AggregationType aggregationType);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search;
|
||||
|
||||
public interface ISearchEngine
|
||||
{
|
||||
Task Add<TEntity>(IEnumerable<TEntity> entities);
|
||||
|
||||
Task Commit();
|
||||
|
||||
Task CreateSchema();
|
||||
|
||||
Task Delete<TEntity>(IEnumerable<Guid> entityIds);
|
||||
|
||||
Task DeleteSchema();
|
||||
|
||||
Task<QueryResult> Search<TEntity, TQuery>(TQuery query)
|
||||
where TEntity : class
|
||||
where TQuery : Query;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries.Concordances;
|
||||
|
||||
public class ConcordancesQuery : Query
|
||||
{
|
||||
public SearchedMainWordQuery MainWord { get; set; }
|
||||
|
||||
public bool ReturnRandomRows { get; set; }
|
||||
|
||||
public List<Guid> TextIds { get; set; }
|
||||
|
||||
public List<SearchedWordInContextQuery> WordsInContext { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries.Concordances;
|
||||
|
||||
public class SearchedMainWordQuery : SearchedWordQuery
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries.Concordances;
|
||||
|
||||
public class SearchedWordInContextQuery : SearchedWordQuery
|
||||
{
|
||||
public ConditionType ConditionType { get; set; }
|
||||
|
||||
public List<int> Positions { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries.Concordances;
|
||||
|
||||
public abstract class SearchedWordQuery
|
||||
{
|
||||
public string Form { get; set; }
|
||||
|
||||
public List<string> Lemmas { get; set; }
|
||||
|
||||
public List<string> Msds { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries;
|
||||
|
||||
public abstract class Query
|
||||
{
|
||||
public int From { get; set; } = 0;
|
||||
|
||||
public int Size { get; set; } = 20;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries;
|
||||
|
||||
public class QueryResult
|
||||
{
|
||||
public List<string> EntityIds { get; set; }
|
||||
|
||||
public long Total { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries.TermLists;
|
||||
|
||||
public class SearchedTermQuery
|
||||
{
|
||||
public string Form { get; set; }
|
||||
|
||||
public List<string> Lemmas { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Core.Search.Queries.TermLists;
|
||||
|
||||
public class TermListQuery : Query
|
||||
{
|
||||
public List<SearchedTermQuery> Words { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Autofac;
|
||||
using Rsdo.Concordancer.Core.Interfaces;
|
||||
using Rsdo.Concordancer.Data.Framework;
|
||||
using Rsdo.Concordancer.Data.Services;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.CompositionRoot;
|
||||
|
||||
public class DataModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
builder.RegisterType<ConnectionStringProvider>().As<IConnectionStringProvider>().SingleInstance();
|
||||
builder.RegisterType<DatabaseMigrationRunner>().As<IDatabaseMigrationRunner>().SingleInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using FluentMigrator;
|
||||
using FluentMigrator.Builders.Create.Table;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Extensions;
|
||||
|
||||
public static class FluentMigrationExtensions
|
||||
{
|
||||
public static ICreateTableColumnOptionOrWithColumnSyntax WithEntity(this ICreateTableWithColumnSyntax tableWithColumnSyntax)
|
||||
{
|
||||
return tableWithColumnSyntax.WithColumn(nameof(Entity.AutoId))
|
||||
.AsInt64()
|
||||
.PrimaryKey()
|
||||
.Identity()
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Entity.CreatedDate))
|
||||
.AsDateTime2()
|
||||
.NotNullable()
|
||||
.WithDefault(SystemMethods.CurrentDateTime)
|
||||
.WithColumn(nameof(Entity.Id))
|
||||
.AsGuid()
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Entity.ModifiedDate))
|
||||
.AsDateTime2()
|
||||
.NotNullable()
|
||||
.WithDefault(SystemMethods.CurrentDateTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Npgsql;
|
||||
using Rsdo.Concordancer.Core.Constants;
|
||||
using Rsdo.Concordancer.Core.Framework;
|
||||
using Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Framework;
|
||||
|
||||
public class ConnectionStringProvider : IConnectionStringProvider
|
||||
{
|
||||
private readonly IConfiguration configuration;
|
||||
|
||||
public ConnectionStringProvider(IConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public string GetCorpusConnectionString()
|
||||
{
|
||||
return GetCorpusConnectionString(CurrentContext.Current.CorpusId);
|
||||
}
|
||||
|
||||
public string GetCorpusConnectionString(Guid corpusId)
|
||||
{
|
||||
var masterConnectionString = GetMasterConnectionString();
|
||||
var masterBuilder = new NpgsqlConnectionStringBuilder(masterConnectionString)
|
||||
{
|
||||
Database = GetCorpusDatabaseName(corpusId),
|
||||
};
|
||||
return masterBuilder.ConnectionString;
|
||||
}
|
||||
|
||||
public string GetCorpusDatabaseName()
|
||||
{
|
||||
return GetCorpusDatabaseName(CurrentContext.Current.CorpusId);
|
||||
}
|
||||
|
||||
public string GetMasterConnectionString()
|
||||
{
|
||||
return configuration[ConfigurationKey.Database.MasterConnectionString];
|
||||
}
|
||||
|
||||
private string GetMasterDatabaseName()
|
||||
{
|
||||
var connectionString = GetMasterConnectionString();
|
||||
return new NpgsqlConnectionStringBuilder(connectionString).Database;
|
||||
}
|
||||
|
||||
private string GetCorpusDatabaseName(Guid corpusId)
|
||||
{
|
||||
var masterDatabaseName = GetMasterDatabaseName();
|
||||
return $"{masterDatabaseName}_{corpusId:N}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using FluentMigrator;
|
||||
using Rsdo.Concordancer.Core.Interfaces;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Framework;
|
||||
|
||||
public abstract class DbAttribute : TagsAttribute
|
||||
{
|
||||
protected DbAttribute(MigrationTag migrationTag)
|
||||
: base(migrationTag.ToString())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class CorpusDbAttribute : DbAttribute
|
||||
{
|
||||
public CorpusDbAttribute()
|
||||
: base(MigrationTag.Corpus)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class MasterDbAttribute : DbAttribute
|
||||
{
|
||||
public MasterDbAttribute()
|
||||
: base(MigrationTag.Master)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentMigrator.Runner.Generators.Postgres;
|
||||
using FluentMigrator.Runner.Processors.Postgres;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Framework;
|
||||
|
||||
public class NoQuoteQuoter : PostgresQuoter
|
||||
{
|
||||
public NoQuoteQuoter(PostgresOptions options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ShouldQuote(string name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentMigrator.Runner.VersionTableInfo;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Framework;
|
||||
|
||||
public class VersionInfoTableMetadata : IVersionTableMetaData
|
||||
{
|
||||
public string ColumnName => "version";
|
||||
|
||||
public string TableName => "versioninfo";
|
||||
|
||||
public string DescriptionColumnName => "description";
|
||||
|
||||
public string AppliedOnColumnName => "appliedon";
|
||||
|
||||
public string UniqueIndexName => "uc_version";
|
||||
|
||||
public object ApplicationContext { get; set; }
|
||||
|
||||
public bool OwnsSchema => true;
|
||||
|
||||
public string SchemaName => "public";
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
using Rsdo.Concordancer.Data.Extensions;
|
||||
using Rsdo.Concordancer.Data.Framework;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Migrations.Initial.Corpus;
|
||||
|
||||
[CorpusDb]
|
||||
[Migration(20211103214800)]
|
||||
public class Mig20211103214800_Initial : ForwardOnlyMigration
|
||||
{
|
||||
public override void Up()
|
||||
{
|
||||
// Text
|
||||
Create.Table(nameof(Text))
|
||||
.WithEntity()
|
||||
.WithColumn(nameof(Text.Author))
|
||||
.AsString(255)
|
||||
.Nullable()
|
||||
.WithColumn(nameof(Text.SourceFile))
|
||||
.AsString(255)
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Text.Title))
|
||||
.AsString(255)
|
||||
.Nullable()
|
||||
.WithColumn(nameof(Text.Year))
|
||||
.AsInt16()
|
||||
.Nullable();
|
||||
|
||||
Create.Index().OnTable(nameof(Text)).OnColumn(nameof(Text.Id)).Ascending().WithOptions().Unique();
|
||||
|
||||
// Paragraph
|
||||
Create.Table(nameof(Paragraph))
|
||||
.WithEntity()
|
||||
.WithColumn(nameof(Paragraph.RecordOrder))
|
||||
.AsInt32()
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Paragraph.TextAutoId))
|
||||
.AsInt64()
|
||||
.NotNullable()
|
||||
.ForeignKey(nameof(Text), nameof(Text.AutoId))
|
||||
.OnDelete(Rule.Cascade);
|
||||
|
||||
Create.Index().OnTable(nameof(Paragraph)).OnColumn(nameof(Paragraph.Id)).Ascending().WithOptions().Unique();
|
||||
Create.Index().OnTable(nameof(Paragraph)).OnColumn(nameof(Paragraph.TextAutoId));
|
||||
|
||||
// Sentence
|
||||
Create.Table(nameof(Sentence))
|
||||
.WithEntity()
|
||||
.WithColumn(nameof(Sentence.ParagraphAutoId))
|
||||
.AsInt64()
|
||||
.NotNullable()
|
||||
.ForeignKey(nameof(Paragraph), nameof(Paragraph.AutoId))
|
||||
.OnDelete(Rule.Cascade)
|
||||
.WithColumn(nameof(Sentence.RecordOrder))
|
||||
.AsInt32()
|
||||
.NotNullable();
|
||||
|
||||
Create.Index().OnTable(nameof(Sentence)).OnColumn(nameof(Sentence.Id)).Ascending().WithOptions().Unique();
|
||||
Create.Index().OnTable(nameof(Sentence)).OnColumn(nameof(Sentence.ParagraphAutoId));
|
||||
|
||||
// Token
|
||||
Create.Table(nameof(Token))
|
||||
.WithEntity()
|
||||
.WithColumn(nameof(Token.Form))
|
||||
.AsString(400)
|
||||
.Nullable()
|
||||
.WithColumn(nameof(Token.Lemma))
|
||||
.AsString(400)
|
||||
.Nullable()
|
||||
.WithColumn(nameof(Token.Msd))
|
||||
.AsString(20)
|
||||
.Nullable()
|
||||
.WithColumn(nameof(Token.RecordOrder))
|
||||
.AsInt32()
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Token.SentenceAutoId))
|
||||
.AsInt64()
|
||||
.NotNullable()
|
||||
.ForeignKey(nameof(Sentence), nameof(Sentence.AutoId))
|
||||
.OnDelete(Rule.Cascade)
|
||||
.WithColumn(nameof(Token.TokenOrder))
|
||||
.AsInt32()
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Token.Type))
|
||||
.AsByte()
|
||||
.NotNullable();
|
||||
|
||||
Create.Index().OnTable(nameof(Token)).OnColumn(nameof(Token.Id)).Ascending().WithOptions().Unique();
|
||||
Create.Index().OnTable(nameof(Token)).OnColumn(nameof(Token.SentenceAutoId)).Ascending().OnColumn(nameof(Token.TokenOrder));
|
||||
|
||||
// TermList
|
||||
Create.Table(nameof(TermList)).WithEntity().WithColumn(nameof(Text.SourceFile)).AsString(255).NotNullable();
|
||||
|
||||
// Term
|
||||
Create.Table(nameof(Term))
|
||||
.WithEntity()
|
||||
.WithColumn(nameof(Term.Form))
|
||||
.AsString(400)
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Term.Frequency))
|
||||
.AsInt32()
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Term.Lemma))
|
||||
.AsString(400)
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Term.Msd))
|
||||
.AsString(100)
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Term.TermListAutoId))
|
||||
.AsInt64()
|
||||
.NotNullable()
|
||||
.ForeignKey(nameof(TermList), nameof(TermList.AutoId))
|
||||
.OnDelete(Rule.Cascade)
|
||||
.WithColumn(nameof(Term.Weight))
|
||||
.AsDecimal(16, 15)
|
||||
.NotNullable();
|
||||
|
||||
Create.Index().OnTable(nameof(Term)).OnColumn(nameof(Term.Id)).Ascending().WithOptions().Unique();
|
||||
Create.Index().OnTable(nameof(Term)).OnColumn(nameof(Term.TermListAutoId)).Ascending();
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using FluentMigrator;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
using Rsdo.Concordancer.Data.Extensions;
|
||||
using Rsdo.Concordancer.Data.Framework;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Migrations.Initial.Master;
|
||||
|
||||
[MasterDb]
|
||||
[Migration(20211016212200)]
|
||||
public class Mig20211016212200_Initial : ForwardOnlyMigration
|
||||
{
|
||||
public override void Up()
|
||||
{
|
||||
// Corpus
|
||||
Create.Table(nameof(Core.Entities.Corpus))
|
||||
.WithEntity()
|
||||
.WithColumn(nameof(Core.Entities.Corpus.Description))
|
||||
.AsString(255)
|
||||
.Nullable()
|
||||
.WithColumn(nameof(Core.Entities.Corpus.Status))
|
||||
.AsByte()
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(Core.Entities.Corpus.Title))
|
||||
.AsString(100)
|
||||
.NotNullable();
|
||||
|
||||
// LemmaFormPair
|
||||
Create.Table(nameof(LemmaFormPair))
|
||||
.WithEntity()
|
||||
.WithColumn(nameof(LemmaFormPair.Lemma))
|
||||
.AsString(255)
|
||||
.NotNullable()
|
||||
.WithColumn(nameof(LemmaFormPair.Form))
|
||||
.AsString(255)
|
||||
.NotNullable();
|
||||
Execute.Sql($"CREATE INDEX ix_lemmaformpair_form_lower ON lemmaformpair (lower(form))");
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using FluentMigrator;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
using Rsdo.Concordancer.Data.Framework;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Migrations.v1_0.Corpus;
|
||||
|
||||
[CorpusDb]
|
||||
[Migration(20221015220400)]
|
||||
public class Mig20221015220400_CreateTokenTypeIndex : ForwardOnlyMigration
|
||||
{
|
||||
public override void Up()
|
||||
{
|
||||
var token = nameof(Token).ToLower();
|
||||
var type = nameof(Token.Type).ToLower();
|
||||
var word = (int)TokenType.Word;
|
||||
|
||||
Execute.Sql($"CREATE INDEX ix_{token}_{type}_partial ON {token}({type}) WHERE {type} = {word}");
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using FluentMigrator;
|
||||
using Rsdo.Concordancer.Core.Entities;
|
||||
using Rsdo.Concordancer.Data.Framework;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Migrations.v1_0.Corpus;
|
||||
|
||||
[CorpusDb]
|
||||
[Migration(20221021080000)]
|
||||
public class Mig20221021080000_AddTextAndTermListStatus : ForwardOnlyMigration
|
||||
{
|
||||
public override void Up()
|
||||
{
|
||||
// Text
|
||||
Alter.Table(nameof(Text)).AddColumn(nameof(Text.Status)).AsByte().Nullable();
|
||||
Execute.Sql($"UPDATE {nameof(Text)} SET {nameof(Text.Status)} = {(int)ImportStatus.Active};");
|
||||
Alter.Table(nameof(Text)).AlterColumn(nameof(Text.Status)).AsByte().NotNullable();
|
||||
|
||||
// TermList
|
||||
Alter.Table(nameof(TermList)).AddColumn(nameof(TermList.Status)).AsByte().Nullable();
|
||||
Execute.Sql($"UPDATE {nameof(TermList)} SET {nameof(TermList.Status)} = {(int)ImportStatus.Active};");
|
||||
Alter.Table(nameof(TermList)).AlterColumn(nameof(TermList.Status)).AsByte().NotNullable();
|
||||
}
|
||||
}
|
||||
+7419
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.4.0" />
|
||||
<PackageReference Include="FluentMigrator" Version="3.3.2" />
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="3.3.2" />
|
||||
<PackageReference Include="Npgsql" Version="6.0.7" />
|
||||
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Rsdo.Concordancer.Core\Rsdo.Concordancer.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using FluentMigrator.Runner;
|
||||
using FluentMigrator.Runner.Generators.Postgres;
|
||||
using FluentMigrator.Runner.Initialization;
|
||||
using FluentMigrator.Runner.VersionTableInfo;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Rsdo.Concordancer.Core.Interfaces;
|
||||
using Rsdo.Concordancer.Data.CompositionRoot;
|
||||
using Rsdo.Concordancer.Data.Framework;
|
||||
|
||||
namespace Rsdo.Concordancer.Data.Services;
|
||||
|
||||
public class DatabaseMigrationRunner : IDatabaseMigrationRunner
|
||||
{
|
||||
public void MigrateUp(string connectionString, MigrationTag migrationTag)
|
||||
{
|
||||
var serviceProvider = CreateServices(connectionString, migrationTag);
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
UpdateDatabase(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
private static IServiceProvider CreateServices(string connectionString, MigrationTag migrationTag)
|
||||
{
|
||||
return new ServiceCollection().AddFluentMigratorCore()
|
||||
.ConfigureRunner(rb => rb.AddPostgres11_0().WithGlobalConnectionString(connectionString).ScanIn(typeof(DataModule).Assembly).For.Migrations())
|
||||
.AddLogging(lb => lb.AddFluentMigratorConsole())
|
||||
.Configure<RunnerOptions>(
|
||||
opt => opt.Tags = new[]
|
||||
{
|
||||
migrationTag.ToString(),
|
||||
})
|
||||
.AddScoped<PostgresQuoter, NoQuoteQuoter>()
|
||||
.AddScoped<IVersionTableMetaData, VersionInfoTableMetadata>()
|
||||
.BuildServiceProvider(false);
|
||||
}
|
||||
|
||||
private static void UpdateDatabase(IServiceProvider serviceProvider)
|
||||
{
|
||||
var runner = serviceProvider.GetRequiredService<IMigrationRunner>();
|
||||
runner.MigrateUp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Reflection;
|
||||
using Autofac;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Rsdo.Concordancer.Core.Search;
|
||||
using Rsdo.Concordancer.Core.Search.Aggregations;
|
||||
using Rsdo.Concordancer.Infrastructure.Search;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Aggregations;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
using Module = Autofac.Module;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.CompositionRoot;
|
||||
|
||||
public class InfrastructureModule : Module
|
||||
{
|
||||
private Assembly InfrastructureAssembly => GetType().Assembly;
|
||||
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
RegisterConfiguration(builder);
|
||||
RegisterSearch(builder);
|
||||
}
|
||||
|
||||
private static void RegisterConfiguration(ContainerBuilder builder)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder().AddEnvironmentVariables().Build();
|
||||
builder.RegisterInstance(configuration).As<IConfiguration>().SingleInstance();
|
||||
}
|
||||
|
||||
private void RegisterSearch(ContainerBuilder builder)
|
||||
{
|
||||
// Search engine
|
||||
builder.RegisterType<ElasticSearchEngine>().As<ISearchEngine>().SingleInstance();
|
||||
|
||||
// Elastic client
|
||||
builder.RegisterType<ElasticClientFactory>().As<IElasticClientFactory>().SingleInstance();
|
||||
builder.Register(
|
||||
c =>
|
||||
{
|
||||
var factory = c.Resolve<IElasticClientFactory>();
|
||||
return factory.Get();
|
||||
})
|
||||
.SingleInstance();
|
||||
|
||||
// Index providers
|
||||
builder.RegisterType<IndexProviderFactory>().As<IIndexProviderFactory>().SingleInstance();
|
||||
builder.RegisterAssemblyTypes(InfrastructureAssembly).AsClosedTypesOf(typeof(IIndexProvider<>)).AsImplementedInterfaces().SingleInstance();
|
||||
|
||||
// Add and delete records
|
||||
builder.RegisterType<AddRecordsHandlerFactory>().As<IAddRecordsHandlerFactory>().SingleInstance();
|
||||
builder.RegisterType<DeleteRecordsHandlerFactory>().As<IDeleteRecordsHandlerFactory>().SingleInstance();
|
||||
builder.RegisterAssemblyTypes(InfrastructureAssembly).AsClosedTypesOf(typeof(IAddRecordsHandler<>)).AsImplementedInterfaces().SingleInstance();
|
||||
builder.RegisterAssemblyTypes(InfrastructureAssembly).AsClosedTypesOf(typeof(IDeleteRecordsHandler<>)).AsImplementedInterfaces().SingleInstance();
|
||||
|
||||
// Query builders
|
||||
builder.RegisterType<QueryBuilderFactory>().As<IQueryBuilderFactory>().SingleInstance();
|
||||
builder.RegisterAssemblyTypes(InfrastructureAssembly).AsClosedTypesOf(typeof(IQueryBuilder<>)).AsImplementedInterfaces().SingleInstance();
|
||||
|
||||
// Aggregations
|
||||
builder.RegisterType<AggregatorFactory>().As<IAggregatorFactory>().SingleInstance();
|
||||
builder.RegisterType<TextAggregator>().Keyed<IAggregator>(AggregationType.Text).SingleInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenSearch.Client;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Extensions;
|
||||
|
||||
public static class ElasticQueryExtensions
|
||||
{
|
||||
public static QueryContainer ToBooleanAndQuery(this List<QueryContainer> queries)
|
||||
{
|
||||
return queries.Count switch
|
||||
{
|
||||
0 => null,
|
||||
1 => queries[0],
|
||||
_ => new BoolQuery()
|
||||
{
|
||||
Must = queries,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static QueryContainer ToBooleanOrQuery(this List<QueryContainer> queries)
|
||||
{
|
||||
return queries.Count switch
|
||||
{
|
||||
0 => null,
|
||||
1 => queries[0],
|
||||
_ => new BoolQuery()
|
||||
{
|
||||
Should = queries,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
||||
<PackageReference Include="OpenSearch.Client" Version="1.1.0" />
|
||||
<PackageReference Include="Rsdo.StyleCop" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Rsdo.Concordancer.Core\Rsdo.Concordancer.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Model;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
|
||||
public class AddConcordanceRecordsHandler : BaseAddRecordsHandler<Concordance, EsConcordance>
|
||||
{
|
||||
public AddConcordanceRecordsHandler(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory)
|
||||
: base(client, indexProviderFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override EsConcordance ConvertEntity(Concordance entity)
|
||||
{
|
||||
return new EsConcordance
|
||||
{
|
||||
Id = entity.Token.Id,
|
||||
TextId = entity.TextId,
|
||||
Token = ConvertToken(entity.Token),
|
||||
TokenLeft1 = ConvertToken(entity.TokenLeft1),
|
||||
TokenLeft2 = ConvertToken(entity.TokenLeft2),
|
||||
TokenLeft3 = ConvertToken(entity.TokenLeft3),
|
||||
TokenLeft4 = ConvertToken(entity.TokenLeft4),
|
||||
TokenLeft5 = ConvertToken(entity.TokenLeft5),
|
||||
TokenLeft6 = ConvertToken(entity.TokenLeft6),
|
||||
TokenLeft7 = ConvertToken(entity.TokenLeft7),
|
||||
TokenLeft8 = ConvertToken(entity.TokenLeft8),
|
||||
TokenLeft9 = ConvertToken(entity.TokenLeft9),
|
||||
TokenLeft10 = ConvertToken(entity.TokenLeft10),
|
||||
TokenRight1 = ConvertToken(entity.TokenRight1),
|
||||
TokenRight2 = ConvertToken(entity.TokenRight2),
|
||||
TokenRight3 = ConvertToken(entity.TokenRight3),
|
||||
TokenRight4 = ConvertToken(entity.TokenRight4),
|
||||
TokenRight5 = ConvertToken(entity.TokenRight5),
|
||||
TokenRight6 = ConvertToken(entity.TokenRight6),
|
||||
TokenRight7 = ConvertToken(entity.TokenRight7),
|
||||
TokenRight8 = ConvertToken(entity.TokenRight8),
|
||||
TokenRight9 = ConvertToken(entity.TokenRight9),
|
||||
TokenRight10 = ConvertToken(entity.TokenRight10),
|
||||
};
|
||||
}
|
||||
|
||||
private static EsToken ConvertToken(Rsdo.Concordancer.Core.Entities.Token token)
|
||||
{
|
||||
if (token == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EsToken
|
||||
{
|
||||
Form = token.Form,
|
||||
FormLower = token.Form?.ToLower(),
|
||||
Lemma = token.Lemma,
|
||||
LemmaLower = token.Lemma?.ToLower(),
|
||||
Msd = token.Msd,
|
||||
};
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using Autofac;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
|
||||
public class AddRecordsHandlerFactory : IAddRecordsHandlerFactory
|
||||
{
|
||||
private readonly ILifetimeScope lifetimeScope;
|
||||
|
||||
public AddRecordsHandlerFactory(ILifetimeScope lifetimeScope)
|
||||
{
|
||||
this.lifetimeScope = lifetimeScope;
|
||||
}
|
||||
|
||||
public IAddRecordsHandler<TEntity> GetHandler<TEntity>()
|
||||
{
|
||||
return lifetimeScope.Resolve<IAddRecordsHandler<TEntity>>();
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
using Term = Rsdo.Concordancer.Core.Entities.Term;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
|
||||
public class AddTermRecordsHandler : BaseAddRecordsHandler<Term, EsTerm>
|
||||
{
|
||||
public AddTermRecordsHandler(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory)
|
||||
: base(client, indexProviderFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override EsTerm ConvertEntity(Term entity)
|
||||
{
|
||||
// Tokenize form, lemma and msd
|
||||
var forms = entity.Form.Split(' ');
|
||||
var lemmas = entity.Lemma.Split(' ');
|
||||
var msds = entity.Msd.Split(' ');
|
||||
|
||||
return new EsTerm
|
||||
{
|
||||
Id = entity.Id,
|
||||
Frequency = entity.Frequency,
|
||||
Token = GetToken(0),
|
||||
TokenRight1 = GetToken(1),
|
||||
TokenRight2 = GetToken(2),
|
||||
TokenRight3 = GetToken(3),
|
||||
TokenRight4 = GetToken(4),
|
||||
Weight = entity.Weight,
|
||||
};
|
||||
|
||||
EsToken GetToken(int index)
|
||||
{
|
||||
if (index > forms.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EsToken()
|
||||
{
|
||||
Form = GetValue(forms, index),
|
||||
Lemma = GetValue(lemmas, index),
|
||||
Msd = GetValue(msds, index),
|
||||
FormLower = GetValue(forms, index)?.ToLower(),
|
||||
LemmaLower = GetValue(lemmas, index)?.ToLower(),
|
||||
};
|
||||
}
|
||||
|
||||
string GetValue(string[] values, int index)
|
||||
{
|
||||
return index < values.Length ? values[index] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Extensions;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
|
||||
public abstract class BaseAddRecordsHandler<TEntity, TElasticEntity> : IAddRecordsHandler<TEntity>
|
||||
where TElasticEntity : class
|
||||
{
|
||||
private readonly IOpenSearchClient client;
|
||||
private readonly IIndexProviderFactory indexProviderFactory;
|
||||
|
||||
protected BaseAddRecordsHandler(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory)
|
||||
{
|
||||
this.client = client;
|
||||
this.indexProviderFactory = indexProviderFactory;
|
||||
}
|
||||
|
||||
public async Task Add(IEnumerable<TEntity> entities)
|
||||
{
|
||||
// Get index provider
|
||||
var indexProvider = indexProviderFactory.GetProvider<TElasticEntity>();
|
||||
|
||||
// Get index name
|
||||
var indexName = indexProvider.IndexName;
|
||||
|
||||
// Create request
|
||||
var request = new BulkRequest(indexName)
|
||||
{
|
||||
Operations = new List<IBulkOperation>(),
|
||||
Timeout = TimeSpan.FromMinutes(5),
|
||||
};
|
||||
|
||||
// Convert entities to elastic entities
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
var elasticEntity = ConvertEntity(entity);
|
||||
request.Operations.Add(new BulkIndexOperation<TElasticEntity>(elasticEntity));
|
||||
}
|
||||
|
||||
// Get response
|
||||
var response = await client.BulkAsync(request);
|
||||
response.ThrowIfInvalid();
|
||||
}
|
||||
|
||||
protected abstract TElasticEntity ConvertEntity(TEntity entity);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
|
||||
public interface IAddRecordsHandler<TEntity>
|
||||
{
|
||||
Task Add(IEnumerable<TEntity> entities);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
|
||||
public interface IAddRecordsHandlerFactory
|
||||
{
|
||||
IAddRecordsHandler<TEntity> GetHandler<TEntity>();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Autofac.Features.Indexed;
|
||||
using Rsdo.Concordancer.Core.Search.Aggregations;
|
||||
using Rsdo.Concordancer.ServiceModel.Types;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Aggregations;
|
||||
|
||||
public class AggregatorFactory : IAggregatorFactory
|
||||
{
|
||||
private readonly IIndex<AggregationType, IAggregator> aggregators;
|
||||
|
||||
public AggregatorFactory(IIndex<AggregationType, IAggregator> aggregators)
|
||||
{
|
||||
this.aggregators = aggregators;
|
||||
}
|
||||
|
||||
public IAggregator GetAggregator(AggregationType aggregationType)
|
||||
{
|
||||
return aggregators[aggregationType];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Search.Aggregations;
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Extensions;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Aggregations;
|
||||
|
||||
public abstract class BaseAggregator : IAggregator
|
||||
{
|
||||
private readonly IOpenSearchClient client;
|
||||
private readonly IIndexProviderFactory indexProviderFactory;
|
||||
private readonly IQueryBuilderFactory queryBuilderFactory;
|
||||
|
||||
protected BaseAggregator(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
|
||||
{
|
||||
this.client = client;
|
||||
this.indexProviderFactory = indexProviderFactory;
|
||||
this.queryBuilderFactory = queryBuilderFactory;
|
||||
}
|
||||
|
||||
protected abstract string FieldName { get; }
|
||||
|
||||
public async Task<IDictionary<string, long>> Get<TQuery>(TQuery query)
|
||||
where TQuery : Query
|
||||
{
|
||||
// Get elastic query
|
||||
var queryBuilder = queryBuilderFactory.GetBuilder<TQuery>();
|
||||
var elasticQuery = queryBuilder.Build(query);
|
||||
|
||||
// Get and execute search request
|
||||
var searchRequest = GetSearchRequest(elasticQuery);
|
||||
var response = await client.SearchAsync<EsConcordance>(searchRequest);
|
||||
response.ThrowIfInvalid();
|
||||
|
||||
// Read response
|
||||
return ReadAggregations(response);
|
||||
}
|
||||
|
||||
private static IDictionary<string, long> ReadAggregations(ISearchResponse<EsConcordance> response)
|
||||
{
|
||||
var terms = response.Aggregations.Terms("agg");
|
||||
return terms?.Buckets?.ToDictionary(x => x.Key, x => x.DocCount ?? 0);
|
||||
}
|
||||
|
||||
private SearchRequest GetSearchRequest(QueryContainer query)
|
||||
{
|
||||
var indexProvider = indexProviderFactory.GetProvider<EsConcordance>();
|
||||
var indexName = indexProvider.IndexName;
|
||||
|
||||
return new SearchRequest(indexName)
|
||||
{
|
||||
From = 0,
|
||||
Size = 0,
|
||||
Query = query,
|
||||
Aggregations = new AggregationDictionary()
|
||||
{
|
||||
{
|
||||
"agg", new TermsAggregation("terms")
|
||||
{
|
||||
Field = FieldName,
|
||||
Size = 100,
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Aggregations;
|
||||
|
||||
public class TextAggregator : BaseAggregator
|
||||
{
|
||||
public TextAggregator(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory, IQueryBuilderFactory queryBuilderFactory)
|
||||
: base(client, indexProviderFactory, queryBuilderFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string FieldName => "textId";
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Constants;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Client;
|
||||
|
||||
public class ElasticClientFactory : IElasticClientFactory
|
||||
{
|
||||
private readonly IConfiguration configuration;
|
||||
|
||||
public ElasticClientFactory(IConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public IOpenSearchClient Get()
|
||||
{
|
||||
var connectionString = configuration[ConfigurationKey.Search.ElasticConnectionString];
|
||||
var connectionSettings = new ConnectionSettings(new Uri(connectionString)).SniffOnStartup(false).RequestTimeout(TimeSpan.FromSeconds(30));
|
||||
#if DEBUG
|
||||
connectionSettings.EnableDebugMode().IncludeServerStackTraceOnError(false);
|
||||
#endif
|
||||
return new OpenSearchClient(connectionSettings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using OpenSearch.Client;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Client;
|
||||
|
||||
public interface IElasticClientFactory
|
||||
{
|
||||
IOpenSearchClient Get();
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Extensions;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
|
||||
public abstract class BaseDeleteRecordsHandler<TEntity, TElasticEntity> : IDeleteRecordsHandler<TEntity>
|
||||
where TElasticEntity : class
|
||||
{
|
||||
private readonly IOpenSearchClient client;
|
||||
private readonly IIndexProviderFactory indexProviderFactory;
|
||||
|
||||
protected BaseDeleteRecordsHandler(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory)
|
||||
{
|
||||
this.client = client;
|
||||
this.indexProviderFactory = indexProviderFactory;
|
||||
}
|
||||
|
||||
public async Task Delete(IEnumerable<Guid> entityIds)
|
||||
{
|
||||
// Get index provider
|
||||
var indexProvider = indexProviderFactory.GetProvider<TElasticEntity>();
|
||||
|
||||
// Get index name
|
||||
var indexName = indexProvider.IndexName;
|
||||
|
||||
// Create request
|
||||
var request = new BulkRequest(indexName)
|
||||
{
|
||||
Operations = new List<IBulkOperation>(),
|
||||
Timeout = TimeSpan.FromMinutes(5),
|
||||
};
|
||||
|
||||
// Convert entities to elastic entities
|
||||
foreach (var entityId in entityIds)
|
||||
{
|
||||
request.Operations.Add(new BulkDeleteDescriptor<TElasticEntity>().Id(entityId));
|
||||
}
|
||||
|
||||
// Get response
|
||||
var response = await client.BulkAsync(request);
|
||||
response.ThrowIfInvalid();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Model;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
|
||||
public class DeleteConcordanceRecordsHandler : BaseDeleteRecordsHandler<Concordance, EsConcordance>
|
||||
{
|
||||
public DeleteConcordanceRecordsHandler(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory)
|
||||
: base(client, indexProviderFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using Autofac;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
|
||||
public class DeleteRecordsHandlerFactory : IDeleteRecordsHandlerFactory
|
||||
{
|
||||
private readonly ILifetimeScope lifetimeScope;
|
||||
|
||||
public DeleteRecordsHandlerFactory(ILifetimeScope lifetimeScope)
|
||||
{
|
||||
this.lifetimeScope = lifetimeScope;
|
||||
}
|
||||
|
||||
public IDeleteRecordsHandler<TEntity> GetHandler<TEntity>()
|
||||
{
|
||||
return lifetimeScope.Resolve<IDeleteRecordsHandler<TEntity>>();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
using Term = Rsdo.Concordancer.Core.Entities.Term;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
|
||||
public class DeleteTermRecordsHandler : BaseDeleteRecordsHandler<Term, EsTerm>
|
||||
{
|
||||
public DeleteTermRecordsHandler(IOpenSearchClient client, IIndexProviderFactory indexProviderFactory)
|
||||
: base(client, indexProviderFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
|
||||
public interface IDeleteRecordsHandler<TEntity>
|
||||
{
|
||||
Task Delete(IEnumerable<Guid> entityIds);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
|
||||
public interface IDeleteRecordsHandlerFactory
|
||||
{
|
||||
IDeleteRecordsHandler<TEntity> GetHandler<TEntity>();
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Model;
|
||||
using Rsdo.Concordancer.Core.Search;
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.AddRecordsHandlers;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.DeleteRecordsHandlers;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Extensions;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
using Term = Rsdo.Concordancer.Core.Entities.Term;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search;
|
||||
|
||||
public class ElasticSearchEngine : ISearchEngine
|
||||
{
|
||||
private readonly IAddRecordsHandlerFactory addRecordsHandlerFactory;
|
||||
private readonly IOpenSearchClient client;
|
||||
private readonly IDeleteRecordsHandlerFactory deleteRecordsHandlerFactory;
|
||||
private readonly IIndexProviderFactory indexProviderFactory;
|
||||
private readonly IQueryBuilderFactory queryBuilderFactory;
|
||||
|
||||
public ElasticSearchEngine(
|
||||
IAddRecordsHandlerFactory addRecordsHandlerFactory,
|
||||
IOpenSearchClient client,
|
||||
IDeleteRecordsHandlerFactory deleteRecordsHandlerFactory,
|
||||
IIndexProviderFactory indexProviderFactory,
|
||||
IQueryBuilderFactory queryBuilderFactory)
|
||||
{
|
||||
this.addRecordsHandlerFactory = addRecordsHandlerFactory;
|
||||
this.client = client;
|
||||
this.deleteRecordsHandlerFactory = deleteRecordsHandlerFactory;
|
||||
this.indexProviderFactory = indexProviderFactory;
|
||||
this.queryBuilderFactory = queryBuilderFactory;
|
||||
}
|
||||
|
||||
public Task Add<TEntity>(IEnumerable<TEntity> entities)
|
||||
{
|
||||
var handler = addRecordsHandlerFactory.GetHandler<TEntity>();
|
||||
return handler.Add(entities);
|
||||
}
|
||||
|
||||
public async Task Commit()
|
||||
{
|
||||
foreach (var provider in indexProviderFactory.GetAllProviders())
|
||||
{
|
||||
await provider.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateSchema()
|
||||
{
|
||||
foreach (var provider in indexProviderFactory.GetAllProviders())
|
||||
{
|
||||
await provider.Create();
|
||||
}
|
||||
}
|
||||
|
||||
public Task Delete<TEntity>(IEnumerable<Guid> entityIds)
|
||||
{
|
||||
var handler = deleteRecordsHandlerFactory.GetHandler<TEntity>();
|
||||
return handler.Delete(entityIds);
|
||||
}
|
||||
|
||||
public async Task DeleteSchema()
|
||||
{
|
||||
foreach (var provider in indexProviderFactory.GetAllProviders())
|
||||
{
|
||||
if (await provider.Exists())
|
||||
{
|
||||
await provider.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<QueryResult> Search<TEntity, TQuery>(TQuery query)
|
||||
where TEntity : class
|
||||
where TQuery : Query
|
||||
{
|
||||
// Get search request
|
||||
var searchRequest = GetSearchRequest<TEntity, TQuery>(query);
|
||||
|
||||
// Execute search
|
||||
var searchResponse = await client.SearchAsync<TEntity>(searchRequest);
|
||||
searchResponse.ThrowIfInvalid();
|
||||
|
||||
// Return result
|
||||
return new QueryResult()
|
||||
{
|
||||
EntityIds = searchResponse.Hits?.Select(h => h.Id).ToList(),
|
||||
Total = searchResponse.Total,
|
||||
};
|
||||
}
|
||||
|
||||
private IIndexProvider GetIndexProvider<TEntity>()
|
||||
{
|
||||
// ToDo: this should be done with some kind of mapper
|
||||
if (typeof(TEntity) == typeof(Concordance))
|
||||
{
|
||||
return indexProviderFactory.GetProvider<EsConcordance>();
|
||||
}
|
||||
|
||||
if (typeof(TEntity) == typeof(Term))
|
||||
{
|
||||
return indexProviderFactory.GetProvider<EsTerm>();
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException($"Entity type {typeof(TEntity)} is not supported as a search type in Elastic!");
|
||||
}
|
||||
|
||||
private SearchRequest GetSearchRequest<TEntity, TQuery>(TQuery query)
|
||||
where TQuery : Query
|
||||
{
|
||||
// Get query builder
|
||||
var queryBuilder = queryBuilderFactory.GetBuilder<TQuery>();
|
||||
|
||||
// Build elastic query
|
||||
var elasticQuery = queryBuilder.Build(query);
|
||||
|
||||
// Get index provider
|
||||
var indexProvider = GetIndexProvider<TEntity>();
|
||||
|
||||
// Return search request
|
||||
return new SearchRequest(indexProvider.IndexName)
|
||||
{
|
||||
From = query.From,
|
||||
Size = query.Size,
|
||||
TrackTotalHits = true,
|
||||
Query = elasticQuery,
|
||||
};
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using OpenSearch.Client;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Extensions;
|
||||
|
||||
public static class ElasticResponseExtensions
|
||||
{
|
||||
public static void ThrowIfInvalid(this IResponse response)
|
||||
{
|
||||
if (!response.IsValid)
|
||||
{
|
||||
throw new Exception($"Invalid Elastic response: {response.DebugInformation}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Threading.Tasks;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Framework;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Extensions;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
public abstract class BaseIndexProvider<TModel> : IIndexProvider<TModel>
|
||||
where TModel : class
|
||||
{
|
||||
private readonly IOpenSearchClient client;
|
||||
|
||||
protected BaseIndexProvider(IOpenSearchClient client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public string IndexName => $"rsdo_{typeof(TModel).Name.ToLower()}_{CurrentContext.Current.CorpusId:N}";
|
||||
|
||||
public async Task Create()
|
||||
{
|
||||
var response = await client.Indices.CreateAsync(
|
||||
IndexName,
|
||||
c => c.Settings(s => s.NumberOfShards(3).NumberOfReplicas(0)).Map(ms => ms.AutoMap<TModel>().SourceField(sf => sf.Enabled(false))));
|
||||
response.ThrowIfInvalid();
|
||||
}
|
||||
|
||||
public async Task Delete()
|
||||
{
|
||||
var response = await client.Indices.DeleteAsync(IndexName);
|
||||
response.ThrowIfInvalid();
|
||||
}
|
||||
|
||||
public async Task<bool> Exists()
|
||||
{
|
||||
return (await client.Indices.ExistsAsync(IndexName)).Exists;
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
var response = await client.Indices.RefreshAsync(IndexName);
|
||||
response.ThrowIfInvalid();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
public class ConcordanceIndexProvider : BaseIndexProvider<EsConcordance>
|
||||
{
|
||||
public ConcordanceIndexProvider(IOpenSearchClient client)
|
||||
: base(client)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
public interface IIndexProvider<TModel> : IIndexProvider
|
||||
{
|
||||
}
|
||||
|
||||
public interface IIndexProvider
|
||||
{
|
||||
string IndexName { get; }
|
||||
|
||||
Task Create();
|
||||
|
||||
Task Delete();
|
||||
|
||||
Task<bool> Exists();
|
||||
|
||||
Task Refresh();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
public interface IIndexProviderFactory
|
||||
{
|
||||
IEnumerable<IIndexProvider> GetAllProviders();
|
||||
|
||||
IIndexProvider<TModel> GetProvider<TModel>();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using Autofac;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
public class IndexProviderFactory : IIndexProviderFactory
|
||||
{
|
||||
private readonly ILifetimeScope lifetimeScope;
|
||||
|
||||
public IndexProviderFactory(ILifetimeScope lifetimeScope)
|
||||
{
|
||||
this.lifetimeScope = lifetimeScope;
|
||||
}
|
||||
|
||||
public IEnumerable<IIndexProvider> GetAllProviders()
|
||||
{
|
||||
return lifetimeScope.Resolve<IEnumerable<IIndexProvider>>();
|
||||
}
|
||||
|
||||
public IIndexProvider<TModel> GetProvider<TModel>()
|
||||
{
|
||||
return lifetimeScope.Resolve<IIndexProvider<TModel>>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Indexes;
|
||||
|
||||
public class TermIndexProvider : BaseIndexProvider<EsTerm>
|
||||
{
|
||||
public TermIndexProvider(IOpenSearchClient client)
|
||||
: base(client)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using OpenSearch.Client;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
|
||||
public class EsConcordance
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Keyword]
|
||||
public Guid TextId { get; set; }
|
||||
|
||||
public EsToken Token { get; set; }
|
||||
|
||||
public EsToken TokenLeft1 { get; set; }
|
||||
|
||||
public EsToken TokenLeft2 { get; set; }
|
||||
|
||||
public EsToken TokenLeft3 { get; set; }
|
||||
|
||||
public EsToken TokenLeft4 { get; set; }
|
||||
|
||||
public EsToken TokenLeft5 { get; set; }
|
||||
|
||||
public EsToken TokenLeft6 { get; set; }
|
||||
|
||||
public EsToken TokenLeft7 { get; set; }
|
||||
|
||||
public EsToken TokenLeft8 { get; set; }
|
||||
|
||||
public EsToken TokenLeft9 { get; set; }
|
||||
|
||||
public EsToken TokenLeft10 { get; set; }
|
||||
|
||||
public EsToken TokenRight1 { get; set; }
|
||||
|
||||
public EsToken TokenRight2 { get; set; }
|
||||
|
||||
public EsToken TokenRight3 { get; set; }
|
||||
|
||||
public EsToken TokenRight4 { get; set; }
|
||||
|
||||
public EsToken TokenRight5 { get; set; }
|
||||
|
||||
public EsToken TokenRight6 { get; set; }
|
||||
|
||||
public EsToken TokenRight7 { get; set; }
|
||||
|
||||
public EsToken TokenRight8 { get; set; }
|
||||
|
||||
public EsToken TokenRight9 { get; set; }
|
||||
|
||||
public EsToken TokenRight10 { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
|
||||
public class EsTerm
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public int Frequency { get; set; }
|
||||
|
||||
public EsToken Token { get; set; }
|
||||
|
||||
public EsToken TokenRight1 { get; set; }
|
||||
|
||||
public EsToken TokenRight2 { get; set; }
|
||||
|
||||
public EsToken TokenRight3 { get; set; }
|
||||
|
||||
public EsToken TokenRight4 { get; set; }
|
||||
|
||||
public decimal Weight { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using OpenSearch.Client;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.Model;
|
||||
|
||||
public class EsToken
|
||||
{
|
||||
[Keyword]
|
||||
public string Form { get; set; }
|
||||
|
||||
[Keyword]
|
||||
public string FormLower { get; set; }
|
||||
|
||||
[Keyword]
|
||||
public string Lemma { get; set; }
|
||||
|
||||
[Keyword]
|
||||
public string LemmaLower { get; set; }
|
||||
|
||||
[Keyword]
|
||||
public string Msd { get; set; }
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Extensions;
|
||||
using Rsdo.Concordancer.Core.Search.Queries.Concordances;
|
||||
using Rsdo.Concordancer.Infrastructure.Extensions;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
|
||||
public class ConcordancesQueryBuilder : IQueryBuilder<ConcordancesQuery>
|
||||
{
|
||||
public QueryContainer Build(ConcordancesQuery query)
|
||||
{
|
||||
// Get filter queries
|
||||
var queries = GetFilterQueries(query);
|
||||
|
||||
// Main word query
|
||||
queries.Add(GetMainWordEsQuery(query.MainWord));
|
||||
|
||||
// Words in context queries
|
||||
if (!query.WordsInContext.IsNullOrEmpty())
|
||||
{
|
||||
queries.AddRange(query.WordsInContext.Select(GetWordInContextEsQuery));
|
||||
}
|
||||
|
||||
// Merge queries
|
||||
var mergedQuery = queries.ToBooleanAndQuery();
|
||||
|
||||
// Check if we should return random rows (used in export)
|
||||
if (query.ReturnRandomRows)
|
||||
{
|
||||
mergedQuery = new FunctionScoreQuery()
|
||||
{
|
||||
Query = mergedQuery,
|
||||
Functions = new List<IScoreFunction>()
|
||||
{
|
||||
new RandomScoreFunction()
|
||||
{
|
||||
Seed = DateTime.Now.Ticks,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return mergedQuery;
|
||||
}
|
||||
|
||||
private static List<QueryContainer> GetFilterQueries(ConcordancesQuery query)
|
||||
{
|
||||
var queries = new List<QueryContainer>();
|
||||
|
||||
if (!query.TextIds.IsNullOrEmpty())
|
||||
{
|
||||
queries.Add(
|
||||
new TermsQuery()
|
||||
{
|
||||
Field = "textId",
|
||||
Terms = query.TextIds.Cast<object>(),
|
||||
});
|
||||
}
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
private static QueryContainer GetMainWordEsQuery(SearchedMainWordQuery mainWordQuery)
|
||||
{
|
||||
return GetWordEsQuery(mainWordQuery, GetTokenField(0));
|
||||
}
|
||||
|
||||
private static QueryContainer GetWordEsQuery(SearchedWordQuery wordQuery, string tokenField)
|
||||
{
|
||||
var queries = new List<QueryContainer>();
|
||||
|
||||
if (!string.IsNullOrEmpty(wordQuery.Form))
|
||||
{
|
||||
queries.Add(
|
||||
new TermQuery()
|
||||
{
|
||||
Field = $"{tokenField}.formLower",
|
||||
Value = wordQuery.Form.ToLower(),
|
||||
});
|
||||
}
|
||||
|
||||
if (!wordQuery.Lemmas.IsNullOrEmpty())
|
||||
{
|
||||
queries.Add(
|
||||
new TermsQuery()
|
||||
{
|
||||
Field = $"{tokenField}.lemma",
|
||||
Terms = wordQuery.Lemmas,
|
||||
});
|
||||
}
|
||||
|
||||
if (queries.Count == 0)
|
||||
{
|
||||
return new MatchNoneQuery();
|
||||
}
|
||||
|
||||
if (!wordQuery.Msds.IsNullOrEmpty())
|
||||
{
|
||||
queries.Add(
|
||||
new TermsQuery()
|
||||
{
|
||||
Field = $"{tokenField}.msd",
|
||||
Terms = wordQuery.Msds,
|
||||
});
|
||||
}
|
||||
|
||||
return queries.ToBooleanAndQuery();
|
||||
}
|
||||
|
||||
private static QueryContainer GetWordInContextEsQuery(SearchedWordInContextQuery searchedWord)
|
||||
{
|
||||
var queries = new List<QueryContainer>();
|
||||
foreach (var position in searchedWord.Positions)
|
||||
{
|
||||
var positionQuery = GetWordEsQuery(searchedWord, GetTokenField(position));
|
||||
queries.Add(positionQuery);
|
||||
}
|
||||
|
||||
var query = queries.ToBooleanOrQuery();
|
||||
return searchedWord.ConditionType == ServiceModel.Types.ConditionType.Is ? query : !query;
|
||||
}
|
||||
|
||||
private static string GetTokenField(int position)
|
||||
{
|
||||
return position switch
|
||||
{
|
||||
< 0 => $"tokenLeft{-position}",
|
||||
> 0 => $"tokenRight{position}",
|
||||
_ => "token",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
|
||||
public interface IQueryBuilder<TQuery>
|
||||
where TQuery : Query
|
||||
{
|
||||
QueryContainer Build(TQuery query);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
|
||||
public interface IQueryBuilderFactory
|
||||
{
|
||||
IQueryBuilder<TQuery> GetBuilder<TQuery>()
|
||||
where TQuery : Query;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Autofac;
|
||||
using Rsdo.Concordancer.Core.Search.Queries;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
|
||||
public class QueryBuilderFactory : IQueryBuilderFactory
|
||||
{
|
||||
private readonly ILifetimeScope lifetimeScope;
|
||||
|
||||
public QueryBuilderFactory(ILifetimeScope lifetimeScope)
|
||||
{
|
||||
this.lifetimeScope = lifetimeScope;
|
||||
}
|
||||
|
||||
public IQueryBuilder<TQuery> GetBuilder<TQuery>()
|
||||
where TQuery : Query
|
||||
{
|
||||
return lifetimeScope.Resolve<IQueryBuilder<TQuery>>();
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenSearch.Client;
|
||||
using Rsdo.Concordancer.Core.Extensions;
|
||||
using Rsdo.Concordancer.Core.Search.Queries.TermLists;
|
||||
using Rsdo.Concordancer.Infrastructure.Extensions;
|
||||
|
||||
namespace Rsdo.Concordancer.Infrastructure.Search.QueryBuilders;
|
||||
|
||||
public class TermListQueryBuilder : IQueryBuilder<TermListQuery>
|
||||
{
|
||||
public QueryContainer Build(TermListQuery query)
|
||||
{
|
||||
var queries = new List<QueryContainer>();
|
||||
|
||||
// Search for words in all possible positions
|
||||
for (var i = 0; i <= 5 - query.Words.Count; i++)
|
||||
{
|
||||
var termQuery = new List<QueryContainer>();
|
||||
for (var j = 0; j < query.Words.Count; j++)
|
||||
{
|
||||
var word = query.Words[j];
|
||||
var tokenField = GetTokenField(i + j);
|
||||
termQuery.Add(GetWordEsQuery(word, tokenField));
|
||||
}
|
||||
|
||||
queries.Add(termQuery.ToBooleanAndQuery());
|
||||
}
|
||||
|
||||
return queries.ToBooleanOrQuery();
|
||||
}
|
||||
|
||||
private static QueryContainer GetWordEsQuery(SearchedTermQuery wordQuery, string tokenField)
|
||||
{
|
||||
var queries = new List<QueryContainer>();
|
||||
|
||||
if (!string.IsNullOrEmpty(wordQuery.Form))
|
||||
{
|
||||
queries.Add(
|
||||
new TermQuery()
|
||||
{
|
||||
Field = $"{tokenField}.formLower",
|
||||
Value = wordQuery.Form.ToLower(),
|
||||
});
|
||||
}
|
||||
|
||||
if (!wordQuery.Lemmas.IsNullOrEmpty())
|
||||
{
|
||||
queries.Add(
|
||||
new TermsQuery()
|
||||
{
|
||||
Field = $"{tokenField}.lemma",
|
||||
Terms = wordQuery.Lemmas,
|
||||
});
|
||||
}
|
||||
|
||||
return queries.Count == 0 ? new MatchNoneQuery() : queries.ToBooleanAndQuery();
|
||||
}
|
||||
|
||||
private static string GetTokenField(int position)
|
||||
{
|
||||
return position switch
|
||||
{
|
||||
> 0 => $"tokenRight{position}",
|
||||
_ => "token",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Rsdo.Concordancer.ServiceModel.Interfaces;
|
||||
|
||||
public interface IHaveCorpusId
|
||||
{
|
||||
public Guid CorpusId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Rsdo.Concordancer.ServiceModel.Interfaces;
|
||||
|
||||
public interface IPagedResponse
|
||||
{
|
||||
int Offset { get; }
|
||||
|
||||
long Total { get; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Rsdo.Concordancer.ServiceModel.Interfaces;
|
||||
|
||||
public interface IPagedSearch
|
||||
{
|
||||
int From { get; }
|
||||
|
||||
int Size { get; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user