From 2a1c8fd5b9e0789f073c61cc9eaffdb0b6fc82e2 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Thu, 2 May 2019 14:01:14 +1000 Subject: [PATCH] Implement Intervals query Closes #3680 --- .../Abstractions/Container/IQueryContainer.cs | 3 + .../Container/QueryContainer-Assignments.cs | 7 + .../Container/QueryContainerDescriptor.cs | 10 + .../FullText/Intervals/IntervalsAllOf.cs | 67 +++++++ .../FullText/Intervals/IntervalsAnyOf.cs | 38 ++++ .../FullText/Intervals/IntervalsFilter.cs | 149 +++++++++++++++ .../FullText/Intervals/IntervalsMatch.cs | 102 ++++++++++ .../FullText/Intervals/IntervalsQuery.cs | 177 +++++++++++++++++ src/Nest/QueryDsl/Query.cs | 4 + .../QueryDsl/Visitor/DslPrettyPrintVisitor.cs | 2 + src/Nest/QueryDsl/Visitor/QueryVisitor.cs | 4 + src/Nest/QueryDsl/Visitor/QueryWalker.cs | 1 + .../Tests.Configuration/tests.default.yaml | 2 +- .../Tests/CodeStandards/Descriptors.doc.cs | 3 +- .../FullText/Intervals/IntervalsUsageTests.cs | 179 ++++++++++++++++++ 15 files changed, 746 insertions(+), 2 deletions(-) create mode 100644 src/Nest/QueryDsl/FullText/Intervals/IntervalsAllOf.cs create mode 100644 src/Nest/QueryDsl/FullText/Intervals/IntervalsAnyOf.cs create mode 100644 src/Nest/QueryDsl/FullText/Intervals/IntervalsFilter.cs create mode 100644 src/Nest/QueryDsl/FullText/Intervals/IntervalsMatch.cs create mode 100644 src/Nest/QueryDsl/FullText/Intervals/IntervalsQuery.cs create mode 100644 src/Tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs diff --git a/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs b/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs index cd9312a30af..e538f7fc58c 100644 --- a/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs +++ b/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs @@ -52,6 +52,9 @@ public interface IQueryContainer [DataMember(Name ="ids")] IIdsQuery Ids { get; set; } + [DataMember(Name = "intervals")] + IIntervalsQuery Intervals { get; set; } + [IgnoreDataMember] bool IsConditionless { get; } diff --git a/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs b/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs index f4904023dd6..15024c1fa55 100644 --- a/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs +++ b/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs @@ -21,6 +21,7 @@ public partial class QueryContainer : IQueryContainer, IDescriptor private IHasChildQuery _hasChild; private IHasParentQuery _hasParent; private IIdsQuery _ids; + private IIntervalsQuery _intervals; private IMatchQuery _match; private IMatchAllQuery _matchAllQuery; private IMatchNoneQuery _matchNoneQuery; @@ -148,6 +149,12 @@ IIdsQuery IQueryContainer.Ids set => _ids = Set(value); } + IIntervalsQuery IQueryContainer.Intervals + { + get => _intervals; + set => _intervals = Set(value); + } + IMatchQuery IQueryContainer.Match { get => _match; diff --git a/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs b/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs index 3aeabbcba8a..fcf0197a01d 100644 --- a/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs +++ b/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs @@ -343,6 +343,16 @@ public QueryContainer Prefix(Func, IPrefixQuery> select public QueryContainer Ids(Func selector) => WrapInContainer(selector, (query, container) => container.Ids = query); + /// + /// Allows fine-grained control over the order and proximity of matching terms. + /// Matching rules are constructed from a small set of definitions, + /// and the rules are then applied to terms from a particular field. + /// The definitions produce sequences of minimal intervals that span terms in a body of text. + /// These intervals can be further combined and filtered by parent sources. + /// + public QueryContainer Intervals(Func, IIntervalsQuery> selector) => + WrapInContainer(selector, (query, container) => container.Intervals = query); + /// /// Matches spans containing a term. The span term query maps to Lucene SpanTermQuery. /// diff --git a/src/Nest/QueryDsl/FullText/Intervals/IntervalsAllOf.cs b/src/Nest/QueryDsl/FullText/Intervals/IntervalsAllOf.cs new file mode 100644 index 00000000000..4c69606d7fe --- /dev/null +++ b/src/Nest/QueryDsl/FullText/Intervals/IntervalsAllOf.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Nest +{ + /// + /// A rule that returns matches that span a combination of other rules. + /// + [ReadAs(typeof(IntervalsAllOf))] + public interface IIntervalsAllOf : IIntervals + { + /// + /// An array of rules to combine. All rules must produce a match in a document for the overall source to match. + /// + [DataMember(Name = "intervals")] + IEnumerable Intervals { get; set; } + + /// + /// Specify a maximum number of gaps between the terms in the text. Terms that appear further apart than this will not + /// match. + /// If unspecified, or set to -1, then there is no width restriction on the match. + /// If set to 0 then the terms must appear next to each other. + /// + [DataMember(Name = "max_gaps")] + int? MaxGaps { get; set; } + + /// + /// Whether or not the terms must appear in their specified order. Defaults to false + /// + [DataMember(Name = "ordered")] + bool? Ordered { get; set; } + } + + /// + public class IntervalsAllOf : IntervalsBase, IIntervalsAllOf + { + /// + public IEnumerable Intervals { get; set; } + + /// + public int? MaxGaps { get; set; } + + /// + public bool? Ordered { get; set; } + + internal override void WrapInContainer(IIntervalsContainer container) => container.AllOf = this; + } + + /// + public class IntervalsAllOfDescriptor : IntervalsDescriptorBase, IIntervalsAllOf + { + IEnumerable IIntervalsAllOf.Intervals { get; set; } + int? IIntervalsAllOf.MaxGaps { get; set; } + bool? IIntervalsAllOf.Ordered { get; set; } + + /// + public IntervalsAllOfDescriptor MaxGaps(int? maxGaps) => Assign(maxGaps, (a, v) => a.MaxGaps = v); + + /// + public IntervalsAllOfDescriptor Ordered(bool? ordered = true) => Assign(ordered, (a, v) => a.Ordered = v); + + /// + public IntervalsAllOfDescriptor Intervals(Func>> selector) => + Assign(selector, (a, v) => a.Intervals = v.InvokeOrDefault(new IntervalsListDescriptor())?.Value); + } +} diff --git a/src/Nest/QueryDsl/FullText/Intervals/IntervalsAnyOf.cs b/src/Nest/QueryDsl/FullText/Intervals/IntervalsAnyOf.cs new file mode 100644 index 00000000000..55b11011784 --- /dev/null +++ b/src/Nest/QueryDsl/FullText/Intervals/IntervalsAnyOf.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Nest +{ + /// + /// A rule that emits intervals produced by any of its sub-rules. + /// + [ReadAs(typeof(IntervalsAnyOf))] + public interface IIntervalsAnyOf : IIntervals + { + /// + /// An array of rules to match. + /// + [DataMember(Name = "intervals")] + IEnumerable Intervals { get; set; } + } + + /// + public class IntervalsAnyOf : IntervalsBase, IIntervalsAnyOf + { + /// + public IEnumerable Intervals { get; set; } + + internal override void WrapInContainer(IIntervalsContainer container) => container.AnyOf = this; + } + + /// + public class IntervalsAnyOfDescriptor : IntervalsDescriptorBase, IIntervalsAnyOf + { + IEnumerable IIntervalsAnyOf.Intervals { get; set; } + + /// + public IntervalsAnyOfDescriptor Intervals(Func>> selector) => + Assign(selector, (a, v) => a.Intervals = v.InvokeOrDefault(new IntervalsListDescriptor())?.Value); + } +} diff --git a/src/Nest/QueryDsl/FullText/Intervals/IntervalsFilter.cs b/src/Nest/QueryDsl/FullText/Intervals/IntervalsFilter.cs new file mode 100644 index 00000000000..c3c08b5788a --- /dev/null +++ b/src/Nest/QueryDsl/FullText/Intervals/IntervalsFilter.cs @@ -0,0 +1,149 @@ +using System; +using System.Runtime.Serialization; + +namespace Nest +{ + /// + /// Filters intervals produced by any rules by their relation to the intervals produced by another rule + /// + [ReadAs(typeof(IntervalsFilter))] + public interface IIntervalsFilter + { + /// + /// Produces intervals that appear after an interval from the filter role + /// + [DataMember(Name = "after")] + IntervalsContainer After { get; set; } + + /// + /// Produces intervals that appear before an interval from the filter role + /// + [DataMember(Name = "before")] + IntervalsContainer Before { get; set; } + + /// + /// Produces intervals that are contained by an interval from the filter rule + /// + [DataMember(Name = "contained_by")] + IntervalsContainer ContainedBy { get; set; } + + /// + /// Produces intervals that contain an interval from the filter rule + /// + [DataMember(Name = "containing")] + IntervalsContainer Containing { get; set; } + + /// + /// Produces intervals that are not contained by an interval from the filter rule + /// + [DataMember(Name = "not_contained_by")] + IntervalsContainer NotContainedBy { get; set; } + + /// + /// Produces intervals that do not contain an interval from the filter rule + /// + [DataMember(Name = "not_containing")] + IntervalsContainer NotContaining { get; set; } + + /// + /// Produces intervals that do not overlap with an interval from the filter rule + /// + [DataMember(Name = "not_overlapping")] + IntervalsContainer NotOverlapping { get; set; } + + /// + /// Produces intervals that overlap with an interval from the filter rule + /// + [DataMember(Name = "overlapping")] + IntervalsContainer Overlapping { get; set; } + + /// + /// filter intervals based on their start position, end position and internal gap count, using a script. + /// The script has access to an interval variable, with start, + /// end and gaps properties + /// + [DataMember(Name = "script")] + IScript Script { get; set; } + } + + /// + public class IntervalsFilter : IIntervalsFilter + { + /// + public IntervalsContainer After { get; set; } + + /// + public IntervalsContainer Before { get; set; } + + /// + public IntervalsContainer ContainedBy { get; set; } + + /// + public IntervalsContainer Containing { get; set; } + + /// + public IntervalsContainer NotContainedBy { get; set; } + + /// + public IntervalsContainer NotContaining { get; set; } + + /// + public IntervalsContainer NotOverlapping { get; set; } + + /// + public IntervalsContainer Overlapping { get; set; } + + /// + public IScript Script { get; set; } + } + + /// + public class IntervalsFilterDescriptor : DescriptorBase, IIntervalsFilter + { + IntervalsContainer IIntervalsFilter.After { get; set; } + IntervalsContainer IIntervalsFilter.Before { get; set; } + IntervalsContainer IIntervalsFilter.ContainedBy { get; set; } + IntervalsContainer IIntervalsFilter.Containing { get; set; } + IntervalsContainer IIntervalsFilter.NotContainedBy { get; set; } + IntervalsContainer IIntervalsFilter.NotContaining { get; set; } + IntervalsContainer IIntervalsFilter.NotOverlapping { get; set; } + IntervalsContainer IIntervalsFilter.Overlapping { get; set; } + IScript IIntervalsFilter.Script { get; set; } + + /// + public IntervalsFilterDescriptor Containing(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.Containing = v); + + /// + public IntervalsFilterDescriptor ContainedBy(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.ContainedBy = v); + + /// + public IntervalsFilterDescriptor NotContaining(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.NotContaining = v); + + /// + public IntervalsFilterDescriptor NotContainedBy(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.NotContainedBy = v); + + /// + public IntervalsFilterDescriptor Overlapping(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.Overlapping = v); + + /// + public IntervalsFilterDescriptor NotOverlapping(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.NotOverlapping = v); + + /// + public IntervalsFilterDescriptor Before(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.Before = v); + + /// + public IntervalsFilterDescriptor After(Func selector) => + Assign(selector?.Invoke(new IntervalsDescriptor()), (a, v) => a.After = v); + + /// + public IntervalsFilterDescriptor Script(Func selector) => + Assign(selector?.Invoke(new ScriptDescriptor()), (a, v) => a.Script = v); + } +} diff --git a/src/Nest/QueryDsl/FullText/Intervals/IntervalsMatch.cs b/src/Nest/QueryDsl/FullText/Intervals/IntervalsMatch.cs new file mode 100644 index 00000000000..1eda01c019e --- /dev/null +++ b/src/Nest/QueryDsl/FullText/Intervals/IntervalsMatch.cs @@ -0,0 +1,102 @@ +using System; +using System.Linq.Expressions; +using System.Runtime.Serialization; + +namespace Nest +{ + /// + /// Matches the analyzed text + /// + [ReadAs(typeof(IntervalsMatch))] + public interface IIntervalsMatch : IIntervals + { + /// + /// Which analyzer should be used to analyze terms in the query. + /// By default, the search analyzer of the top-level field will be used. + /// + [DataMember(Name = "analyzer")] + string Analyzer { get; set; } + + /// + /// Specify a maximum number of gaps between the terms in the text. Terms that appear further apart than this will not + /// match. + /// If unspecified, or set to -1, then there is no width restriction on the match. + /// If set to 0 then the terms must appear next to each other. + /// + [DataMember(Name = "max_gaps")] + int? MaxGaps { get; set; } + + /// + /// Whether or not the terms must appear in their specified order. Defaults to false + /// + [DataMember(Name = "ordered")] + bool? Ordered { get; set; } + + /// + /// The text to match. + /// + [DataMember(Name = "query")] + string Query { get; set; } + + /// + /// If specified, then match intervals from this field rather than the top-level field. + /// Terms will be analyzed using the search analyzer from this field. + /// This allows you to search across multiple fields as if they were all the same field + /// + [DataMember(Name = "use_field")] + Field UseField { get; set; } + } + + /// + public class IntervalsMatch : IntervalsBase, IIntervalsMatch + { + /// + [DataMember(Name = "analyzer")] + public string Analyzer { get; set; } + + /// + [DataMember(Name = "max_gaps")] + public int? MaxGaps { get; set; } + + /// + [DataMember(Name = "ordered")] + public bool? Ordered { get; set; } + + /// + public string Query { get; set; } + + /// + [DataMember(Name = "use_field")] + public Field UseField { get; set; } + + internal override void WrapInContainer(IIntervalsContainer container) => container.Match = this; + } + + /// + public class IntervalsMatchDescriptor : IntervalsDescriptorBase, IIntervalsMatch + { + string IIntervalsMatch.Analyzer { get; set; } + int? IIntervalsMatch.MaxGaps { get; set; } + bool? IIntervalsMatch.Ordered { get; set; } + string IIntervalsMatch.Query { get; set; } + Field IIntervalsMatch.UseField { get; set; } + + /// + public IntervalsMatchDescriptor Analyzer(string analyzer) => Assign(analyzer, (a, v) => a.Analyzer = v); + + /// + public IntervalsMatchDescriptor MaxGaps(int? maxGaps) => Assign(maxGaps, (a, v) => a.MaxGaps = v); + + /// + public IntervalsMatchDescriptor Ordered(bool? ordered = true) => Assign(ordered, (a, v) => a.Ordered = v); + + /// + public IntervalsMatchDescriptor Query(string query) => Assign(query, (a, v) => a.Query = v); + + /// + public IntervalsMatchDescriptor UseField(Expression> objectPath) => Assign(objectPath, (a, v) => a.UseField = v); + + /// + public IntervalsMatchDescriptor 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 new file mode 100644 index 00000000000..d1fc534c21b --- /dev/null +++ b/src/Nest/QueryDsl/FullText/Intervals/IntervalsQuery.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Elasticsearch.Net; + +namespace Nest +{ + /// + /// A query that allows fine-grained control over the order and proximity of matching terms. + /// Matching rules are constructed from a small set of definitions, and the rules are then applied to terms from a + /// particular field. + /// The definitions produce sequences of minimal intervals that span terms in a body of text. + /// These intervals can be further combined and filtered by parent sources. + /// + [InterfaceDataContract] + [JsonFormatter(typeof(FieldNameQueryFormatter))] + public interface IIntervalsQuery : IFieldNameQuery, IIntervalsContainer { } + + /// + public class IntervalsQuery : FieldNameQueryBase, IIntervalsQuery + { + /// + public IIntervalsAllOf AllOf { get; set; } + /// + public IIntervalsAnyOf AnyOf { get; set; } + /// + public IIntervalsMatch Match { get; set; } + + protected override bool Conditionless => IsConditionless(this); + + internal static bool IsConditionless(IIntervalsQuery q) => + q.Field.IsConditionless() || q.Match == null && q.AllOf == null && q.AnyOf == null; + + internal override void InternalWrapInContainer(IQueryContainer container) => container.Intervals = this; + } + + /// + [DataContract] + public class IntervalsQueryDescriptor + : FieldNameQueryDescriptorBase, IIntervalsQuery, T> + , IIntervalsQuery where T : class + { + protected override bool Conditionless => IntervalsQuery.IsConditionless(this); + + IIntervalsAllOf IIntervalsContainer.AllOf { get; set; } + IIntervalsAnyOf IIntervalsContainer.AnyOf { get; set; } + IIntervalsMatch IIntervalsContainer.Match { get; set; } + + /// + public IntervalsQueryDescriptor Match(Func selector) => + Assign(selector, (a, v) => a.Match = v?.Invoke(new IntervalsMatchDescriptor())); + + /// + public IntervalsQueryDescriptor AnyOf(Func selector) => + Assign(selector, (a, v) => a.AnyOf = v?.Invoke(new IntervalsAnyOfDescriptor())); + + /// + public IntervalsQueryDescriptor AllOf(Func selector) => + Assign(selector, (a, v) => a.AllOf = v?.Invoke(new IntervalsAllOfDescriptor())); + } + + /// + /// Container for an rule + /// + public interface IIntervalsContainer + { + /// + [DataMember(Name = "all_of")] + IIntervalsAllOf AllOf { get; set; } + + /// + [DataMember(Name = "any_of")] + IIntervalsAnyOf AnyOf { get; set; } + + /// + [DataMember(Name = "match")] + IIntervalsMatch Match { get; set; } + } + + /// + public class IntervalsContainer : IIntervalsContainer, IDescriptor + { + public IntervalsContainer() { } + + public IntervalsContainer(IntervalsBase intervals) + { + intervals.ThrowIfNull(nameof(intervals)); + intervals.WrapInContainer(this); + } + + IIntervalsAllOf IIntervalsContainer.AllOf { get; set; } + IIntervalsAnyOf IIntervalsContainer.AnyOf { get; set; } + IIntervalsMatch IIntervalsContainer.Match { get; set; } + + public static implicit operator IntervalsContainer(IntervalsBase intervals) => intervals == null + ? null + : new IntervalsContainer(intervals); + } + + /// + /// Descriptor to construct an rule + /// + public class IntervalsDescriptor : IntervalsContainer + { + private IntervalsDescriptor Assign(TValue value, Action assigner) => + Fluent.Assign(this, value, assigner); + + /// + public IntervalsDescriptor Match(Func selector) => + Assign(selector, (a, v) => a.Match = v?.Invoke(new IntervalsMatchDescriptor())); + + /// + public IntervalsDescriptor AnyOf(Func selector) => + Assign(selector, (a, v) => a.AnyOf = v?.Invoke(new IntervalsAnyOfDescriptor())); + + /// + public IntervalsDescriptor AllOf(Func selector) => + Assign(selector, (a, v) => a.AllOf = v?.Invoke(new IntervalsAllOfDescriptor())); + } + + /// + /// An rule + /// + public interface IIntervals + { + /// + /// An optional interval filter + /// + [DataMember(Name = "filter")] + IIntervalsFilter Filter { get; set; } + } + + /// + /// Base type for an implementation + /// + public abstract class IntervalsBase : IIntervals + { + /// + public IIntervalsFilter Filter { get; set; } + + internal abstract void WrapInContainer(IIntervalsContainer container); + } + + /// + /// Base type for descriptors that define + /// + public abstract class IntervalsDescriptorBase : DescriptorBase, IIntervals + where TDescriptor : DescriptorBase, TInterface + where TInterface : class, IIntervals + { + IIntervalsFilter IIntervals.Filter { get; set; } + + /// + public TDescriptor Filter(Func selector) => + Assign(selector, (a, v) => a.Filter = v?.Invoke(new IntervalsFilterDescriptor())); + } + + /// + /// Constructs a collection of + /// + public class IntervalsListDescriptor : DescriptorPromiseBase> + { + public IntervalsListDescriptor() : base(new List()) { } + + /// + public IntervalsListDescriptor Match(Func selector) => + Assign(selector, (a, v) => a.AddIfNotNull(new IntervalsDescriptor().Match(v))); + + /// + public IntervalsListDescriptor AnyOf(Func selector) => + Assign(selector, (a, v) => a.AddIfNotNull(new IntervalsDescriptor().AnyOf(v))); + + /// + public IntervalsListDescriptor AllOf(Func selector) => + Assign(selector, (a, v) => a.AddIfNotNull(new IntervalsDescriptor().AllOf(v))); + } +} diff --git a/src/Nest/QueryDsl/Query.cs b/src/Nest/QueryDsl/Query.cs index 62bf7980da1..44b4653453c 100644 --- a/src/Nest/QueryDsl/Query.cs +++ b/src/Nest/QueryDsl/Query.cs @@ -56,6 +56,10 @@ public static QueryContainer HasParent(Func selector) => new QueryContainerDescriptor().Ids(selector); + /// + public static QueryContainer Intervals(Func, IIntervalsQuery> selector) => + new QueryContainerDescriptor().Intervals(selector); + public static QueryContainer Match(Func, IMatchQuery> selector) => new QueryContainerDescriptor().Match(selector); diff --git a/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs b/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs index f6962728c62..4509ec24fce 100644 --- a/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs +++ b/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs @@ -123,6 +123,8 @@ public virtual void Visit(IGeoShapeQuery query) public virtual void Visit(IIdsQuery query) => Write("ids"); + public virtual void Visit(IIntervalsQuery query) => Write("intervals"); + public virtual void Visit(IMatchQuery query) => Write("match", query.Field); public virtual void Visit(IMatchPhraseQuery query) => Write("match_phrase", query.Field); diff --git a/src/Nest/QueryDsl/Visitor/QueryVisitor.cs b/src/Nest/QueryDsl/Visitor/QueryVisitor.cs index c7ee7b124fc..dd5f757cb9e 100644 --- a/src/Nest/QueryDsl/Visitor/QueryVisitor.cs +++ b/src/Nest/QueryDsl/Visitor/QueryVisitor.cs @@ -50,6 +50,8 @@ public interface IQueryVisitor void Visit(IIdsQuery query); + void Visit(IIntervalsQuery query); + void Visit(IMatchQuery query); void Visit(IMatchPhraseQuery query); @@ -183,6 +185,8 @@ public virtual void Visit(IHasParentQuery query) { } public virtual void Visit(IIdsQuery query) { } + public virtual void Visit(IIntervalsQuery query) { } + public virtual void Visit(IMatchQuery query) { } public virtual void Visit(IMatchPhraseQuery query) { } diff --git a/src/Nest/QueryDsl/Visitor/QueryWalker.cs b/src/Nest/QueryDsl/Visitor/QueryWalker.cs index f0df3e5d0ab..dc7e8ab7834 100644 --- a/src/Nest/QueryDsl/Visitor/QueryWalker.cs +++ b/src/Nest/QueryDsl/Visitor/QueryWalker.cs @@ -31,6 +31,7 @@ public void Walk(IQueryContainer qd, IQueryVisitor visitor) }); VisitQuery(qd.GeoShape, visitor, (v, d) => v.Visit(d)); VisitQuery(qd.Ids, visitor, (v, d) => v.Visit(d)); + VisitQuery(qd.Intervals, visitor, (v, d) => v.Visit(d)); VisitQuery(qd.Prefix, visitor, (v, d) => v.Visit(d)); VisitQuery(qd.QueryString, visitor, (v, d) => v.Visit(d)); VisitQuery(qd.Range, visitor, (v, d) => v.Visit(d)); diff --git a/src/Tests/Tests.Configuration/tests.default.yaml b/src/Tests/Tests.Configuration/tests.default.yaml index e55b45b5aad..d27016c161f 100644 --- a/src/Tests/Tests.Configuration/tests.default.yaml +++ b/src/Tests/Tests.Configuration/tests.default.yaml @@ -5,7 +5,7 @@ # tracked by git). # mode either u (unit test), i (integration test) or m (mixed mode) -mode: u +mode: m # the elasticsearch version that should be started # Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype diff --git a/src/Tests/Tests/CodeStandards/Descriptors.doc.cs b/src/Tests/Tests/CodeStandards/Descriptors.doc.cs index a07ff131fe6..745c64e6277 100644 --- a/src/Tests/Tests/CodeStandards/Descriptors.doc.cs +++ b/src/Tests/Tests/CodeStandards/Descriptors.doc.cs @@ -78,7 +78,8 @@ from t in typeof(DescriptorBase<,>).Assembly.Types() {typeof(SmoothingModelContainerDescriptor), typeof(SmoothingModelContainer)}, {typeof(InputDescriptor), typeof(InputContainer)}, {typeof(RoleMappingRuleDescriptor), typeof(RoleMappingRuleBase)}, - {typeof(FluentDictionary<,>), typeof(FluentDictionary<,>)} + {typeof(FluentDictionary<,>), typeof(FluentDictionary<,>)}, + {typeof(IntervalsDescriptor), typeof(IntervalsContainer)} }; Func exclude = (first, second) => diff --git a/src/Tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs b/src/Tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs new file mode 100644 index 00000000000..fb603a16774 --- /dev/null +++ b/src/Tests/Tests/QueryDsl/FullText/Intervals/IntervalsUsageTests.cs @@ -0,0 +1,179 @@ +using Nest; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework.Integration; +using static Nest.Infer; + +namespace Tests.QueryDsl.FullText.Intervals +{ + /** + * An intervals query allows fine-grained control over the order and proximity of matching terms. + * Matching rules are constructed from a small set of definitions, and the rules are then applied to terms from a particular field. + * + * The definitions produce sequences of minimal intervals that span terms in a body of text. These intervals can be further combined and filtered by parent sources. + * + * NOTE: Only available in Elasticsearch 6.7.0+ + * + * Be sure to read the Elasticsearch documentation on {ref_current}/query-dsl-intervals-query.html[Intervals query] + */ + public class IntervalsUsageTests : QueryDslUsageTestsBase + { + public IntervalsUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } + + protected override ConditionlessWhen ConditionlessWhen => new ConditionlessWhen(a => a.Intervals) + { + q => q.Field = null, + q => q.AnyOf = null + }; + + protected override QueryContainer QueryInitializer => new IntervalsQuery + { + Field = Field(p => p.Description), + Name = "named_query", + Boost = 1.1, + AnyOf = new IntervalsAnyOf + { + Intervals = new IntervalsContainer[] + { + new IntervalsMatch + { + Query = "my favourite food", + MaxGaps = 5, + Ordered = true, + Filter = new IntervalsFilter + { + Containing = new IntervalsMatch + { + Query = "kimchy" + } + } + }, + new IntervalsAllOf + { + Intervals = new IntervalsContainer[] + { + new IntervalsMatch + { + Query = "hot water", + }, + new IntervalsMatch + { + Query = "cold porridge", + }, + }, + Filter = new IntervalsFilter + { + Script = new InlineScript("interval.start > 0 && interval.end < 200") + } + } + } + } + }; + + protected override object QueryJson => new + { + intervals = new + { + description = new + { + _name = "named_query", + boost = 1.1, + any_of = new + { + intervals = new object[] + { + new + { + match = new + { + query = "my favourite food", + max_gaps = 5, + ordered = true, + filter = new + { + containing = new + { + match = new + { + query = "kimchy" + } + } + } + } + }, + new + { + all_of = new + { + intervals = new object[] + { + new + { + match = new + { + query = "hot water" + } + }, + new + { + match = new + { + query = "cold porridge" + } + }, + }, + filter = new + { + script = new + { + source = "interval.start > 0 && interval.end < 200" + } + } + } + } + } + } + + } + } + }; + + protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q + .Intervals(c => c + .Field(p => p.Description) + .Name("named_query") + .Boost(1.1) + .AnyOf(any => any + .Intervals(i => i + .Match(m => m + .Query("my favourite food") + .MaxGaps(5) + .Ordered() + .Filter(f => f + .Containing(co => co + .Match(mm => mm + .Query("kimchy") + ) + ) + ) + ) + .AllOf(all => all + .Intervals(ii => ii + .Match(m => m + .Query("hot water") + ) + .Match(m => m + .Query("cold porridge") + ) + ) + .Filter(f => f + .Script(s => s + .Source("interval.start > 0 && interval.end < 200") + ) + ) + ) + ) + ) + ); + } +}