diff --git a/src/Nest/QueryDsl/TermLevel/Terms/TermsQuery.cs b/src/Nest/QueryDsl/TermLevel/Terms/TermsQuery.cs index b608712760f..a65170d717b 100644 --- a/src/Nest/QueryDsl/TermLevel/Terms/TermsQuery.cs +++ b/src/Nest/QueryDsl/TermLevel/Terms/TermsQuery.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -26,14 +27,14 @@ public class TermsQuery : FieldNameQueryBase, ITermsQuery internal override void WrapInContainer(IQueryContainer c) => c.Terms = this; internal static bool IsConditionless(ITermsQuery q) { - return q.Field.IsConditionless() + return q.Field.IsConditionless() || ( - (q.Terms == null - || !q.Terms.HasAny() - || q.Terms.All(t=>t == null + (q.Terms == null + || !q.Terms.HasAny() + || q.Terms.All(t=>t == null || ((t as string)?.IsNullOrEmpty()).GetValueOrDefault(false)) ) - && + && (q.TermsLookup == null || q.TermsLookup.Id == null || q.TermsLookup.Path.IsConditionless() @@ -44,11 +45,11 @@ internal static bool IsConditionless(ITermsQuery q) } /// - /// A query that match on any (configurable) of the provided terms. + /// A query that match on any (configurable) of the provided terms. /// This is a simpler syntax query for using a bool query with several term queries in the should clauses. /// /// The type that represents the expected hit type - public class TermsQueryDescriptor + public class TermsQueryDescriptor : FieldNameQueryDescriptorBase, ITermsQuery, T> , ITermsQuery where T : class { @@ -67,7 +68,13 @@ public TermsQueryDescriptor TermsLookup(Func Terms(IEnumerable terms) => Assign(a => a.Terms = terms?.Cast()); - public TermsQueryDescriptor Terms(params TValue[] terms) => Assign(a => a.Terms = terms?.Cast()); + public TermsQueryDescriptor Terms(params TValue[] terms) => Assign(a => { + if(terms?.Length == 1 && typeof(IEnumerable).IsAssignableFrom(typeof(TValue)) && typeof(TValue) != typeof(string)) + { + a.Terms = (terms.First() as IEnumerable)?.Cast(); + } + else a.Terms = terms?.Cast(); + }); } } diff --git a/src/Nest/QueryDsl/TermLevel/Terms/TermsQueryJsonConverter.cs b/src/Nest/QueryDsl/TermLevel/Terms/TermsQueryJsonConverter.cs index 1c2ff54e267..0b7c495c8b9 100644 --- a/src/Nest/QueryDsl/TermLevel/Terms/TermsQueryJsonConverter.cs +++ b/src/Nest/QueryDsl/TermLevel/Terms/TermsQueryJsonConverter.cs @@ -75,7 +75,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist case "minimum_should_match": reader.Read(); var min = serializer.Deserialize(reader); - f.MinimumShouldMatch = min; + f.MinimumShouldMatch = min; break; case "boost": reader.Read(); @@ -131,7 +131,7 @@ private void ReadTerms(ITermsQuery termsQuery, JsonReader reader, JsonSerializer } else if (reader.TokenType == JsonToken.StartArray) { - var values = JArray.Load(reader).Values(); + var values = JArray.Load(reader).Values(); termsQuery.Terms = values; } } diff --git a/src/Tests/QueryDsl/QueryDslIntegrationTestsBase.cs b/src/Tests/QueryDsl/QueryDslIntegrationTestsBase.cs new file mode 100644 index 00000000000..ec08ce2e540 --- /dev/null +++ b/src/Tests/QueryDsl/QueryDslIntegrationTestsBase.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using Elasticsearch.Net; +using FluentAssertions; +using Nest; +using Tests.Framework; +using Tests.Framework.Integration; +using Tests.Framework.MockData; +using Xunit; + +namespace Tests.QueryDsl +{ + [Collection(IntegrationContext.ReadOnly)] + public abstract class QueryDslIntegrationTestsBase : ApiIntegrationTestBase, ISearchRequest, SearchDescriptor, SearchRequest> + { + protected QueryDslIntegrationTestsBase(IIntegrationCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + protected override LazyResponses ClientUsage() => Calls( + fluent: (client, f) => client.Search(f), + fluentAsync: (client, f) => client.SearchAsync(f), + request: (client, r) => client.Search(r), + requestAsync: (client, r) => client.SearchAsync(r) + ); + + protected override HttpMethod HttpMethod => HttpMethod.POST; + protected override string UrlPath => "/project/project/_search"; + protected override int ExpectStatusCode => 200; + protected override bool ExpectIsValid => true; + + protected abstract object QueryJson { get; } + + protected abstract QueryContainer QueryInitializer { get; } + protected abstract QueryContainer QueryFluent(QueryContainerDescriptor q); + + protected override object ExpectJson => new { query = this.QueryJson }; + + protected override Func, ISearchRequest> Fluent => s => s + .Query(this.QueryFluent); + + protected override SearchRequest Initializer => + new SearchRequest + { + Query = this.QueryInitializer + }; + } +} diff --git a/src/Tests/QueryDsl/TermLevel/Terms/TermsListQueryUsageTests.cs b/src/Tests/QueryDsl/TermLevel/Terms/TermsListQueryUsageTests.cs new file mode 100644 index 00000000000..f69b7edf224 --- /dev/null +++ b/src/Tests/QueryDsl/TermLevel/Terms/TermsListQueryUsageTests.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Nest; +using Tests.Framework; +using Tests.Framework.Integration; +using Tests.Framework.MockData; + +namespace Tests.QueryDsl.TermLevel.Terms +{ + public class TermsListQueryUsageTests : QueryDslUsageTestsBase + { + public TermsListQueryUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) {} + + protected override object QueryJson => new + { + terms = new + { + _name = "named_query", + boost = 1.1, + description = new[] { "term1", "term2" }, + disable_coord = true, + minimum_should_match = 2 + } + }; + + protected override QueryContainer QueryInitializer => new TermsQuery + { + Name = "named_query", + Boost = 1.1, + Field = "description", + Terms = new List { "term1", "term2" }, + DisableCoord = true, + MinimumShouldMatch = 2 + }; + + protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q + .Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .DisableCoord() + .MinimumShouldMatch(MinimumShouldMatch.Fixed(2)) + .Terms(new List { "term1", "term2" }) + ); + } + + public class TermsListOfListIntegrationTests : QueryDslIntegrationTestsBase + { + public TermsListOfListIntegrationTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + private List> _terms = new List> { new List { "term1", "term2" } }; + + protected override object QueryJson => new + { + terms = new + { + _name = "named_query", + boost = 1.1, + description = new[] { new [] { "term1", "term2" } }, + disable_coord = true, + } + }; + + protected override QueryContainer QueryInitializer => new TermsQuery + { + Name = "named_query", + Boost = 1.1, + Field = "description", + Terms = _terms, + DisableCoord = true, + }; + + protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q + .Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .DisableCoord() + .Terms(_terms) + ); + + } + + public class TermsListOfListStringAgainstNumericFieldIntegrationTests : QueryDslIntegrationTestsBase + { + public TermsListOfListStringAgainstNumericFieldIntegrationTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + private List> _terms = new List> { new List { "term1", "term2" } }; + + + protected override int ExpectStatusCode => 400; + protected override bool ExpectIsValid => false; + + protected override object QueryJson => new + { + terms = new + { + _name = "named_query", + boost = 1.1, + numberOfCommits = new[] { new [] { "term1", "term2" } }, + disable_coord = true, + } + }; + + protected override QueryContainer QueryInitializer => new TermsQuery + { + Name = "named_query", + Boost = 1.1, + Field = "numberOfCommits", + Terms = _terms, + DisableCoord = true, + }; + + protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q + .Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.NumberOfCommits) + .DisableCoord() + .Terms(_terms) + ); + + [I] public Task AsserResponse() => AssertOnAllResponses(r => + { + r.ServerError.Should().NotBeNull(); + r.ServerError.Status.Should().Be(400); + r.ServerError.Error.Should().NotBeNull(); + var rootCauses = r.ServerError.Error.RootCause; + rootCauses.Should().NotBeNullOrEmpty(); + var rootCause = rootCauses.First(); + rootCause.Type.Should().Be("number_format_exception"); + }); + + } + +} diff --git a/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs b/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs index 0443c63f879..1984fba674b 100644 --- a/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs +++ b/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using Nest; using Tests.Framework.Integration; @@ -7,6 +8,8 @@ namespace Tests.QueryDsl.TermLevel.Terms { public class TermsQueryUsageTests : QueryDslUsageTestsBase { + protected virtual string[] ExpectedTerms => new [] { "term1", "term2" }; + public TermsQueryUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) {} protected override object QueryJson => new @@ -15,7 +18,7 @@ public TermsQueryUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base { _name = "named_query", boost = 1.1, - description = new[] { "term1", "term2" }, + description = ExpectedTerms, disable_coord = true, minimum_should_match = 2 } @@ -26,7 +29,7 @@ public TermsQueryUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base Name = "named_query", Boost = 1.1, Field = "description", - Terms = new [] { "term1", "term2" }, + Terms = ExpectedTerms, DisableCoord = true, MinimumShouldMatch = 2 }; @@ -49,4 +52,23 @@ protected override QueryContainer QueryFluent(QueryContainerDescriptor q => q.Terms = new [] { "" } }; } -} \ No newline at end of file + + public class SingleTermTermsQueryUsageTests : TermsQueryUsageTests + { + protected override string[] ExpectedTerms => new [] { "term1" }; + + public SingleTermTermsQueryUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q + .Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .DisableCoord() + .MinimumShouldMatch(MinimumShouldMatch.Fixed(2)) + .Terms("term1") + ); + } + + +} diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 87ed005a0ec..73db5892d1a 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -542,6 +542,8 @@ + +