diff --git a/CHANGELOG.md b/CHANGELOG.md index b81dbf8c4e..6b5c3b9fac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Moved `OpenSearch.Client` request classes into their respective namespaces to match those in `OpenSearch.Net` ([#200](https://github.com/opensearch-project/opensearch-net/pull/200), [#202](https://github.com/opensearch-project/opensearch-net/pull/202), [#203](https://github.com/opensearch-project/opensearch-net/pull/203), [#205](https://github.com/opensearch-project/opensearch-net/pull/205), [#206](https://github.com/opensearch-project/opensearch-net/pull/206), [#207](https://github.com/opensearch-project/opensearch-net/pull/207), [#208](https://github.com/opensearch-project/opensearch-net/pull/208), [#209](https://github.com/opensearch-project/opensearch-net/pull/209)) - Removed support for the `net461` target ([#256](https://github.com/opensearch-project/opensearch-net/pull/256)) +### Fixed +- Fix highlight max_analyzer_offset field name to match with the one introduced in OpenSearch 2.2.0 ([#322](https://github.com/opensearch-project/opensearch-net/pull/322)) + ### Dependencies - Bumps `Microsoft.CodeAnalysis.CSharp` from 4.2.0 to 4.6.0 - Bumps `Microsoft.TestPlatform.ObjectModel` from 17.5.0 to 17.6.3 @@ -88,4 +91,4 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) [Unreleased]: https://github.com/opensearch-project/opensearch-net/compare/v1.4.0...main [1.4.0]: https://github.com/opensearch-project/opensearch-net/compare/v1.3.0...v1.4.0 -[1.3.0]: https://github.com/opensearch-project/opensearch-net/compare/v1.2.0...v1.3.0 \ No newline at end of file +[1.3.0]: https://github.com/opensearch-project/opensearch-net/compare/v1.2.0...v1.3.0 diff --git a/src/OpenSearch.Client/Search/Search/Highlighting/Highlight.cs b/src/OpenSearch.Client/Search/Search/Highlighting/Highlight.cs index f164dd6b3a..94b93bbb04 100644 --- a/src/OpenSearch.Client/Search/Search/Highlighting/Highlight.cs +++ b/src/OpenSearch.Client/Search/Search/Highlighting/Highlight.cs @@ -107,8 +107,9 @@ public interface IHighlight /// If this setting is set to a non-negative value, the highlighting stops at this defined maximum limit, and the /// rest of the text is not processed, thus not highlighted and no error is returned. /// - [DataMember(Name ="max_analyzed_offset")] - int? MaxAnalyzedOffset { get; set; } + /// Introduced in OpenSearch 2.2 + [DataMember(Name ="max_analyzer_offset")] + int? MaxAnalyzerOffset { get; set; } [DataMember(Name ="max_fragment_length")] int? MaxFragmentLength { get; set; } @@ -210,7 +211,7 @@ public class Highlight : IHighlight public QueryContainer HighlightQuery { get; set; } /// - public int? MaxAnalyzedOffset { get; set; } + public int? MaxAnalyzerOffset { get; set; } /// public int? MaxFragmentLength { get; set; } @@ -258,7 +259,7 @@ public class HighlightDescriptor : DescriptorBase, IHi int? IHighlight.FragmentOffset { get; set; } int? IHighlight.FragmentSize { get; set; } QueryContainer IHighlight.HighlightQuery { get; set; } - int? IHighlight.MaxAnalyzedOffset { get; set; } + int? IHighlight.MaxAnalyzerOffset { get; set; } int? IHighlight.MaxFragmentLength { get; set; } int? IHighlight.NoMatchSize { get; set; } int? IHighlight.NumberOfFragments { get; set; } @@ -323,8 +324,8 @@ public HighlightDescriptor HighlightQuery(Func, Q /// public HighlightDescriptor BoundaryMaxScan(int? boundaryMaxScan) => Assign(boundaryMaxScan, (a, v) => a.BoundaryMaxScan = v); - /// - public HighlightDescriptor MaxAnalyzedOffset(int? maxAnalyzedOffset) => Assign(maxAnalyzedOffset, (a, v) => a.MaxAnalyzedOffset = v); + /// + public HighlightDescriptor MaxAnalyzerOffset(int? maxAnalyzerOffset) => Assign(maxAnalyzerOffset, (a, v) => a.MaxAnalyzerOffset = v); /// public HighlightDescriptor MaxFragmentLength(int? maxFragmentLength) => Assign(maxFragmentLength, (a, v) => a.MaxFragmentLength = v); diff --git a/src/OpenSearch.Client/Search/Search/Highlighting/HighlightField.cs b/src/OpenSearch.Client/Search/Search/Highlighting/HighlightField.cs index 4e78137ed6..8702926752 100644 --- a/src/OpenSearch.Client/Search/Search/Highlighting/HighlightField.cs +++ b/src/OpenSearch.Client/Search/Search/Highlighting/HighlightField.cs @@ -116,6 +116,15 @@ public interface IHighlightField [DataMember(Name = "matched_fields")] Fields MatchedFields { get; set; } + + /// + /// If this setting is set to a non-negative value, the highlighting stops at this defined maximum limit, and the + /// rest of the text is not processed, thus not highlighted and no error is returned. + /// + /// Introduced in OpenSearch 2.2 + [DataMember(Name = "max_analyzer_offset")] + int? MaxAnalyzerOffset { get; set; } + [DataMember(Name = "max_fragment_length")] int? MaxFragmentLength { get; set; } @@ -191,6 +200,7 @@ public interface IHighlightField /// [DataMember(Name = "type")] Union Type { get; set; } + } public class HighlightField : IHighlightField @@ -228,6 +238,9 @@ public class HighlightField : IHighlightField /// public Fields MatchedFields { get; set; } + /// + public int? MaxAnalyzerOffset { get; set; } + /// public int? MaxFragmentLength { get; set; } @@ -273,6 +286,9 @@ public class HighlightFieldDescriptor : DescriptorBase MatchedFields(Func, IProm public HighlightFieldDescriptor HighlightQuery(Func, QueryContainer> querySelector) => Assign(querySelector, (a, v) => a.HighlightQuery = v?.Invoke(new QueryContainerDescriptor())); + /// + public HighlightFieldDescriptor MaxAnalyzerOffset(int? maxAnalyzerOffset) => Assign(maxAnalyzerOffset, (a, v) => a.MaxAnalyzerOffset = v); + /// public HighlightFieldDescriptor MaxFragmentLength(int? maxFragmentLength) => Assign(maxFragmentLength, (a, v) => a.MaxFragmentLength = v); diff --git a/tests/Tests/Search/Request/HighlightingUsageTestsWithMaxAnalyzerOffset.cs b/tests/Tests/Search/Request/HighlightingUsageTestsWithMaxAnalyzerOffset.cs new file mode 100644 index 0000000000..63faf46347 --- /dev/null +++ b/tests/Tests/Search/Request/HighlightingUsageTestsWithMaxAnalyzerOffset.cs @@ -0,0 +1,316 @@ +/* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +*/ +/* +* Modifications Copyright OpenSearch Contributors. See +* GitHub history for details. +* +* Licensed to Elasticsearch B.V. under one or more contributor +* license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright +* ownership. Elasticsearch B.V. licenses this file to you under +* the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using OpenSearch.Client; +using OpenSearch.OpenSearch.Xunit.XunitPlumbing; +using Tests.Core.Extensions; +using Tests.Core.ManagedOpenSearch.Clusters; +using Tests.Domain; +using Tests.Framework.EndpointTests; +using Tests.Framework.EndpointTests.TestState; +using Xunit; + +namespace Tests.Search.Request +{ + /** + * Allows to highlight search results on one or more fields. + * The implementation uses either the lucene `highlighter` or `fast-vector-highlighter`. + * + * See the OpenSearch documentation on {ref_current}/search-request-body.html#request-body-search-highlighting[highlighting] for more detail. + */ + [SkipVersion("<2.2.0", "MaxAnalyzerOffset field was introduced in 2.2.0")] + public class HighlightingUsageTestsWithMaxAnalyzerOffset : HighlightingUsageTests + { + public HighlightingUsageTestsWithMaxAnalyzerOffset(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override object ExpectJson => + new + { + query = new + { + match = new Dictionary + { + { + "name.standard", new Dictionary + { + { "query", "Upton Sons Shield Rice Rowe Roberts" } + } + } + } + }, + highlight = new + { + pre_tags = new[] { "" }, + post_tags = new[] { "" }, + encoder = "html", + highlight_query = new + { + match = new Dictionary + { + { + "name.standard", new Dictionary + { + { "query", "Upton Sons Shield Rice Rowe Roberts" } + } + } + } + }, + fields = new Dictionary + { + { + "name.standard", new Dictionary + { + { "type", "plain" }, + { "force_source", true }, + { "fragment_size", 150 }, + { "fragmenter", "span" }, + { "number_of_fragments", 3 }, + { "no_match_size", 150 }, + { "max_analyzer_offset", 500 } + } + }, + { + "leadDeveloper.firstName", new Dictionary + { + { "type", "fvh" }, + { "phrase_limit", 10 }, + { "boundary_max_scan", 50 }, + { "pre_tags", new[] { "" } }, + { "post_tags", new[] { "" } }, + { + "highlight_query", new Dictionary + { + { + "match", new Dictionary + { + { + "leadDeveloper.firstName", new Dictionary + { + { "query", "Kurt Edgardo Naomi Dariana Justice Felton" } + } + } + } + } + } + } + } + }, + { + "leadDeveloper.lastName", new Dictionary + { + { "type", "unified" }, + { "pre_tags", new[] { "" } }, + { "post_tags", new[] { "" } }, + { + "highlight_query", new Dictionary + { + { + "match", new Dictionary + { + { + "leadDeveloper.lastName", new Dictionary + { + { "query", LastNameSearch } + } + } + } + } + } + } + } + } + }, + max_analyzer_offset = 1_000_000 + } + }; + + protected override Func, ISearchRequest> Fluent => s => s + + + .Query(q => q + .Match(m => m + .Field(f => f.Name.Suffix("standard")) + .Query("Upton Sons Shield Rice Rowe Roberts") + ) + ) + .Highlight(h => h + .PreTags("") + .PostTags("") + .Encoder(HighlighterEncoder.Html) + .HighlightQuery(q => q + .Match(m => m + .Field(f => f.Name.Suffix("standard")) + .Query("Upton Sons Shield Rice Rowe Roberts") + ) + ) + .Fields( + fs => fs + .Field(p => p.Name.Suffix("standard")) + .Type("plain") + .ForceSource() + .FragmentSize(150) + .Fragmenter(HighlighterFragmenter.Span) + .NumberOfFragments(3) + .NoMatchSize(150) + .MaxAnalyzerOffset(500), + fs => fs + .Field(p => p.LeadDeveloper.FirstName) + .Type(HighlighterType.Fvh) + .PreTags("") + .PostTags("") + .BoundaryMaxScan(50) + .PhraseLimit(10) + .HighlightQuery(q => q + .Match(m => m + .Field(p => p.LeadDeveloper.FirstName) + .Query("Kurt Edgardo Naomi Dariana Justice Felton") + ) + ), + fs => fs + .Field(p => p.LeadDeveloper.LastName) + .Type(HighlighterType.Unified) + .PreTags("") + .PostTags("") + .HighlightQuery(q => q + .Match(m => m + .Field(p => p.LeadDeveloper.LastName) + .Query(LastNameSearch) + ) + ) + ) + .MaxAnalyzerOffset(1_000_000) //the default value + ); + + protected override SearchRequest Initializer => + new SearchRequest + { + Query = new MatchQuery + { + Query = "Upton Sons Shield Rice Rowe Roberts", + Field = "name.standard" + }, + Highlight = new Highlight + { + PreTags = new[] { "" }, + PostTags = new[] { "" }, + Encoder = HighlighterEncoder.Html, + HighlightQuery = new MatchQuery + { + Query = "Upton Sons Shield Rice Rowe Roberts", + Field = "name.standard" + }, + Fields = new Dictionary + { + { + "name.standard", new HighlightField + { + Type = HighlighterType.Plain, + ForceSource = true, + FragmentSize = 150, + Fragmenter = HighlighterFragmenter.Span, + NumberOfFragments = 3, + NoMatchSize = 150, + MaxAnalyzerOffset = 500 + } + }, + { + "leadDeveloper.firstName", new HighlightField + { + Type = "fvh", + PhraseLimit = 10, + BoundaryMaxScan = 50, + PreTags = new[] { "" }, + PostTags = new[] { "" }, + HighlightQuery = new MatchQuery + { + Field = "leadDeveloper.firstName", + Query = "Kurt Edgardo Naomi Dariana Justice Felton" + } + } + }, + { + "leadDeveloper.lastName", new HighlightField + { + Type = HighlighterType.Unified, + PreTags = new[] { "" }, + PostTags = new[] { "" }, + HighlightQuery = new MatchQuery + { + Field = "leadDeveloper.lastName", + Query = LastNameSearch + } + } + } + }, + MaxAnalyzerOffset = 1_000_000 + } + }; + + protected override void ExpectResponse(ISearchResponse response) + { + response.ShouldBeValid(); + + foreach (var highlightsInEachHit in response.Hits.Select(d => d.Highlight)) + { + foreach (var highlightField in highlightsInEachHit) + { + if (highlightField.Key == "name.standard") + { + foreach (var highlight in highlightField.Value) + { + highlight.Should().Contain(""); + highlight.Should().Contain(""); + } + } + else if (highlightField.Key == "leadDeveloper.firstName") + { + foreach (var highlight in highlightField.Value) + { + highlight.Should().Contain(""); + highlight.Should().Contain(""); + } + } + else if (highlightField.Key == "leadDeveloper.lastName") + { + foreach (var highlight in highlightField.Value) + { + highlight.Should().Contain(""); + highlight.Should().Contain(""); + } + } + else + Assert.True(false, $"highlights contains unexpected key {highlightField.Key}"); + } + } + } + } +}