diff --git a/src/Nest/QueryDsl/FullText/Intervals/IntervalsFuzzy.cs b/src/Nest/QueryDsl/FullText/Intervals/IntervalsFuzzy.cs new file mode 100644 index 00000000000..a9da1ccbc90 --- /dev/null +++ b/src/Nest/QueryDsl/FullText/Intervals/IntervalsFuzzy.cs @@ -0,0 +1,107 @@ +using System; +using System.Linq.Expressions; +using System.Runtime.Serialization; + +namespace Nest +{ + /// + /// The fuzzy rule matches terms that are similar to the provided term, within an edit distance defined by Fuzziness. + /// If the fuzzy expansion matches more than 128 terms, Elasticsearch returns an error. + /// + /// Available in Elasticsearch 7.6.0+ + /// + [ReadAs(typeof(IntervalsFuzzy))] + public interface IIntervalsFuzzy : IIntervalsNoFilter + { + /// + /// Analyzer used to normalize the term. Defaults to the top-level field's analyzer. + /// + [DataMember(Name = "analyzer")] + string Analyzer { get; set; } + + /// + /// Number of beginning characters left unchanged when creating expansions. Defaults to 0. + /// + [DataMember(Name = "prefix_length")] + int? PrefixLength { get; set; } + + /// + /// Indicates whether edits include transpositions of two adjacent characters (ab → ba). Defaults to true. + /// + [DataMember(Name = "transpositions")] + bool? Transpositions { get; set; } + + /// + /// Maximum edit distance allowed for matching. See Fuzziness for valid values and more information. + /// Defaults to . + /// + [DataMember(Name = "fuzziness")] + Fuzziness Fuzziness { get; set; } + + /// + /// The term to match. + /// + [DataMember(Name = "term")] + string Term { get; set; } + + /// + /// If specified, then match intervals from this field rather than the top-level field. + /// The term is normalized using the search analyzer from this field, + /// unless analyzer is specified separately. + /// + [DataMember(Name = "use_field")] + Field UseField { get; set; } + } + + /// + public class IntervalsFuzzy : IntervalsNoFilterBase, IIntervalsFuzzy + { + internal override void WrapInContainer(IIntervalsContainer container) => container.Fuzzy = this; + + /// + public string Analyzer { get; set; } + /// + public int? PrefixLength { get; set; } + /// + public bool? Transpositions { get; set; } + /// + public Fuzziness Fuzziness { get; set; } + /// + public string Term { get; set; } + /// + public Field UseField { get; set; } + } + + /// + public class IntervalsFuzzyDescriptor : DescriptorBase, IIntervalsFuzzy + { + string IIntervalsFuzzy.Analyzer { get; set; } + int? IIntervalsFuzzy.PrefixLength { get; set; } + bool? IIntervalsFuzzy.Transpositions { get; set; } + Fuzziness IIntervalsFuzzy.Fuzziness { get; set; } + string IIntervalsFuzzy.Term { get; set; } + Field IIntervalsFuzzy.UseField { get; set; } + + /// + public IntervalsFuzzyDescriptor Analyzer(string analyzer) => Assign(analyzer, (a, v) => a.Analyzer = v); + + /// + public IntervalsFuzzyDescriptor PrefixLength(int? prefixLength) => Assign(prefixLength, (a, v) => a.PrefixLength = v); + + /// + public IntervalsFuzzyDescriptor Transpositions(bool? transpositions = true) => Assign(transpositions, (a, v) => a.Transpositions = v); + + /// + public IntervalsFuzzyDescriptor Fuzziness(Fuzziness fuzziness) => Assign(fuzziness, (a, v) => a.Fuzziness = v); + + /// + public IntervalsFuzzyDescriptor Term(string term) => Assign(term, (a, v) => a.Term = v); + + + /// + public IntervalsFuzzyDescriptor UseField(Expression> objectPath) => Assign(objectPath, (a, v) => a.UseField = v); + + /// + public IntervalsFuzzyDescriptor UseField(Field useField) => Assign(useField, (a, v) => a.UseField = v); + } +} diff --git a/src/Nest/QueryDsl/FullText/Intervals/IntervalsQuery.cs b/src/Nest/QueryDsl/FullText/Intervals/IntervalsQuery.cs index 30cb8f92abb..39c87227493 100644 --- a/src/Nest/QueryDsl/FullText/Intervals/IntervalsQuery.cs +++ b/src/Nest/QueryDsl/FullText/Intervals/IntervalsQuery.cs @@ -25,6 +25,8 @@ public class IntervalsQuery : FieldNameQueryBase, IIntervalsQuery public IIntervalsAnyOf AnyOf { get; set; } /// public IIntervalsMatch Match { get; set; } + /// + public IIntervalsFuzzy Fuzzy { get; set; } /// public IIntervalsPrefix Prefix { get; set; } @@ -35,7 +37,8 @@ public class IntervalsQuery : FieldNameQueryBase, IIntervalsQuery protected override bool Conditionless => IsConditionless(this); internal static bool IsConditionless(IIntervalsQuery q) => - q.Field.IsConditionless() || q.Match == null && q.AllOf == null && q.AnyOf == null && q.Prefix == null && q.Wildcard == null; + q.Field.IsConditionless() || q.Match == null && q.AllOf == null && q.AnyOf == null && q.Prefix == null && q.Wildcard == null + && q.Fuzzy == null; internal override void InternalWrapInContainer(IQueryContainer container) => container.Intervals = this; } @@ -50,10 +53,15 @@ public class IntervalsQueryDescriptor IIntervalsAllOf IIntervalsContainer.AllOf { get; set; } IIntervalsAnyOf IIntervalsContainer.AnyOf { get; set; } + IIntervalsFuzzy IIntervalsContainer.Fuzzy { get; set; } IIntervalsMatch IIntervalsContainer.Match { get; set; } IIntervalsPrefix IIntervalsContainer.Prefix { get; set; } IIntervalsWildcard IIntervalsContainer.Wildcard { get; set; } + /// + public IntervalsQueryDescriptor Fuzzy(Func selector) => + Assign(selector, (a, v) => a.Fuzzy = v?.Invoke(new IntervalsFuzzyDescriptor())); + /// public IntervalsQueryDescriptor Match(Func selector) => Assign(selector, (a, v) => a.Match = v?.Invoke(new IntervalsMatchDescriptor())); @@ -88,6 +96,10 @@ public interface IIntervalsContainer [DataMember(Name = "any_of")] IIntervalsAnyOf AnyOf { get; set; } + /// + [DataMember(Name = "fuzzy")] + IIntervalsFuzzy Fuzzy { get; set; } + /// [DataMember(Name = "match")] IIntervalsMatch Match { get; set; } @@ -120,6 +132,7 @@ public IntervalsContainer(IntervalsNoFilterBase intervals) IIntervalsAllOf IIntervalsContainer.AllOf { get; set; } IIntervalsAnyOf IIntervalsContainer.AnyOf { get; set; } + IIntervalsFuzzy IIntervalsContainer.Fuzzy { get; set; } IIntervalsMatch IIntervalsContainer.Match { get; set; } IIntervalsPrefix IIntervalsContainer.Prefix { get; set; } IIntervalsWildcard IIntervalsContainer.Wildcard { get; set; } @@ -141,6 +154,10 @@ public class IntervalsDescriptor : IntervalsContainer private IntervalsDescriptor Assign(TValue value, Action assigner) => Fluent.Assign(this, value, assigner); + /// + public IntervalsDescriptor Fuzzy(Func selector) => + Assign(selector, (a, v) => a.Fuzzy = v?.Invoke(new IntervalsFuzzyDescriptor())); + /// public IntervalsDescriptor Match(Func selector) => Assign(selector, (a, v) => a.Match = v?.Invoke(new IntervalsMatchDescriptor())); @@ -222,6 +239,10 @@ public class IntervalsListDescriptor : DescriptorPromiseBase()) { } + /// + public IntervalsListDescriptor Fuzzy(Func selector) => + Assign(selector, (a, v) => a.AddIfNotNull(new IntervalsDescriptor().Fuzzy(v))); + /// public IntervalsListDescriptor Match(Func selector) => Assign(selector, (a, v) => a.AddIfNotNull(new IntervalsDescriptor().Match(v))); diff --git a/tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs b/tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs index 9d71d8fef24..26c7c0b7dc7 100644 --- a/tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs +++ b/tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs @@ -262,4 +262,62 @@ protected override QueryContainer QueryFluent(QueryContainerDescriptor ) ); } + + /**[float] + * === Fuzzy rules + * + * Fuzzy rules can be used to match terms that are similar to the provided term, within an edit distance defined by Fuzziness. + * If the fuzzy expansion matches more than 128 terms, Elasticsearch returns an error. + * + * NOTE: Only available in Elasticsearch 7.6.0+ + */ + [SkipVersion("<7.6.0", "fuzzy rules introduced in 7.6.0")] + public class IntervalsFuzzyUsageTests : QueryDslUsageTestsBase + { + public IntervalsFuzzyUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } + + private static readonly string IntervalsPrefix = Project.First.Description.Split(' ')[0]; + + private static readonly string IntervalsFuzzy = IntervalsPrefix.Substring(0, IntervalsPrefix.Length) + "z"; + + protected override QueryContainer QueryInitializer => new IntervalsQuery + { + Field = Field(p => p.Description), + Name = "named_query", + Boost = 1.1, + Fuzzy = new IntervalsFuzzy + { + Term = IntervalsFuzzy, + Fuzziness = Fuzziness.Auto + } + }; + + protected override object QueryJson => new + { + intervals = new + { + description = new + { + _name = "named_query", + boost = 1.1, + fuzzy = new + { + term = IntervalsFuzzy, + fuzziness = "AUTO" + } + } + } + }; + + protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q + .Intervals(c => c + .Field(p => p.Description) + .Name("named_query") + .Boost(1.1) + .Fuzzy(m => m + .Term(IntervalsFuzzy) + .Fuzziness(Fuzziness.Auto) + ) + ); + } }