diff --git a/src/core/Rsdo.Concordancer.Core/Extensions/StringExtensions.cs b/src/core/Rsdo.Concordancer.Core/Extensions/StringExtensions.cs new file mode 100644 index 0000000..62b92f9 --- /dev/null +++ b/src/core/Rsdo.Concordancer.Core/Extensions/StringExtensions.cs @@ -0,0 +1,9 @@ +namespace Rsdo.Concordancer.Core.Extensions; + +public static class StringExtensions +{ + public static bool IsWildcardSearch(this string value) + { + return !string.IsNullOrEmpty(value) && (value.Contains('*') || value.Contains('?')); + } +} \ No newline at end of file diff --git a/src/core/Rsdo.Concordancer.Infrastructure/Extensions/ElasticQueryExtensions.cs b/src/core/Rsdo.Concordancer.Infrastructure/Extensions/ElasticQueryExtensions.cs index 674afd1..239a183 100644 --- a/src/core/Rsdo.Concordancer.Infrastructure/Extensions/ElasticQueryExtensions.cs +++ b/src/core/Rsdo.Concordancer.Infrastructure/Extensions/ElasticQueryExtensions.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.Linq; using OpenSearch.Client; +using Rsdo.Concordancer.Core.Extensions; namespace Rsdo.Concordancer.Infrastructure.Extensions; @@ -30,4 +32,36 @@ public static class ElasticQueryExtensions }, }; } + + public static QueryContainer ToQuery(this string value, string field) + { + if (value.IsWildcardSearch()) + { + return new WildcardQuery() + { + Field = field, + Value = value, + }; + } + + return new TermQuery() + { + Field = field, + Value = value, + }; + } + + public static QueryContainer ToQuery(this List values, string field) + { + if (values.Any(v => v.IsWildcardSearch())) + { + return values.Select(value => value.ToQuery(field)).ToList().ToBooleanOrQuery(); + } + + return new TermsQuery() + { + Field = field, + Terms = values, + }; + } } \ No newline at end of file diff --git a/src/core/Rsdo.Concordancer.Infrastructure/Search/QueryBuilders/TermListQueryBuilder.cs b/src/core/Rsdo.Concordancer.Infrastructure/Search/QueryBuilders/TermListQueryBuilder.cs index 806cbda..36886e9 100644 --- a/src/core/Rsdo.Concordancer.Infrastructure/Search/QueryBuilders/TermListQueryBuilder.cs +++ b/src/core/Rsdo.Concordancer.Infrastructure/Search/QueryBuilders/TermListQueryBuilder.cs @@ -35,22 +35,12 @@ public class TermListQueryBuilder : IQueryBuilder if (!string.IsNullOrEmpty(wordQuery.Form)) { - queries.Add( - new TermQuery() - { - Field = $"{tokenField}.formLower", - Value = wordQuery.Form.ToLower(), - }); + queries.Add(wordQuery.Form.ToLower().ToQuery($"{tokenField}.formLower")); } if (!wordQuery.Lemmas.IsNullOrEmpty()) { - queries.Add( - new TermsQuery() - { - Field = $"{tokenField}.lemma", - Terms = wordQuery.Lemmas, - }); + queries.Add(wordQuery.Lemmas.ToQuery($"{tokenField}.lemma")); } return queries.Count == 0 ? new MatchNoneQuery() : queries.ToBooleanAndQuery(); diff --git a/src/core/Rsdo.Concordancer.Services/CompositionRoot/ServicesModule.cs b/src/core/Rsdo.Concordancer.Services/CompositionRoot/ServicesModule.cs index c371d2f..66b0061 100644 --- a/src/core/Rsdo.Concordancer.Services/CompositionRoot/ServicesModule.cs +++ b/src/core/Rsdo.Concordancer.Services/CompositionRoot/ServicesModule.cs @@ -1,10 +1,14 @@ using System; using System.Reflection; using Autofac; +using Autofac.Core; using FluentValidation; using Microsoft.Extensions.Caching.Memory; using Rsdo.Concordancer.Core.Interfaces; +using Rsdo.Concordancer.Core.Search.Queries.Concordances; +using Rsdo.Concordancer.Core.Search.Queries.TermLists; using Rsdo.Concordancer.ServiceModel.Requests.Concordances; +using Rsdo.Concordancer.ServiceModel.Requests.TermLists; using Rsdo.Concordancer.ServiceModel.Types; using Rsdo.Concordancer.Services.Framework; using Rsdo.Concordancer.Services.Framework.BulkLoaders; @@ -15,6 +19,8 @@ using Rsdo.Concordancer.Services.Framework.Decorators; using Rsdo.Concordancer.Services.Search.Aggregations; using Rsdo.Concordancer.Services.Search.AlternateSearches; using Rsdo.Concordancer.Services.Search.QueryFactories; +using Rsdo.Concordancer.Services.Search.QueryFactories.Concordances; +using Rsdo.Concordancer.Services.Search.QueryFactories.TermLists; using Rsdo.Concordancer.Services.Services.InputQueryParser; using Rsdo.Concordancer.Services.Services.LemmatizationService; using Rsdo.Concordancer.Services.Services.ParagraphService; @@ -83,7 +89,8 @@ public class ServicesModule : Module builder.RegisterType().Keyed(TokenizerType.Default).SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerLifetimeScope(); - builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().Keyed(LemmatizationType.Simple).InstancePerLifetimeScope(); + builder.RegisterType().Keyed(LemmatizationType.Wildcard).InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerLifetimeScope(); } @@ -103,7 +110,33 @@ public class ServicesModule : Module private void RegisterSearch(ContainerBuilder builder) { // Query factories - builder.RegisterAssemblyTypes(ServicesAssembly).AsClosedTypesOf(typeof(IQueryFactory<,>)).AsImplementedInterfaces().InstancePerLifetimeScope(); + var wildcardLemmatizationServiceParameter = new ResolvedParameter( + (p, ctx) => p.ParameterType == typeof(ILemmatizationService), + (p, ctx) => ctx.ResolveKeyed(LemmatizationType.Wildcard)); + var simpleLemmatizationServiceParameter = new ResolvedParameter( + (p, ctx) => p.ParameterType == typeof(ILemmatizationService), + (p, ctx) => ctx.ResolveKeyed(LemmatizationType.Simple)); + + builder.RegisterType() + .As>() + .WithParameter(simpleLemmatizationServiceParameter) + .InstancePerLifetimeScope(); + builder.RegisterType() + .As>() + .WithParameter(simpleLemmatizationServiceParameter) + .InstancePerLifetimeScope(); + builder.RegisterType() + .As>() + .WithParameter(simpleLemmatizationServiceParameter) + .InstancePerLifetimeScope(); + builder.RegisterType() + .As>() + .WithParameter(wildcardLemmatizationServiceParameter) + .InstancePerLifetimeScope(); + builder.RegisterType() + .As>() + .WithParameter(wildcardLemmatizationServiceParameter) + .InstancePerLifetimeScope(); // Aggregations builder.RegisterType().As().InstancePerLifetimeScope(); @@ -112,6 +145,7 @@ public class ServicesModule : Module // Alternate searches builder.RegisterType() .As>() + .WithParameter(simpleLemmatizationServiceParameter) .InstancePerLifetimeScope(); } } \ No newline at end of file diff --git a/src/core/Rsdo.Concordancer.Services/Services/InputQueryParser/InputQueryParser.cs b/src/core/Rsdo.Concordancer.Services/Services/InputQueryParser/InputQueryParser.cs index 213ea9a..7c78f0c 100644 --- a/src/core/Rsdo.Concordancer.Services/Services/InputQueryParser/InputQueryParser.cs +++ b/src/core/Rsdo.Concordancer.Services/Services/InputQueryParser/InputQueryParser.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Autofac.Features.Indexed; +using Rsdo.Concordancer.Core.Extensions; using Rsdo.Concordancer.ServiceModel.Requests.Concordances; using Rsdo.Concordancer.ServiceModel.Types; using Rsdo.Concordancer.Services.Services.TokenizerService; @@ -21,6 +22,12 @@ public class InputQueryParser : IInputQueryParser public async Task> Tokenize(string query) { + if (query.IsWildcardSearch()) + { + // Classla tokenizer doesn't support wildcards, so use default tokenizer + return await tokenizers[TokenizerType.Default].Tokenize(query); + } + try { return await tokenizers[TokenizerType.Classla].Tokenize(query); diff --git a/src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/LemmatizationType.cs b/src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/LemmatizationType.cs new file mode 100644 index 0000000..82e0a0b --- /dev/null +++ b/src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/LemmatizationType.cs @@ -0,0 +1,7 @@ +namespace Rsdo.Concordancer.Services.Services.LemmatizationService; + +public enum LemmatizationType +{ + Simple, + Wildcard, +} \ No newline at end of file diff --git a/src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/LemmatizationService.cs b/src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/SimpleLemmatizationService.cs similarity index 71% rename from src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/LemmatizationService.cs rename to src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/SimpleLemmatizationService.cs index db25879..40cf37a 100644 --- a/src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/LemmatizationService.cs +++ b/src/core/Rsdo.Concordancer.Services/Services/LemmatizationService/SimpleLemmatizationService.cs @@ -7,17 +7,18 @@ using Rsdo.Concordancer.Services.Framework.DbContext; namespace Rsdo.Concordancer.Services.Services.LemmatizationService; -public class LemmatizationService : ILemmatizationService +public class SimpleLemmatizationService : ILemmatizationService { private readonly MasterDbContext dbContext; - public LemmatizationService(MasterDbContext dbContext) + public SimpleLemmatizationService(MasterDbContext dbContext) { this.dbContext = dbContext; } - public async Task> GetLemmas(string form) + public virtual async Task> GetLemmas(string form) { + // Retrieving lemmas without support for wildcard search var lemmas = await dbContext.LemmaFormPair.Where(f => f.Form.ToLower() == form.ToLower()).Select(f => f.Lemma).Distinct().ToListAsync(); if (lemmas.IsNullOrEmpty()) {