Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement runtime fields on searches #5259

Merged
merged 2 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion docs/client-concepts/high-level/mapping/fluent-mapping.asciidoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/7.x
:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/7.11

:github: https://github.com/elastic/elasticsearch-net

Expand Down Expand Up @@ -371,3 +371,59 @@ As demonstrated, by calling `.AutoMap()` inside of the `.Nested<Employee>` 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<Company>(m => m
.RuntimeFields<CompanyRuntimeFields>(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<Company>(m => m
.RuntimeFields(rtf => rtf
.RuntimeField("birthDayOfWeek", FieldType.Keyword, f => f.Script("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))")))
)
);
----

2 changes: 2 additions & 0 deletions docs/high-level.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
150 changes: 150 additions & 0 deletions docs/search/searching-runtime-fields.asciidoc
Original file line number Diff line number Diff line change
@@ -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<Project>(s => s
.Query(q => q
.MatchAll()
)
.Fields<ProjectRuntimeFields>(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<Project>
{
Query = new MatchAllQuery(),
Fields = Infer.Field<ProjectRuntimeFields>(p => p.StartedOnDayOfWeek) <1>
.And<ProjectRuntimeFields>(p => p.ThirtyDaysFromStarted, format: DateFormat.basic_date) <2>
};

searchResponse = _client.Search<Project>(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<Project>(s => s
.Query(q => q
.MatchAll()
)
.Fields<ProjectRuntimeFields>(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<Project>
{
Query = new MatchAllQuery(),
Fields = Infer.Field<ProjectRuntimeFields>(p => p.StartedOnDayOfWeek)
.And<ProjectRuntimeFields>(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<Project>(searchRequest);
----

Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,13 @@ public PutMappingDescriptor<TDocument> RoutingField(Func<RoutingFieldDescriptor<
Assign(routingFieldSelector, (a, v) => a.RoutingField = v?.Invoke(new RoutingFieldDescriptor<TDocument>()));

/// <inheritdoc cref="ITypeMapping.RuntimeFields" />
public PutMappingDescriptor<TDocument> RuntimeFields(Func<RuntimeFieldsDescriptor, IPromise<IRuntimeFields>> runtimeFieldsSelector) =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value);
public PutMappingDescriptor<TDocument> RuntimeFields(Func<RuntimeFieldsDescriptor<TDocument>, IPromise<IRuntimeFields>> runtimeFieldsSelector) =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor<TDocument>())?.Value);

/// <inheritdoc cref="ITypeMapping.RuntimeFields" />
public PutMappingDescriptor<TDocument> RuntimeFields<TSource>(Func<RuntimeFieldsDescriptor<TSource>, IPromise<IRuntimeFields>> runtimeFieldsSelector) where TSource : class =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor<TSource>())?.Value);

/// <inheritdoc cref="ITypeMapping.FieldNamesField" />
public PutMappingDescriptor<TDocument> FieldNamesField(Func<FieldNamesFieldDescriptor<TDocument>, IFieldNamesField> fieldNamesFieldSelector) =>
Assign(fieldNamesFieldSelector, (a, v) => a.FieldNamesField = v.Invoke(new FieldNamesFieldDescriptor<TDocument>()));
Expand Down
27 changes: 17 additions & 10 deletions src/Nest/Mapping/RuntimeFields/RuntimeFields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,40 @@

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Elasticsearch.Net.Utf8Json;

namespace Nest
{
[JsonFormatter(typeof(VerbatimDictionaryKeysFormatter<RuntimeFields, IRuntimeFields, string, IRuntimeField>))]
public interface IRuntimeFields : IIsADictionary<string, IRuntimeField> { }
[JsonFormatter(typeof(VerbatimDictionaryKeysFormatter<RuntimeFields, IRuntimeFields, Field, IRuntimeField>))]
public interface IRuntimeFields : IIsADictionary<Field, IRuntimeField> { }

public class RuntimeFields : IsADictionaryBase<string, IRuntimeField>, IRuntimeFields
public class RuntimeFields : IsADictionaryBase<Field, IRuntimeField>, IRuntimeFields
{
public RuntimeFields() { }

public RuntimeFields(IDictionary<string, IRuntimeField> container) : base(container) { }
public RuntimeFields(IDictionary<Field, IRuntimeField> container) : base(container) { }

public RuntimeFields(Dictionary<string, IRuntimeField> container) : base(container) { }
public RuntimeFields(Dictionary<Field, IRuntimeField> 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<RuntimeFieldsDescriptor, RuntimeFields, string, IRuntimeField>
public class RuntimeFieldsDescriptor<TDocument>
: IsADictionaryDescriptorBase<RuntimeFieldsDescriptor<TDocument>, RuntimeFields, Field, IRuntimeField> where TDocument : class
{
public RuntimeFieldsDescriptor() : base(new RuntimeFields()) { }

public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type, Func<RuntimeFieldDescriptor, IRuntimeField> selector) =>
public RuntimeFieldsDescriptor<TDocument> RuntimeField(string name, FieldType type, Func<RuntimeFieldDescriptor, IRuntimeField> selector) =>
Assign(name, selector?.Invoke(new RuntimeFieldDescriptor(type)));

public RuntimeFieldsDescriptor RuntimeField(string name, FieldType type) =>
public RuntimeFieldsDescriptor<TDocument> RuntimeField(Expression<Func<TDocument, Field>> field, FieldType type, Func<RuntimeFieldDescriptor, IRuntimeField> selector) =>
Assign(field, selector?.Invoke(new RuntimeFieldDescriptor(type)));

public RuntimeFieldsDescriptor<TDocument> RuntimeField(string name, FieldType type) =>
Assign(name, new RuntimeFieldDescriptor(type));

public RuntimeFieldsDescriptor<TDocument> RuntimeField(Expression<Func<TDocument, Field>> field, FieldType type) =>
Assign(field, new RuntimeFieldDescriptor(type));
}
}
7 changes: 5 additions & 2 deletions src/Nest/Mapping/TypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,12 @@ public TypeMappingDescriptor<T> DisableIndexField(bool? disabled = true) =>
public TypeMappingDescriptor<T> RoutingField(Func<RoutingFieldDescriptor<T>, IRoutingField> routingFieldSelector) =>
Assign(routingFieldSelector, (a, v) => a.RoutingField = v?.Invoke(new RoutingFieldDescriptor<T>()));

public TypeMappingDescriptor<T> RuntimeFields(Func<RuntimeFieldsDescriptor<T>, IPromise<IRuntimeFields>> runtimeFieldsSelector) =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor<T>())?.Value);

/// <inheritdoc cref="ITypeMapping.RuntimeFields" />
public TypeMappingDescriptor<T> RuntimeFields(Func<RuntimeFieldsDescriptor, IPromise<IRuntimeFields>> runtimeFieldsSelector) =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor())?.Value);
public TypeMappingDescriptor<T> RuntimeFields<TDocument>(Func<RuntimeFieldsDescriptor<TDocument>, IPromise<IRuntimeFields>> runtimeFieldsSelector) where TDocument : class =>
Assign(runtimeFieldsSelector, (a, v) => a.RuntimeFields = v?.Invoke(new RuntimeFieldsDescriptor<TDocument>())?.Value);

/// <inheritdoc cref="ITypeMapping.FieldNamesField" />
public TypeMappingDescriptor<T> FieldNamesField(Func<FieldNamesFieldDescriptor<T>, IFieldNamesField> fieldNamesFieldSelector) =>
Expand Down
Loading