Skip to content

Commit 69a7a0e

Browse files
committed
Add support for fuzzy intervals in interval query
Relates: #4341 This commit adds support for fuzzy intervals source in Intervals query
1 parent 6b3f816 commit 69a7a0e

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using System.Runtime.Serialization;
4+
5+
namespace Nest
6+
{
7+
/// <summary>
8+
/// The fuzzy rule matches terms that are similar to the provided term, within an edit distance defined by Fuzziness.
9+
/// If the fuzzy expansion matches more than 128 terms, Elasticsearch returns an error.
10+
/// <para />
11+
/// Available in Elasticsearch 7.6.0+
12+
/// </summary>
13+
[ReadAs(typeof(IntervalsFuzzy))]
14+
public interface IIntervalsFuzzy : IIntervalsNoFilter
15+
{
16+
/// <summary>
17+
/// Analyzer used to normalize the term. Defaults to the top-level field's analyzer.
18+
/// </summary>
19+
[DataMember(Name = "analyzer")]
20+
string Analyzer { get; set; }
21+
22+
/// <summary>
23+
/// Number of beginning characters left unchanged when creating expansions. Defaults to <c>0</c>.
24+
/// </summary>
25+
[DataMember(Name = "prefix_length")]
26+
int? PrefixLength { get; set; }
27+
28+
/// <summary>
29+
/// Indicates whether edits include transpositions of two adjacent characters (ab → ba). Defaults to <c>true</c>.
30+
/// </summary>
31+
[DataMember(Name = "transpositions")]
32+
bool? Transpositions { get; set; }
33+
34+
/// <summary>
35+
/// Maximum edit distance allowed for matching. See Fuzziness for valid values and more information.
36+
/// Defaults to <see cref="Nest.Fuzziness.Auto"/>.
37+
/// </summary>
38+
[DataMember(Name = "fuzziness")]
39+
Fuzziness Fuzziness { get; set; }
40+
41+
/// <summary>
42+
/// The term to match.
43+
/// </summary>
44+
[DataMember(Name = "term")]
45+
string Term { get; set; }
46+
47+
/// <summary>
48+
/// If specified, then match intervals from this field rather than the top-level field.
49+
/// The term is normalized using the search analyzer from this field,
50+
/// unless analyzer is specified separately.
51+
/// </summary>
52+
[DataMember(Name = "use_field")]
53+
Field UseField { get; set; }
54+
}
55+
56+
/// <inheritdoc cref="IIntervalsFuzzy" />
57+
public class IntervalsFuzzy : IntervalsNoFilterBase, IIntervalsFuzzy
58+
{
59+
internal override void WrapInContainer(IIntervalsContainer container) => container.Fuzzy = this;
60+
61+
/// <inheritdoc />
62+
public string Analyzer { get; set; }
63+
/// <inheritdoc />
64+
public int? PrefixLength { get; set; }
65+
/// <inheritdoc />
66+
public bool? Transpositions { get; set; }
67+
/// <inheritdoc />
68+
public Fuzziness Fuzziness { get; set; }
69+
/// <inheritdoc />
70+
public string Term { get; set; }
71+
/// <inheritdoc />
72+
public Field UseField { get; set; }
73+
}
74+
75+
/// <inheritdoc cref="IIntervalsFuzzy" />
76+
public class IntervalsFuzzyDescriptor : DescriptorBase<IntervalsFuzzyDescriptor, IIntervalsFuzzy>, IIntervalsFuzzy
77+
{
78+
string IIntervalsFuzzy.Analyzer { get; set; }
79+
int? IIntervalsFuzzy.PrefixLength { get; set; }
80+
bool? IIntervalsFuzzy.Transpositions { get; set; }
81+
Fuzziness IIntervalsFuzzy.Fuzziness { get; set; }
82+
string IIntervalsFuzzy.Term { get; set; }
83+
Field IIntervalsFuzzy.UseField { get; set; }
84+
85+
/// <inheritdoc cref="IIntervalsFuzzy.Analyzer" />
86+
public IntervalsFuzzyDescriptor Analyzer(string analyzer) => Assign(analyzer, (a, v) => a.Analyzer = v);
87+
88+
/// <inheritdoc cref="IIntervalsFuzzy.PrefixLength" />
89+
public IntervalsFuzzyDescriptor PrefixLength(int? prefixLength) => Assign(prefixLength, (a, v) => a.PrefixLength = v);
90+
91+
/// <inheritdoc cref="IIntervalsFuzzy.Transpositions" />
92+
public IntervalsFuzzyDescriptor Transpositions(bool? transpositions = true) => Assign(transpositions, (a, v) => a.Transpositions = v);
93+
94+
/// <inheritdoc cref="IIntervalsFuzzy.Fuzziness" />
95+
public IntervalsFuzzyDescriptor Fuzziness(Fuzziness fuzziness) => Assign(fuzziness, (a, v) => a.Fuzziness = v);
96+
97+
/// <inheritdoc cref="IIntervalsFuzzy.Term" />
98+
public IntervalsFuzzyDescriptor Term(string term) => Assign(term, (a, v) => a.Term = v);
99+
100+
101+
/// <inheritdoc cref="IIntervalsFuzzy.UseField" />
102+
public IntervalsFuzzyDescriptor UseField<T>(Expression<Func<T, object>> objectPath) => Assign(objectPath, (a, v) => a.UseField = v);
103+
104+
/// <inheritdoc cref="IIntervalsFuzzy.UseField" />
105+
public IntervalsFuzzyDescriptor UseField(Field useField) => Assign(useField, (a, v) => a.UseField = v);
106+
}
107+
}

src/Nest/QueryDsl/FullText/Intervals/IntervalsQuery.cs

+22-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class IntervalsQuery : FieldNameQueryBase, IIntervalsQuery
2525
public IIntervalsAnyOf AnyOf { get; set; }
2626
/// <inheritdoc cref="IIntervalsMatch"/>
2727
public IIntervalsMatch Match { get; set; }
28+
/// <inheritdoc cref="IIntervalsFuzzy"/>
29+
public IIntervalsFuzzy Fuzzy { get; set; }
2830

2931
/// <inheritdoc cref="IIntervalsPrefix"/>
3032
public IIntervalsPrefix Prefix { get; set; }
@@ -35,7 +37,8 @@ public class IntervalsQuery : FieldNameQueryBase, IIntervalsQuery
3537
protected override bool Conditionless => IsConditionless(this);
3638

3739
internal static bool IsConditionless(IIntervalsQuery q) =>
38-
q.Field.IsConditionless() || q.Match == null && q.AllOf == null && q.AnyOf == null && q.Prefix == null && q.Wildcard == null;
40+
q.Field.IsConditionless() || q.Match == null && q.AllOf == null && q.AnyOf == null && q.Prefix == null && q.Wildcard == null
41+
&& q.Fuzzy == null;
3942

4043
internal override void InternalWrapInContainer(IQueryContainer container) => container.Intervals = this;
4144
}
@@ -50,10 +53,15 @@ public class IntervalsQueryDescriptor<T>
5053

5154
IIntervalsAllOf IIntervalsContainer.AllOf { get; set; }
5255
IIntervalsAnyOf IIntervalsContainer.AnyOf { get; set; }
56+
IIntervalsFuzzy IIntervalsContainer.Fuzzy { get; set; }
5357
IIntervalsMatch IIntervalsContainer.Match { get; set; }
5458
IIntervalsPrefix IIntervalsContainer.Prefix { get; set; }
5559
IIntervalsWildcard IIntervalsContainer.Wildcard { get; set; }
5660

61+
/// <inheritdoc cref="IntervalsQuery.Fuzzy" />
62+
public IntervalsQueryDescriptor<T> Fuzzy(Func<IntervalsFuzzyDescriptor, IIntervalsFuzzy> selector) =>
63+
Assign(selector, (a, v) => a.Fuzzy = v?.Invoke(new IntervalsFuzzyDescriptor()));
64+
5765
/// <inheritdoc cref="IntervalsQuery.Match" />
5866
public IntervalsQueryDescriptor<T> Match(Func<IntervalsMatchDescriptor, IIntervalsMatch> selector) =>
5967
Assign(selector, (a, v) => a.Match = v?.Invoke(new IntervalsMatchDescriptor()));
@@ -88,6 +96,10 @@ public interface IIntervalsContainer
8896
[DataMember(Name = "any_of")]
8997
IIntervalsAnyOf AnyOf { get; set; }
9098

99+
/// <inheritdoc cref="IIntervalsFuzzy" />
100+
[DataMember(Name = "fuzzy")]
101+
IIntervalsFuzzy Fuzzy { get; set; }
102+
91103
/// <inheritdoc cref="IIntervalsMatch" />
92104
[DataMember(Name = "match")]
93105
IIntervalsMatch Match { get; set; }
@@ -120,6 +132,7 @@ public IntervalsContainer(IntervalsNoFilterBase intervals)
120132

121133
IIntervalsAllOf IIntervalsContainer.AllOf { get; set; }
122134
IIntervalsAnyOf IIntervalsContainer.AnyOf { get; set; }
135+
IIntervalsFuzzy IIntervalsContainer.Fuzzy { get; set; }
123136
IIntervalsMatch IIntervalsContainer.Match { get; set; }
124137
IIntervalsPrefix IIntervalsContainer.Prefix { get; set; }
125138
IIntervalsWildcard IIntervalsContainer.Wildcard { get; set; }
@@ -141,6 +154,10 @@ public class IntervalsDescriptor : IntervalsContainer
141154
private IntervalsDescriptor Assign<TValue>(TValue value, Action<IIntervalsContainer, TValue> assigner) =>
142155
Fluent.Assign(this, value, assigner);
143156

157+
/// <inheritdoc cref="IntervalsFuzzyDescriptor" />
158+
public IntervalsDescriptor Fuzzy(Func<IntervalsFuzzyDescriptor, IIntervalsFuzzy> selector) =>
159+
Assign(selector, (a, v) => a.Fuzzy = v?.Invoke(new IntervalsFuzzyDescriptor()));
160+
144161
/// <inheritdoc cref="IntervalsMatchDescriptor" />
145162
public IntervalsDescriptor Match(Func<IntervalsMatchDescriptor, IIntervalsMatch> selector) =>
146163
Assign(selector, (a, v) => a.Match = v?.Invoke(new IntervalsMatchDescriptor()));
@@ -222,6 +239,10 @@ public class IntervalsListDescriptor : DescriptorPromiseBase<IntervalsListDescri
222239
{
223240
public IntervalsListDescriptor() : base(new List<IntervalsContainer>()) { }
224241

242+
/// <inheritdoc cref="IIntervalsFuzzy" />
243+
public IntervalsListDescriptor Fuzzy(Func<IntervalsFuzzyDescriptor, IIntervalsFuzzy> selector) =>
244+
Assign(selector, (a, v) => a.AddIfNotNull(new IntervalsDescriptor().Fuzzy(v)));
245+
225246
/// <inheritdoc cref="IIntervalsMatch" />
226247
public IntervalsListDescriptor Match(Func<IntervalsMatchDescriptor, IIntervalsMatch> selector) =>
227248
Assign(selector, (a, v) => a.AddIfNotNull(new IntervalsDescriptor().Match(v)));

tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs

+58
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,62 @@ protected override QueryContainer QueryFluent(QueryContainerDescriptor<Project>
262262
)
263263
);
264264
}
265+
266+
/**[float]
267+
* === Fuzzy rules
268+
*
269+
* Fuzzy rules can be used to match terms that are similar to the provided term, within an edit distance defined by Fuzziness.
270+
* If the fuzzy expansion matches more than 128 terms, Elasticsearch returns an error.
271+
*
272+
* NOTE: Only available in Elasticsearch 7.6.0+
273+
*/
274+
[SkipVersion("<7.6.0", "fuzzy rules introduced in 7.6.0")]
275+
public class IntervalsFuzzyUsageTests : QueryDslUsageTestsBase
276+
{
277+
public IntervalsFuzzyUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
278+
279+
private static readonly string IntervalsPrefix = Project.First.Description.Split(' ')[0];
280+
281+
private static readonly string IntervalsFuzzy = IntervalsPrefix.Substring(0, IntervalsPrefix.Length) + "z";
282+
283+
protected override QueryContainer QueryInitializer => new IntervalsQuery
284+
{
285+
Field = Field<Project>(p => p.Description),
286+
Name = "named_query",
287+
Boost = 1.1,
288+
Fuzzy = new IntervalsFuzzy
289+
{
290+
Term = IntervalsFuzzy,
291+
Fuzziness = Fuzziness.Auto
292+
}
293+
};
294+
295+
protected override object QueryJson => new
296+
{
297+
intervals = new
298+
{
299+
description = new
300+
{
301+
_name = "named_query",
302+
boost = 1.1,
303+
fuzzy = new
304+
{
305+
term = IntervalsFuzzy,
306+
fuzziness = "AUTO"
307+
}
308+
}
309+
}
310+
};
311+
312+
protected override QueryContainer QueryFluent(QueryContainerDescriptor<Project> q) => q
313+
.Intervals(c => c
314+
.Field(p => p.Description)
315+
.Name("named_query")
316+
.Boost(1.1)
317+
.Fuzzy(m => m
318+
.Term(IntervalsFuzzy)
319+
.Fuzziness(Fuzziness.Auto)
320+
)
321+
);
322+
}
265323
}

0 commit comments

Comments
 (0)