diff --git a/docs/client-concepts/high-level/mapping/fluent-mapping.asciidoc b/docs/client-concepts/high-level/mapping/fluent-mapping.asciidoc index d057bad302e..c0bef07961a 100644 --- a/docs/client-concepts/high-level/mapping/fluent-mapping.asciidoc +++ b/docs/client-concepts/high-level/mapping/fluent-mapping.asciidoc @@ -371,3 +371,59 @@ As demonstrated, by calling `.AutoMap()` inside of the `.Nested` mappi `Employee` nested properties and again, override any inferred mapping from the automapping process, through manual mapping +[[mapping-runtime-fields]] +==== Mapping runtime fields + +A {ref_current}/runtime.html[runtime field] is a field that is evaluated at query time. Runtime fields may +be defined in the mapping of an index. + +In this example, we'll define a `CompanyRuntimeFields` class with a single property which we may then use in +the strongly-typed runtime field mapping. + +[source,csharp] +---- +public class CompanyRuntimeFields +{ + public string BirthDayOfWeek { get; set; } +} + +var createIndexResponse = _client.Indices.Create("myindex", c => c + .Map(m => m + .RuntimeFields(rtf => rtf <1> + .RuntimeField(f => f.BirthDayOfWeek, FieldType.Keyword, f => f.Script("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"))) <2> + ) + ); +---- +<1> Use the `CompanyRuntimeFields` class as the generic argument +<2> Use the `BirthDayOfWeek` property as the runtime field name + +[source,javascript] +---- +{ + "mappings": { + "runtime": { + "birthDayOfWeek": { + "type": "keyword", + "script": { + "lang": "painless", + "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))" + } + } + } + } +} +---- + +It's not necessary to define a type for the runtime field mapping. Runtime fields can optionally be defined +by providing a `string` name. + +[source,csharp] +---- +createIndexResponse = _client.Indices.Create("myindex", c => c + .Map(m => m + .RuntimeFields(rtf => rtf + .RuntimeField("birthDayOfWeek", FieldType.Keyword, f => f.Script("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"))) + ) +); +---- + diff --git a/docs/high-level.asciidoc b/docs/high-level.asciidoc index 16b2b18090a..af388043b4e 100644 --- a/docs/high-level.asciidoc +++ b/docs/high-level.asciidoc @@ -211,6 +211,8 @@ include::search/scrolling-documents.asciidoc[] include::{output-dir}/covariant-hits/covariant-search-results.asciidoc[] +include::search/searching-runtime-fields.asciidoc[] + [[aggregations]] == Aggregations diff --git a/docs/search/searching-runtime-fields.asciidoc b/docs/search/searching-runtime-fields.asciidoc new file mode 100644 index 00000000000..7da66e533d3 --- /dev/null +++ b/docs/search/searching-runtime-fields.asciidoc @@ -0,0 +1,150 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/7.11 + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +//// +IMPORTANT NOTE +============== +This file has been generated from https://github.com/elastic/elasticsearch-net/tree/7.x/src/Tests/Tests/Search/SearchingRuntimeFields.doc.cs. +If you wish to submit a PR for any spelling mistakes, typos or grammatical errors for this file, +please modify the original csharp file found at the link and submit the PR with that change. Thanks! +//// + +[[searching-runtime-fields]] +=== Searching runtime fields + +Runtime fields can be returned with search requests by specifying the fields using `.Fields` +on the search request. + +[WARNING] +-- +This functionality is in beta and is subject to change. The design and code is less mature +than official GA features and is being provided as-is with no warranties. Beta features +are not subject to the support SLA of official GA features. + +-- + +[source,csharp] +---- +var searchResponse = _client.Search(s => s + .Query(q => q + .MatchAll() + ) + .Fields(fs => fs + .Field(f => f.StartedOnDayOfWeek) + .Field(f => f.ThirtyDaysFromStarted, format: DateFormat.basic_date) + ) +); +---- + +which serializes to the following JSON + +[source,javascript] +---- +{ + "query": { + "match_all": {} + }, + "fields": [ + "runtime_started_on_day_of_week", + { + "field": "runtime_thirty_days_after_started", + "format": "basic_date" + } + ] +} +---- + +The previous example used the Fluent API to express the query. NEST also exposes an +Object Initializer syntax to compose queries + +[source,csharp] +---- +var searchRequest = new SearchRequest +{ + Query = new MatchAllQuery(), + Fields = Infer.Field(p => p.StartedOnDayOfWeek) <1> + .And(p => p.ThirtyDaysFromStarted, format: DateFormat.basic_date) <2> +}; + +searchResponse = _client.Search(searchRequest); +---- +<1> Here we infer the field name from a property on a POCO class +<2> For runtime fields which return a date, a format may be specified. + +==== Defining runtime fields + +You may define runtime fields that exist only as part of a query by specifying `.RuntimeFields` on +the search request. You may return this field using `.Fields` or use it for an aggregation. + +[source,csharp] +---- +var searchResponse = _client.Search(s => s + .Query(q => q + .MatchAll() + ) + .Fields(fs => fs + .Field(f => f.StartedOnDayOfWeek) + .Field(f => f.ThirtyDaysFromStarted, format: DateFormat.basic_date) + .Field("search_runtime_field") + ) + .RuntimeFields(rtf => rtf.RuntimeField("search_runtime_field", FieldType.Keyword, r => r + .Script("if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}"))) +); +---- + +which yields the following query JSON + +[source,javascript] +---- +{ + "query": { + "match_all": {} + }, + "fields": [ + "runtime_started_on_day_of_week", + { + "field": "runtime_thirty_days_after_started", + "format": "basic_date" + }, + "search_runtime_field" + ], + "runtime_mappings": { + "search_runtime_field": { + "script": { + "lang": "painless", + "source": "if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}" + }, + "type": "keyword" + } + } +} +---- + +The previous example used the Fluent API to express the query. Here is the same query using the +Object Initializer syntax. + +[source,csharp] +---- +var searchRequest = new SearchRequest +{ + Query = new MatchAllQuery(), + Fields = Infer.Field(p => p.StartedOnDayOfWeek) + .And(p => p.ThirtyDaysFromStarted, format: DateFormat.basic_date) + .And("search_runtime_field"), + RuntimeFields = new RuntimeFields + { + { "search_runtime_field", new RuntimeField + { + Type = FieldType.Keyword, + Script = new PainlessScript("if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}") + } + } + } +}; + +searchResponse = _client.Search(searchRequest); +---- + diff --git a/src/Nest/Indices/MappingManagement/PutMapping/PutMappingRequest.cs b/src/Nest/Indices/MappingManagement/PutMapping/PutMappingRequest.cs index 5133a051e03..a801717926a 100644 --- a/src/Nest/Indices/MappingManagement/PutMapping/PutMappingRequest.cs +++ b/src/Nest/Indices/MappingManagement/PutMapping/PutMappingRequest.cs @@ -155,9 +155,13 @@ public PutMappingDescriptor RoutingField(Func a.RoutingField = v?.Invoke(new RoutingFieldDescriptor())); /// - public PutMappingDescriptor RuntimeFields(Func> runtimeFieldsSelector) => - Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); + public PutMappingDescriptor RuntimeFields(Func, IPromise> runtimeFieldsSelector) => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); + /// + public PutMappingDescriptor RuntimeFields(Func, IPromise> runtimeFieldsSelector) where TSource : class => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); + /// public PutMappingDescriptor FieldNamesField(Func, IFieldNamesField> fieldNamesFieldSelector) => Assign(fieldNamesFieldSelector, (a, v) => a.FieldNamesField = v.Invoke(new FieldNamesFieldDescriptor())); diff --git a/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs b/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs index dac72883f71..fe65900a730 100644 --- a/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs +++ b/src/Nest/Mapping/RuntimeFields/RuntimeFields.cs @@ -4,33 +4,40 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using Elasticsearch.Net.Utf8Json; namespace Nest { - [JsonFormatter(typeof(VerbatimDictionaryKeysFormatter))] - public interface IRuntimeFields : IIsADictionary { } + [JsonFormatter(typeof(VerbatimDictionaryKeysFormatter))] + public interface IRuntimeFields : IIsADictionary { } - public class RuntimeFields : IsADictionaryBase, IRuntimeFields + public class RuntimeFields : IsADictionaryBase, IRuntimeFields { public RuntimeFields() { } - public RuntimeFields(IDictionary container) : base(container) { } + public RuntimeFields(IDictionary container) : base(container) { } - public RuntimeFields(Dictionary container) : base(container) { } + public RuntimeFields(Dictionary container) : base(container) { } - public void Add(string name, IRuntimeField runtimeField) => BackingDictionary.Add(name, runtimeField); + public void Add(Field name, IRuntimeField runtimeField) => BackingDictionary.Add(name, runtimeField); } - public class RuntimeFieldsDescriptor - : IsADictionaryDescriptorBase + public class RuntimeFieldsDescriptor + : IsADictionaryDescriptorBase, RuntimeFields, Field, IRuntimeField> where TDocument : class { public RuntimeFieldsDescriptor() : base(new RuntimeFields()) { } - public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type, Func selector) => + public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type, Func selector) => Assign(name, selector?.Invoke(new RuntimeFieldDescriptor(type))); - public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type) => + public RuntimeFieldsDescriptor RuntimeField(Expression> field, FieldType type, Func selector) => + Assign(field, selector?.Invoke(new RuntimeFieldDescriptor(type))); + + public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type) => Assign(name, new RuntimeFieldDescriptor(type)); + + public RuntimeFieldsDescriptor RuntimeField(Expression> field, FieldType type) => + Assign(field, new RuntimeFieldDescriptor(type)); } } diff --git a/src/Nest/Mapping/TypeMapping.cs b/src/Nest/Mapping/TypeMapping.cs index 0058e2d06f1..aa48c6d3c62 100644 --- a/src/Nest/Mapping/TypeMapping.cs +++ b/src/Nest/Mapping/TypeMapping.cs @@ -269,9 +269,12 @@ public TypeMappingDescriptor DisableIndexField(bool? disabled = true) => public TypeMappingDescriptor RoutingField(Func, IRoutingField> routingFieldSelector) => Assign(routingFieldSelector, (a, v) => a.RoutingField = v?.Invoke(new RoutingFieldDescriptor())); + public TypeMappingDescriptor RuntimeFields(Func, IPromise> runtimeFieldsSelector) => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); + /// - public TypeMappingDescriptor RuntimeFields(Func> runtimeFieldsSelector) => - Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); + public TypeMappingDescriptor RuntimeFields(Func, IPromise> runtimeFieldsSelector) where TDocument : class => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); /// public TypeMappingDescriptor FieldNamesField(Func, IFieldNamesField> fieldNamesFieldSelector) => diff --git a/src/Nest/Search/Search/SearchRequest.cs b/src/Nest/Search/Search/SearchRequest.cs index c2be092d16d..13ab70e464c 100644 --- a/src/Nest/Search/Search/SearchRequest.cs +++ b/src/Nest/Search/Search/SearchRequest.cs @@ -38,6 +38,13 @@ public partial interface ISearchRequest : ITypedSearchRequest [DataMember(Name = "explain")] bool? Explain { get; set; } + /// + /// BETA: Allows for retrieving a list of document fields in the search response. + /// This functionality is in beta and is subject to change. + /// + [DataMember(Name = "fields")] + Fields Fields { get; set; } + /// /// The starting from index of the hits to return. Defaults to 0. /// @@ -172,6 +179,12 @@ public partial interface ISearchRequest : ITypedSearchRequest /// [DataMember(Name = "pit")] IPointInTime PointInTime { get; set; } + + /// + /// Specifies runtime fields which exist only as part of the query. + /// + [DataMember(Name = "runtime_mappings")] + IRuntimeFields RuntimeFields { get; set; } } [ReadAs(typeof(SearchRequest<>))] @@ -198,6 +211,8 @@ public partial class SearchRequest /// public bool? Explain { get; set; } /// + public Fields Fields { get; set; } + /// public int? From { get; set; } /// public IHighlight Highlight { get; set; } @@ -242,6 +257,8 @@ public partial class SearchRequest public bool? Version { get; set; } /// public IPointInTime PointInTime { get; set; } + /// + public IRuntimeFields RuntimeFields { get; set; } protected override HttpMethod HttpMethod => RequestState.RequestParameters?.ContainsQueryString("source") == true @@ -285,6 +302,7 @@ public partial class SearchDescriptor where TInferDocument : cla IFieldCollapse ISearchRequest.Collapse { get; set; } Fields ISearchRequest.DocValueFields { get; set; } bool? ISearchRequest.Explain { get; set; } + Fields ISearchRequest.Fields { get; set; } int? ISearchRequest.From { get; set; } IHighlight ISearchRequest.Highlight { get; set; } IDictionary ISearchRequest.IndicesBoost { get; set; } @@ -307,6 +325,7 @@ public partial class SearchDescriptor where TInferDocument : cla bool? ISearchRequest.TrackTotalHits { get; set; } bool? ISearchRequest.Version { get; set; } IPointInTime ISearchRequest.PointInTime { get; set; } + IRuntimeFields ISearchRequest.RuntimeFields { get; set; } protected sealed override void RequestDefaults(SearchRequestParameters parameters) => TypedKeys(); @@ -335,6 +354,17 @@ public SearchDescriptor Source(Func public SearchDescriptor Take(int? take) => Size(take); + /// + public SearchDescriptor Fields(Func, IPromise> fields) => + Assign(fields, (a, v) => a.Fields = v?.Invoke(new FieldsDescriptor())?.Value); + + /// + public SearchDescriptor Fields(Func, IPromise> fields) where TSource : class => + Assign(fields, (a, v) => a.Fields = v?.Invoke(new FieldsDescriptor())?.Value); + + /// + public SearchDescriptor Fields(Fields fields) => Assign(fields, (a, v) => a.DocValueFields = v); + /// public SearchDescriptor From(int? from) => Assign(from, (a, v) => a.From = v); @@ -476,6 +506,14 @@ public SearchDescriptor PointInTime(string pitId) public SearchDescriptor PointInTime(string pitId, Func pit) => Assign(pit, (a, v) => a.PointInTime = v?.Invoke(new PointInTimeDescriptor(pitId))); + /// + public SearchDescriptor RuntimeFields(Func, IPromise> runtimeFieldsSelector) => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); + + /// + public SearchDescriptor RuntimeFields(Func, IPromise> runtimeFieldsSelector) where TSource : class => + Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value); + protected override string ResolveUrl(RouteValues routeValues, IConnectionSettingsValues settings) { if (Self.PointInTime is object && !string.IsNullOrEmpty(Self.PointInTime.Id) && routeValues.ContainsKey("index")) diff --git a/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs b/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs index c02864a50ce..6bd1a751241 100644 --- a/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs +++ b/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs @@ -222,22 +222,37 @@ public static ITypeMapping ProjectMappings(MappingsDescriptor map) => map .Map(ProjectTypeMappings); #pragma warning restore 618 - public static ITypeMapping ProjectTypeMappings(TypeMappingDescriptor m) => m - .RoutingField(r => r.Required()) - .AutoMap() - .Properties(ProjectProperties) - .Properties(props => props - .Object(o => o - .AutoMap() - .Name(p => p.Committer) - .Properties(DeveloperProperties) - .Dynamic() - ) - .Text(t => t - .Name(p => p.ProjectName) - .Index(false) - ) - ); + public static ITypeMapping ProjectTypeMappings(TypeMappingDescriptor mapping) + { + mapping + .RoutingField(r => r.Required()) + .AutoMap() + .Properties(ProjectProperties) + .Properties(props => props + .Object(o => o + .AutoMap() + .Name(p => p.Committer) + .Properties(DeveloperProperties) + .Dynamic() + ) + .Text(t => t + .Name(p => p.ProjectName) + .Index(false) + ) + ); + + // runtime fields are a new feature added in 7.11.0 + if (TestConfiguration.Instance.InRange(">=7.11.0")) + { + mapping.RuntimeFields(rf => rf + .RuntimeField(r => r.StartedOnDayOfWeek, FieldType.Keyword, rtf => rtf + .Script("if (doc['startedOn'].size() != 0) {emit(doc['startedOn'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))}")) + .RuntimeField(r => r.ThirtyDaysFromStarted, FieldType.Date, rtf => rtf + .Script("if (doc['startedOn'].size() != 0) {emit(doc['startedOn'].value.plusDays(30).toEpochMilli())}"))); + } + + return mapping; + } public static IAnalysis ProjectAnalysisSettings(AnalysisDescriptor analysis) { diff --git a/tests/Tests.Domain/Project.cs b/tests/Tests.Domain/Project.cs index 646c80964cc..e7a5131275f 100644 --- a/tests/Tests.Domain/Project.cs +++ b/tests/Tests.Domain/Project.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using Bogus; using Elasticsearch.Net; using Nest; @@ -28,6 +29,15 @@ public class LabelActivity public long? Created { get; set; } } + public class ProjectRuntimeFields + { + [DataMember(Name = "runtime_started_on_day_of_week")] + public string StartedOnDayOfWeek { get; set; } + + [DataMember(Name = "runtime_thirty_days_after_started")] + public string ThirtyDaysFromStarted { get; set; } + } + public class Project { public static string TypeName = "project"; @@ -66,7 +76,7 @@ public class Project public StateOfBeing State { get; set; } public CompletionField Suggest { get; set; } public IEnumerable Tags { get; set; } - + public string Type => TypeName; //the first applies when using internal source serializer the latter when using JsonNetSourceSerializer diff --git a/tests/Tests/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs b/tests/Tests/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs index 3322bcab1db..fcae1eea90f 100644 --- a/tests/Tests/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs +++ b/tests/Tests/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs @@ -424,5 +424,69 @@ public void OverridingAutoMappedAttributes() // hide Expect(expected).FromRequest(createIndexResponse); } + + /** + * [[mapping-runtime-fields]] + * ==== Mapping runtime fields + * + * A {ref_current}/runtime.html[runtime field] is a field that is evaluated at query time. Runtime fields may + * be defined in the mapping of an index. + * + * In this example, we'll define a `CompanyRuntimeFields` class with a single property which we may then use in + * the strongly-typed runtime field mapping. + */ + + public class CompanyRuntimeFields + { + public string BirthDayOfWeek { get; set; } + } + + [U] + public void MappingRuntimeFields() + { + var createIndexResponse = _client.Indices.Create("myindex", c => c + .Map(m => m + .RuntimeFields(rtf => rtf //<1> Use the `CompanyRuntimeFields` class as the generic argument + .RuntimeField(f => f.BirthDayOfWeek, FieldType.Keyword, f => f.Script("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"))) //<2> Use the `BirthDayOfWeek` property as the runtime field name + ) + ); + + //json + var expected = new + { + mappings = new + { + runtime = new + { + birthDayOfWeek = new + { + type = "keyword", + script = new + { + lang = "painless", + source = "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))" + } + } + } + } + }; + + //hide + Expect(expected).FromRequest(createIndexResponse); + + /** + * It's not necessary to define a type for the runtime field mapping. Runtime fields can optionally be defined + * by providing a `string` name. + */ + createIndexResponse = _client.Indices.Create("myindex", c => c + .Map(m => m + .RuntimeFields(rtf => rtf + .RuntimeField("birthDayOfWeek", FieldType.Keyword, f => f.Script("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"))) + ) + ); + + //hide + Expect(expected).FromRequest(createIndexResponse); + } } } diff --git a/tests/Tests/Search/Search/SearchApiTests.cs b/tests/Tests/Search/Search/SearchApiTests.cs index 5b9ceeac102..36060a22583 100644 --- a/tests/Tests/Search/Search/SearchApiTests.cs +++ b/tests/Tests/Search/Search/SearchApiTests.cs @@ -649,4 +649,84 @@ protected override LazyResponses ClientUsage() => Calls( (c, r) => c.SearchAsync(r) ); } + + [SkipVersion("<7.11.0", "Runtime fields added in Elasticsearch 7.11.0")] + public class SearchApiRuntimeFieldsTests : SearchApiTests + { + private const string RuntimeFieldName = "search_runtime_field"; + private const string RuntimeFieldScript = "if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}"; + + public SearchApiRuntimeFieldsTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override object ExpectJson => new + { + size = 5, + query = new + { + match_all = new { } + }, + fields = new object[] + { + "runtime_started_on_day_of_week", + new + { + field = "runtime_thirty_days_after_started", + format = DateFormat.basic_date + }, + "search_runtime_field" + }, + runtime_mappings = new + { + search_runtime_field = new + { + script = new + { + lang = "painless", + source = RuntimeFieldScript + }, + type = "keyword" + } + } + }; + + protected override Func, ISearchRequest> Fluent => s => s + .Size(5) + .Query(q => q + .MatchAll() + ) + .Fields(fs => fs + .Field(f => f.StartedOnDayOfWeek) + .Field(f => f.ThirtyDaysFromStarted, format: DateFormat.basic_date) + .Field(RuntimeFieldName) + ) + .RuntimeFields(rtf => rtf.RuntimeField(RuntimeFieldName, FieldType.Keyword, r => r.Script(RuntimeFieldScript))); + + protected override SearchRequest Initializer => new() + { + Size = 5, + Query = new QueryContainer(new MatchAllQuery()), + Fields = Infer.Field(p => p.StartedOnDayOfWeek) + .And(p => p.ThirtyDaysFromStarted, format: DateFormat.basic_date) + .And(RuntimeFieldName), + RuntimeFields = new RuntimeFields + { + { RuntimeFieldName, new RuntimeField + { + Type = FieldType.Keyword, + Script = new PainlessScript(RuntimeFieldScript) + } + } + } + }; + + protected override void ExpectResponse(ISearchResponse response) + { + response.Hits.Count.Should().BeGreaterThan(0); + response.Hits.First().Should().NotBeNull(); + response.Hits.First().Type.Should().NotBeNullOrWhiteSpace(); + response.Hits.First().Fields.ValueOf(p => p.StartedOnDayOfWeek).Should().NotBeNullOrEmpty(); + response.Hits.First().Fields.ValueOf(p => p.ThirtyDaysFromStarted).Should().NotBeNullOrEmpty(); + response.Hits.First().Fields[RuntimeFieldName].As().FirstOrDefault().Should().NotBeNullOrEmpty(); + } + } } diff --git a/tests/Tests/Search/SearchingRuntimeFields.doc.cs b/tests/Tests/Search/SearchingRuntimeFields.doc.cs new file mode 100644 index 00000000000..34a4934c7d1 --- /dev/null +++ b/tests/Tests/Search/SearchingRuntimeFields.doc.cs @@ -0,0 +1,170 @@ +// 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 Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Nest; +using Tests.Core.Client; +using Tests.Domain; +using static Tests.Core.Serialization.SerializationTestHelper; + +namespace Tests.Search +{ + /** + * === Searching runtime fields + * + * Runtime fields can be returned with search requests by specifying the fields using `.Fields` + * on the search request. + * + * [WARNING] + * -- + * This functionality is in beta and is subject to change. The design and code is less mature + * than official GA features and is being provided as-is with no warranties. Beta features + * are not subject to the support SLA of official GA features. + * -- + * + */ + public class SearchingRuntimeFields + { + private readonly IElasticClient _client = TestClient.DisabledStreaming; + + [U] + public void RetrievingRuntimeFields() + { + var searchResponse = _client.Search(s => s + .Query(q => q + .MatchAll() + ) + .Fields(fs => fs + .Field(f => f.StartedOnDayOfWeek) + .Field(f => f.ThirtyDaysFromStarted, format: DateFormat.basic_date) + ) + ); + + /** + * which serializes to the following JSON + */ + //json + var expected = new + { + query = new + { + match_all = new { } + }, + fields = new object[] + { + "runtime_started_on_day_of_week", + new + { + field = "runtime_thirty_days_after_started", + format = "basic_date" + } + }, + }; + + //hide + Expect(expected).FromRequest(searchResponse); + + /** + * The previous example used the Fluent API to express the query. NEST also exposes an + * Object Initializer syntax to compose queries + */ + var searchRequest = new SearchRequest + { + Query = new MatchAllQuery(), + Fields = Infer.Field(p => p.StartedOnDayOfWeek) //<1> Here we infer the field name from a property on a POCO class + .And(p => p.ThirtyDaysFromStarted, format: DateFormat.basic_date) //<2> For runtime fields which return a date, a format may be specified. + }; + + searchResponse = _client.Search(searchRequest); + + //hide + Expect(expected).FromRequest(searchResponse); + } + + /**==== Defining runtime fields + * + * You may define runtime fields that exist only as part of a query by specifying `.RuntimeFields` on + * the search request. You may return this field using `.Fields` or use it for an aggregation. + */ + [U] + public void SearchQueryRuntimeFields() + { + var searchResponse = _client.Search(s => s + .Query(q => q + .MatchAll() + ) + .Fields(fs => fs + .Field(f => f.StartedOnDayOfWeek) + .Field(f => f.ThirtyDaysFromStarted, format: DateFormat.basic_date) + .Field("search_runtime_field") + ) + .RuntimeFields(rtf => rtf.RuntimeField("search_runtime_field", FieldType.Keyword, r => r + .Script("if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}"))) + ); + + /**which yields the following query JSON + * + */ + // json + var expected = new + { + query = new + { + match_all = new { } + }, + fields = new object[] + { + "runtime_started_on_day_of_week", + new + { + field = "runtime_thirty_days_after_started", + format = "basic_date" + }, + "search_runtime_field" + }, + runtime_mappings = new + { + search_runtime_field = new + { + script = new + { + lang = "painless", + source = "if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}" + }, + type = "keyword" + } + } + }; + + //hide + Expect(expected).FromRequest(searchResponse); + + /** + * The previous example used the Fluent API to express the query. Here is the same query using the + * Object Initializer syntax. + */ + var searchRequest = new SearchRequest + { + Query = new MatchAllQuery(), + Fields = Infer.Field(p => p.StartedOnDayOfWeek) + .And(p => p.ThirtyDaysFromStarted, format: DateFormat.basic_date) + .And("search_runtime_field"), + RuntimeFields = new RuntimeFields + { + { "search_runtime_field", new RuntimeField + { + Type = FieldType.Keyword, + Script = new PainlessScript("if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}") + } + } + } + }; + + searchResponse = _client.Search(searchRequest); + + //hide + Expect(expected).FromRequest(searchResponse); + } + } +}