mirror of https://github.com/Squidex/squidex.git
13 changed files with 324 additions and 63 deletions
@ -0,0 +1,78 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Elasticsearch.Net; |
|||
|
|||
namespace Squidex.Extensions.Text.ElasticSearch |
|||
{ |
|||
internal class ElasticSearchClient : IElasticSearchClient |
|||
{ |
|||
private readonly IElasticLowLevelClient elasticSearch; |
|||
|
|||
public ElasticSearchClient(string configurationString) |
|||
{ |
|||
var config = new ConnectionConfiguration(new Uri(configurationString)); |
|||
|
|||
elasticSearch = new ElasticLowLevelClient(config); |
|||
} |
|||
|
|||
public async Task CreateIndexAsync<T>(string indexName, T request, |
|||
CancellationToken ct) |
|||
{ |
|||
var result = await elasticSearch.Indices.PutMappingAsync<StringResponse>(indexName, CreatePost(request), ctx: ct); |
|||
|
|||
if (!result.Success) |
|||
{ |
|||
throw new InvalidOperationException($"Failed with ${result.Body}", result.OriginalException); |
|||
} |
|||
} |
|||
|
|||
public async Task BulkAsync<T>(List<T> requests, |
|||
CancellationToken ct) |
|||
{ |
|||
var result = await elasticSearch.BulkAsync<StringResponse>(CreatePost(requests), ctx: ct); |
|||
|
|||
if (!result.Success) |
|||
{ |
|||
throw new InvalidOperationException($"Failed with ${result.Body}", result.OriginalException); |
|||
} |
|||
} |
|||
|
|||
public async Task<List<dynamic>> SearchAsync<T>(string indexName, T request, |
|||
CancellationToken ct) |
|||
{ |
|||
var result = await elasticSearch.SearchAsync<DynamicResponse>(indexName, CreatePost(request), ctx: ct); |
|||
|
|||
if (!result.Success) |
|||
{ |
|||
throw result.OriginalException; |
|||
} |
|||
|
|||
var hits = new List<dynamic>(); |
|||
|
|||
foreach (var item in result.Body.hits.hits) |
|||
{ |
|||
if (item != null) |
|||
{ |
|||
hits.Add(item); |
|||
} |
|||
} |
|||
|
|||
return hits; |
|||
} |
|||
|
|||
private static PostData CreatePost<T>(List<T> requests) |
|||
{ |
|||
return PostData.MultiJson(requests.OfType<object>()); |
|||
} |
|||
|
|||
private static PostData CreatePost<T>(T data) |
|||
{ |
|||
return new SerializableData<T>(data); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Extensions.Text.ElasticSearch |
|||
{ |
|||
public interface IElasticSearchClient |
|||
{ |
|||
Task CreateIndexAsync<T>(string indexName, T request, |
|||
CancellationToken ct); |
|||
|
|||
Task BulkAsync<T>(List<T> requests, |
|||
CancellationToken ct); |
|||
|
|||
Task<List<dynamic>> SearchAsync<T>(string indexName, T request, |
|||
CancellationToken ct); |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using OpenSearch.Net; |
|||
|
|||
namespace Squidex.Extensions.Text.ElasticSearch |
|||
{ |
|||
public sealed class OpenSearchClient : IElasticSearchClient |
|||
{ |
|||
private readonly IOpenSearchLowLevelClient openSearch; |
|||
|
|||
public OpenSearchClient(string configurationString) |
|||
{ |
|||
var config = new ConnectionConfiguration(new Uri(configurationString)); |
|||
|
|||
openSearch = new OpenSearchLowLevelClient(config); |
|||
} |
|||
|
|||
public async Task CreateIndexAsync<T>(string indexName, T request, |
|||
CancellationToken ct) |
|||
{ |
|||
var result = await openSearch.Indices.PutMappingAsync<StringResponse>(indexName, CreatePost(request), ctx: ct); |
|||
|
|||
if (!result.Success) |
|||
{ |
|||
throw new InvalidOperationException($"Failed with ${result.Body}", result.OriginalException); |
|||
} |
|||
} |
|||
|
|||
public async Task BulkAsync<T>(List<T> requests, |
|||
CancellationToken ct) |
|||
{ |
|||
var result = await openSearch.BulkAsync<StringResponse>(CreatePost(requests), ctx: ct); |
|||
|
|||
if (!result.Success) |
|||
{ |
|||
throw new InvalidOperationException($"Failed with ${result.Body}", result.OriginalException); |
|||
} |
|||
} |
|||
|
|||
public async Task<List<dynamic>> SearchAsync<T>(string indexName, T request, |
|||
CancellationToken ct) |
|||
{ |
|||
var result = await openSearch.SearchAsync<DynamicResponse>(indexName, CreatePost(request), ctx: ct); |
|||
|
|||
if (!result.Success) |
|||
{ |
|||
throw result.OriginalException; |
|||
} |
|||
|
|||
var hits = new List<dynamic>(); |
|||
|
|||
foreach (var item in result.Body.hits.hits) |
|||
{ |
|||
if (item != null) |
|||
{ |
|||
hits.Add(item); |
|||
} |
|||
} |
|||
|
|||
return hits; |
|||
} |
|||
|
|||
private static PostData CreatePost<T>(List<T> requests) |
|||
{ |
|||
return PostData.MultiJson(requests.OfType<object>()); |
|||
} |
|||
|
|||
private static PostData CreatePost<T>(T data) |
|||
{ |
|||
return new SerializableData<T>(data); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.TestHelpers; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Extensions.Text.ElasticSearch; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
public sealed class ElasticSearchTextIndexFixture : IAsyncLifetime |
|||
{ |
|||
public ElasticSearchTextIndex Index { get; } |
|||
|
|||
public ElasticSearchTextIndexFixture() |
|||
{ |
|||
Index = new ElasticSearchTextIndex( |
|||
new ElasticSearchClient(TestConfig.Configuration["elastic:configuration"]), |
|||
TestConfig.Configuration["elastic:indexName"], |
|||
TestUtils.DefaultSerializer); |
|||
} |
|||
|
|||
public Task InitializeAsync() |
|||
{ |
|||
return Index.InitializeAsync(default); |
|||
} |
|||
|
|||
public Task DisposeAsync() |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Xunit; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
[Trait("Category", "Dependencies")] |
|||
public class ElasticSearchTextIndexTests : TextIndexerTestsBase, IClassFixture<ElasticSearchTextIndexFixture> |
|||
{ |
|||
public override bool SupportsGeo => true; |
|||
|
|||
public override int WaitAfterUpdate => 2000; |
|||
|
|||
public ElasticSearchTextIndexFixture _ { get; } |
|||
|
|||
public ElasticSearchTextIndexTests(ElasticSearchTextIndexFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
public override ITextIndex CreateIndex() |
|||
{ |
|||
return _.Index; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_retrieve_english_stopword_only_for_german_query() |
|||
{ |
|||
await CreateTextAsync(ids1[0], "de", "and y"); |
|||
await CreateTextAsync(ids2[0], "en", "and y"); |
|||
|
|||
await SearchText(expected: ids2, text: "und"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_retrieve_german_stopword_only_for_english_query() |
|||
{ |
|||
await CreateTextAsync(ids1[0], "de", "and und"); |
|||
await CreateTextAsync(ids2[0], "en", "and und"); |
|||
|
|||
await SearchText(expected: ids1, text: "and"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_index_cjk_content_and_retrieve() |
|||
{ |
|||
await CreateTextAsync(ids1[0], "zh", "東京大学"); |
|||
|
|||
await SearchText(expected: ids1, text: "東京"); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue