Skip to content

Commit 1eccd2a

Browse files
committed
implemented common_terms query support
1 parent 98fd73a commit 1eccd2a

File tree

8 files changed

+218
-2
lines changed

8 files changed

+218
-2
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using Newtonsoft.Json;
6+
using System.Linq.Expressions;
7+
using System.Globalization;
8+
using Newtonsoft.Json.Converters;
9+
using Elasticsearch.Net;
10+
using Nest.Resolvers;
11+
12+
namespace Nest
13+
{
14+
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
15+
public class CommonTermsQueryDescriptor<T> : IQuery where T : class
16+
{
17+
[JsonProperty(PropertyName = "query")]
18+
internal string _QueryString { get; set; }
19+
20+
[JsonProperty(PropertyName = "field")]
21+
internal PropertyPathMarker _Field { get; set; }
22+
23+
[JsonProperty(PropertyName = "cutoff_frequency")]
24+
internal double? _CutoffFrequency { get; set; }
25+
26+
[JsonProperty(PropertyName = "low_freq_operator")]
27+
[JsonConverter(typeof(StringEnumConverter))]
28+
internal Operator? _LowFrequencyOperator { get; set; }
29+
30+
[JsonProperty(PropertyName = "high_freq_operator")]
31+
[JsonConverter(typeof(StringEnumConverter))]
32+
internal Operator? _HighFrequencyOperator { get; set; }
33+
34+
[JsonProperty(PropertyName = "minimum_should_match")]
35+
internal int? _MinimumShouldMatch { get; set; }
36+
37+
[JsonProperty(PropertyName = "boost")]
38+
internal double? _Boost { get; set; }
39+
40+
[JsonProperty(PropertyName = "analyzer")]
41+
internal string _Analyzer { get; set; }
42+
43+
[JsonProperty(PropertyName = "disable_coord")]
44+
internal bool? _DisableCoord { get; set; }
45+
46+
47+
bool IQuery.IsConditionless
48+
{
49+
get
50+
{
51+
return this._Field.IsConditionless() || this._QueryString.IsNullOrEmpty();
52+
}
53+
}
54+
55+
56+
public CommonTermsQueryDescriptor<T> OnField(string field)
57+
{
58+
this._Field = field;
59+
return this;
60+
}
61+
public CommonTermsQueryDescriptor<T> OnField(Expression<Func<T, object>> objectPath)
62+
{
63+
this._Field = objectPath;
64+
return this;
65+
}
66+
67+
public CommonTermsQueryDescriptor<T> Query(string query)
68+
{
69+
this._QueryString = query;
70+
return this;
71+
}
72+
public CommonTermsQueryDescriptor<T> LowFrequencyOperator(Operator op)
73+
{
74+
this._LowFrequencyOperator = op;
75+
return this;
76+
}
77+
public CommonTermsQueryDescriptor<T> HighFrequencyOperator(Operator op)
78+
{
79+
this._HighFrequencyOperator = op;
80+
return this;
81+
}
82+
public CommonTermsQueryDescriptor<T> Analyzer(string analyzer)
83+
{
84+
this._Analyzer = analyzer;
85+
return this;
86+
}
87+
88+
public CommonTermsQueryDescriptor<T> CutOffFrequency(double cutOffFrequency)
89+
{
90+
this._CutoffFrequency = cutOffFrequency;
91+
return this;
92+
}
93+
public CommonTermsQueryDescriptor<T> MinumumShouldMatch(int minimumShouldMatch)
94+
{
95+
this._MinimumShouldMatch = minimumShouldMatch;
96+
return this;
97+
}
98+
public CommonTermsQueryDescriptor<T> Boost(double boost)
99+
{
100+
this._Boost = boost;
101+
return this;
102+
}
103+
public CommonTermsQueryDescriptor<T> DisableCoord(bool disable = true)
104+
{
105+
this._DisableCoord = disable;
106+
return this;
107+
}
108+
109+
}
110+
}

src/Nest/DSL/QueryDescriptor.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,83 +30,114 @@ internal QueryDescriptor(bool forceConditionless)
3030

3131
[JsonProperty(PropertyName = "match_all")]
3232
internal MatchAll MatchAllQuery { get; set; }
33+
3334
[JsonProperty(PropertyName = "term")]
3435
internal Term TermQuery { get; set; }
36+
3537
[JsonProperty(PropertyName = "wildcard")]
3638
internal Wildcard WildcardQuery { get; set; }
39+
3740
[JsonProperty(PropertyName = "prefix")]
3841
internal Prefix PrefixQuery { get; set; }
3942

4043
[JsonProperty(PropertyName = "boosting")]
4144
internal BoostingQueryDescriptor<T> BoostingQueryDescriptor { get; set; }
45+
4246
[JsonProperty(PropertyName = "ids")]
4347
internal IdsQuery IdsQuery { get; set; }
48+
4449
[JsonProperty(PropertyName = "custom_score")]
4550
internal CustomScoreQueryDescriptor<T> CustomScoreQueryDescriptor { get; set; }
51+
4652
[JsonProperty(PropertyName = "custom_filters_score")]
4753
internal CustomFiltersScoreDescriptor<T> CustomFiltersScoreQueryDescriptor { get; set; }
4854

4955
[JsonProperty(PropertyName = "custom_boost_factor")]
5056
internal CustomBoostFactorQueryDescriptor<T> CustomBoostFactorQueryDescriptor { get; set; }
57+
5158
[JsonProperty(PropertyName = "constant_score")]
5259
internal ConstantScoreQueryDescriptor<T> ConstantScoreQueryDescriptor { get; set; }
60+
5361
[JsonProperty(PropertyName = "dis_max")]
5462
internal DismaxQueryDescriptor<T> DismaxQueryDescriptor { get; set; }
63+
5564
[JsonProperty(PropertyName = "filtered")]
5665
internal FilteredQueryDescriptor<T> FilteredQueryDescriptor { get; set; }
5766

5867
[JsonProperty(PropertyName = "text")]
5968
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
6069
internal IDictionary<PropertyPathMarker, object> TextQueryDescriptor { get; set; }
70+
6171
[JsonProperty(PropertyName = "multi_match")]
6272
internal MultiMatchQueryDescriptor<T> MultiMatchQueryDescriptor { get; set; }
73+
6374
[JsonProperty(PropertyName = "match")]
6475
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
6576
internal IDictionary<PropertyPathMarker, object> MatchQueryDescriptor { get; set; }
77+
6678
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
6779
[JsonProperty(PropertyName = "fuzzy")]
6880
internal IDictionary<PropertyPathMarker, object> FuzzyQueryDescriptor { get; set; }
81+
6982
[JsonProperty(PropertyName = "geo_shape")]
7083
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
7184
internal IDictionary<PropertyPathMarker, object> GeoShapeQueryDescriptor { get; set; }
85+
86+
[JsonProperty(PropertyName = "common_terms")]
87+
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
88+
internal IDictionary<PropertyPathMarker, object> CommonTermsQueryDescriptor { get; set; }
89+
7290
[JsonProperty(PropertyName = "terms")]
7391
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
7492
internal IDictionary<PropertyPathMarker, object> TermsQueryDescriptor { get; set; }
93+
7594
[JsonProperty(PropertyName = "simple_query_string")]
7695
internal SimpleQueryStringQueryDescriptor<T> SimpleQueryStringDescriptor { get; set; }
96+
7797
[JsonProperty(PropertyName = "query_string")]
7898
internal QueryStringDescriptor<T> QueryStringDescriptor { get; set; }
99+
79100
[JsonProperty(PropertyName = "regexp")]
80101
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
81102
internal IDictionary<PropertyPathMarker, object> RegexpQueryDescriptor { get; set; }
82103

83104
[JsonProperty(PropertyName = "flt")]
84105
internal FuzzyLikeThisDescriptor<T> FuzzyLikeThisDescriptor { get; set; }
106+
85107
[JsonProperty(PropertyName = "has_child")]
86108
internal object HasChildQueryDescriptor { get; set; }
109+
87110
[JsonProperty(PropertyName = "has_parent")]
88111
internal object HasParentQueryDescriptor { get; set; }
112+
89113
[JsonProperty(PropertyName = "mlt")]
90114
internal MoreLikeThisQueryDescriptor<T> MoreLikeThisDescriptor { get; set; }
115+
91116
[JsonProperty(PropertyName = "range")]
92117
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
93118
internal IDictionary<PropertyPathMarker, object> RangeQueryDescriptor { get; set; }
94119

95120
[JsonProperty(PropertyName = "span_term")]
96121
internal SpanTerm SpanTermQuery { get; set; }
122+
97123
[JsonProperty(PropertyName = "span_first")]
98124
internal SpanFirstQueryDescriptor<T> SpanFirstQueryDescriptor { get; set; }
125+
99126
[JsonProperty(PropertyName = "span_or")]
100127
internal SpanOrQueryDescriptor<T> SpanOrQueryDescriptor { get; set; }
128+
101129
[JsonProperty(PropertyName = "span_near")]
102130
internal SpanNearQueryDescriptor<T> SpanNearQueryDescriptor { get; set; }
131+
103132
[JsonProperty(PropertyName = "span_not")]
104133
internal SpanNotQueryDescriptor<T> SpanNotQueryDescriptor { get; set; }
105134

106135
[JsonProperty(PropertyName = "top_children")]
107136
internal object TopChildrenQueryDescriptor { get; set; }
137+
108138
[JsonProperty(PropertyName = "nested")]
109139
internal NestedQueryDescriptor<T> NestedQueryDescriptor { get; set; }
140+
110141
[JsonProperty(PropertyName = "indices")]
111142
internal IndicesQueryDescriptor<T> IndicesQueryDescriptor { get; set; }
112143

@@ -452,6 +483,21 @@ public BaseQuery GeoShape(Action<GeoShapeQueryDescriptor<T>> selector)
452483
};
453484
return this.New(query, q => q.GeoShapeQueryDescriptor = shape);
454485
}
486+
487+
/// <summary>
488+
/// The common terms query is a modern alternative to stopwords which improves the precision and recall
489+
/// of search results (by taking stopwords into account), without sacrificing performance.
490+
/// </summary>
491+
public BaseQuery CommonTerms(Action<CommonTermsQueryDescriptor<T>> selector)
492+
{
493+
var query = new CommonTermsQueryDescriptor<T>();
494+
selector(query);
495+
var commonTerms = new Dictionary<PropertyPathMarker, object>
496+
{
497+
{ query._Field, query }
498+
};
499+
return this.New(query, q => q.CommonTermsQueryDescriptor = commonTerms);
500+
}
455501

456502
/// <summary>
457503
/// The has_child query works the same as the has_child filter, by automatically wrapping the filter with a

