From 4ffe0a9f8fde21b2a38930622ff5cce7195edba7 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Wed, 27 Oct 2021 09:34:19 +0100 Subject: [PATCH] Support `wildcard` field in `WildcardQuery` (#6038) * Support use of `wildcard` field in WildcardQuery (cherry picked from commit a59e92dba643cf7f79604e5b7e121976c62e2725) --- .../TermLevel/Wildcard/WildcardQuery.cs | 25 +++++++--- .../Wildcard/WildcardQueryUsageTests.cs | 44 +++++++++++++++++ .../Wildcard/WildcardSerialisationTests.cs | 49 +++++++++++++++++++ 3 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardSerialisationTests.cs diff --git a/src/Nest/QueryDsl/TermLevel/Wildcard/WildcardQuery.cs b/src/Nest/QueryDsl/TermLevel/Wildcard/WildcardQuery.cs index 0cd76fd5981..782f9289187 100644 --- a/src/Nest/QueryDsl/TermLevel/Wildcard/WildcardQuery.cs +++ b/src/Nest/QueryDsl/TermLevel/Wildcard/WildcardQuery.cs @@ -15,10 +15,12 @@ public interface IWildcardQuery : ITermQuery { [DataMember(Name = "rewrite")] MultiTermQueryRewrite Rewrite { get; set; } + + [DataMember(Name = "wildcard")] + string Wildcard { get; set; } } - public class WildcardQuery : WildcardQuery - where T : class + public class WildcardQuery : WildcardQuery where T : class { public WildcardQuery(Expression> field) => Field = field; } @@ -28,18 +30,29 @@ public class WildcardQuery : FieldNameQueryBase, IWildcardQuery public MultiTermQueryRewrite Rewrite { get; set; } public object Value { get; set; } public bool? CaseInsensitive { get; set; } - protected override bool Conditionless => TermQuery.IsConditionless(this); + public string Wildcard { get; set; } + + protected override bool Conditionless => IsConditionless(this); internal override void InternalWrapInContainer(IQueryContainer c) => c.Wildcard = this; + + // Wildcard queries must include the `field` and either a `value` OR a `wildcard` to match + internal static bool IsConditionless(IWildcardQuery q) => (q.Value == null && q.Wildcard == null) + || ((q.Value?.ToString().IsNullOrEmpty() ?? true) && (q.Wildcard?.ToString().IsNullOrEmpty() ?? true)) + || q.Field.IsConditionless(); } public class WildcardQueryDescriptor - : TermQueryDescriptorBase, IWildcardQuery, T>, - IWildcardQuery - where T : class + : TermQueryDescriptorBase, IWildcardQuery, T>, IWildcardQuery + where T : class { MultiTermQueryRewrite IWildcardQuery.Rewrite { get; set; } + string IWildcardQuery.Wildcard { get; set; } + + protected override bool Conditionless => WildcardQuery.IsConditionless(this); public WildcardQueryDescriptor Rewrite(MultiTermQueryRewrite rewrite) => Assign(rewrite, (a, v) => a.Rewrite = v); + + public WildcardQueryDescriptor Wildcard(string value) => Assign(value, (a, v) => a.Wildcard = v); } } diff --git a/tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardQueryUsageTests.cs b/tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardQueryUsageTests.cs index 6cdd8f8741c..41057c460d3 100644 --- a/tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardQueryUsageTests.cs +++ b/tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardQueryUsageTests.cs @@ -52,4 +52,48 @@ protected override QueryContainer QueryFluent(QueryContainerDescriptor .Rewrite(MultiTermQueryRewrite.TopTermsBoost(10)) ); } + + public class WildcardQueryUsingWildcardFieldUsageTests : QueryDslUsageTestsBase + { + public WildcardQueryUsingWildcardFieldUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override ConditionlessWhen ConditionlessWhen => new ConditionlessWhen(a => a.Wildcard) + { + q => q.Field = null, + q => { q.Value = null; q.Wildcard = null; }, + q => { q.Value = string.Empty; q.Wildcard = string.Empty; } + }; + + protected override QueryContainer QueryInitializer => new WildcardQuery + { + Name = "named_query", + Boost = 1.1, + Field = "description", + Wildcard = "p*oj", + Rewrite = MultiTermQueryRewrite.TopTermsBoost(10) + }; + + protected override object QueryJson => new + { + wildcard = new + { + description = new + { + _name = "named_query", + boost = 1.1, + rewrite = "top_terms_boost_10", + wildcard = "p*oj" + } + } + }; + + protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q + .Wildcard(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Wildcard("p*oj") + .Rewrite(MultiTermQueryRewrite.TopTermsBoost(10)) + ); + } } diff --git a/tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardSerialisationTests.cs b/tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardSerialisationTests.cs new file mode 100644 index 00000000000..841bc7246b3 --- /dev/null +++ b/tests/Tests/QueryDsl/TermLevel/Wildcard/WildcardSerialisationTests.cs @@ -0,0 +1,49 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.IO; +using System.Linq; +using System.Text; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Elasticsearch.Net; +using FluentAssertions; +using Nest; + +namespace Tests.QueryDsl.TermLevel.Wildcard +{ + public class WildcardSerialisationTests + { + [U] + public void DeserialisesAndSerialises() + { + // This test validates that a response from SQL translate can be used in the seubsequent query + // The WildcardQueryBuilder prefers the `wildcard` field over the `value` field. + + var translateResponse = @"{""size"":1000,""query"":{""bool"":{""must"":[{""wildcard"":{""customershortnm.keyword"":{""wildcard"":""*B*"",""boost"":1}}}],""adjust_pure_negative"":true,""boost"":1}}}"; + + var pool = new SingleNodeConnectionPool(new Uri($"http://localhost:9200")); + var settings = new ConnectionSettings(pool, new InMemoryConnection(Encoding.UTF8.GetBytes(translateResponse))); + var client = new ElasticClient(settings); + + var response = client.Sql.Translate(); + + IQueryContainer queryContainer = response.Result.Query; + + queryContainer.Bool.Should().NotBeNull(); + var clauses = queryContainer.Bool.Must.ToList(); + queryContainer = clauses.Single(); + queryContainer.Wildcard.Wildcard.Should().Be("*B*"); + + var stream = new MemoryStream(); + client.ConnectionSettings.RequestResponseSerializer.Serialize(response.Result, stream); + stream.Position = 0; + var reader = new StreamReader(stream); + var json = reader.ReadToEnd(); + + // note: adjust_pure_negative is not recommended + json.Should().Be(@"{""query"":{""bool"":{""must"":[{""wildcard"":{""customershortnm.keyword"":{""wildcard"":""*B*"",""boost"":1.0}}}],""boost"":1.0}},""size"":1000}"); + } + } +}