src/Nest/Domain/DSL/Query.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,10 @@ public static BaseQuery SimpleQueryString(Action<SimpleQueryStringQueryDescripto
134134
{
135135
return new QueryDescriptor<T>().SimpleQueryString(selector);
136136
}
137-
137+
public static BaseQuery CommonTerms(Action<CommonTermsQueryDescriptor<T>> selector)
138+
{
139+
return new QueryDescriptor<T>().CommonTerms(selector);
140+
}
138141
public static BaseQuery GeoShape(Action<GeoShapeQueryDescriptor<T>> selector)
139142
{
140143
return new QueryDescriptor<T>().GeoShape(selector);

src/Nest/Nest.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
<Compile Include="DSL\ClearScrollDescriptor.cs" />
170170
<Compile Include="DSL\DocumentExistsDescriptor.cs" />
171171
<Compile Include="DSL\PercolateCountDescriptor.cs" />
172+
<Compile Include="DSL\Query\CommonTermsQueryDescriptor.cs" />
172173
<Compile Include="DSL\SuggestDescriptor.cs" />
173174
<Compile Include="DSL\CloseIndexDescriptor.cs" />
174175
<Compile Include="DSL\ClusterStateDescriptor.cs" />
@@ -177,7 +178,7 @@
177178
<Compile Include="DSL\Facets\DateExpressionRange.cs" />
178179
<Compile Include="DSL\Paths\BasePathDescriptor.cs" />
179180
<Compile Include="DSL\Query\GeoShapeQueryDescriptor.cs" />
180-
<Compile Include="DSL\Query\SimpeQueryStringQueryDescriptor.cs" />
181+
<Compile Include="DSL\Query\SimpleQueryStringQueryDescriptor.cs" />
181182
<Compile Include="DSL\SourceDescriptor.cs" />
182183
<Compile Include="DSL\NodesStatsDescriptor.cs" />
183184
<Compile Include="DSL\NodesInfoDescriptor.cs" />

src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
<Compile Include="Search\Filter\Singles\HasParentFilterJson.cs" />
176176
<Compile Include="Search\Query\Modes\ConditionlessQueryJson.cs" />
177177
<Compile Include="Search\Query\Singles\FunctionScoreQueryJson.cs" />
178+
<Compile Include="Search\Query\Singles\CommonTerms\CommonTermsTests.cs" />
178179
<Compile Include="Search\Query\Singles\GeoShape\GeoShapeQueryJson.cs" />
179180
<Compile Include="Search\Query\Singles\SimpleQueryString\SimpleQueryStringQueryJson.cs" />
180181
<Compile Include="Search\Query\Singles\RegexpQueryJson.cs" />
@@ -499,6 +500,9 @@
499500
<None Include="Search\Query\QueriesInQueries\FilteredQueryConditionless.json">
500501
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
501502
</None>
503+
<None Include="Search\Query\Singles\CommonTerms\CommonTermsFull.json">
504+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
505+
</None>
502506
<None Include="Search\Query\Singles\GeoShape\GeoShapeFull.json">
503507
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
504508
</None>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"from": 0,
3+
"size": 10,
4+
"query": {
5+
"common_terms": {
6+
"content": {
7+
"query": "This is the most awful stopwords query ever",
8+
"field": "content",
9+
"cutoff_frequency": 0.01,
10+
"low_freq_operator": "or",
11+
"high_freq_operator": "and",
12+
"minimum_should_match": 1,
13+
"boost": 1.2,
14+
"analyzer": "myAnalyzer",
15+
"disable_coord": false
16+
}
17+
}
18+
}
19+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Reflection;
2+
using Nest.Tests.MockData.Domain;
3+
using NUnit.Framework;
4+
5+
namespace Nest.Tests.Unit.Search.Query.Singles.CommonTerms
6+
{
7+
[TestFixture]
8+
public class CommonTermsTests : BaseJsonTests
9+
{
10+
[Test]
11+
public void CommonTermsFull()
12+
{
13+
var s = new SearchDescriptor<ElasticsearchProject>()
14+
.From(0)
15+
.Size(10)
16+
.Query(q=>q
17+
.CommonTerms(qs=>qs
18+
.OnField(p=>p.Content)
19+
.Analyzer("myAnalyzer")
20+
.Boost(1.2)
21+
.CutOffFrequency(0.01)
22+
.DisableCoord(false)
23+
.HighFrequencyOperator(Operator.and)
24+
.LowFrequencyOperator(Operator.or)
25+
.MinumumShouldMatch(1)
26+
.Query("This is the most awful stopwords query ever")
27+
)
28+
);
29+
30+
this.JsonEquals(s, MethodInfo.GetCurrentMethod());
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)