diff --git a/docs/common-options/date-math/date-math-expressions.asciidoc b/docs/common-options/date-math/date-math-expressions.asciidoc index 0315dd2eac5..075531c08b7 100644 --- a/docs/common-options/date-math/date-math-expressions.asciidoc +++ b/docs/common-options/date-math/date-math-expressions.asciidoc @@ -83,14 +83,28 @@ anchor will be an actual `DateTime`, even after a serialization/deserialization [source,csharp] ---- var date = new DateTime(2015, 05, 05); -Expect("2015-05-05T00:00:00") - .WhenSerializing(date) - .AssertSubject(dateMath => ((IDateMath)dateMath) - .Anchor.Match( - d => d.Should().Be(date), - s => s.Should().BeNull() - ) - ); +---- + +will serialize to + +[source,javascript] +---- +"2015-05-05T00:00:00" +---- + +When the `DateTime` is local or UTC, the time zone information is included. +For example, for a UTC `DateTime` + +[source,csharp] +---- +var utcDate = new DateTime(2015, 05, 05, 0, 0, 0, DateTimeKind.Utc); +---- + +will serialize to + +[source,javascript] +---- +"2015-05-05T00:00:00Z" ---- ==== Complex expressions diff --git a/src/CodeGeneration/DocGenerator/StringExtensions.cs b/src/CodeGeneration/DocGenerator/StringExtensions.cs index 0f61e90ff8a..0e461d1ffff 100644 --- a/src/CodeGeneration/DocGenerator/StringExtensions.cs +++ b/src/CodeGeneration/DocGenerator/StringExtensions.cs @@ -213,7 +213,7 @@ public static string RemoveNumberOfLeadingTabsOrSpacesAfterNewline(this string i public static string[] SplitOnNewLines(this string input, StringSplitOptions options) => input.Split(new[] { "\r\n", "\n" }, options); - public static bool TryGetJsonForAnonymousType(this string anonymousTypeString, out string json) + public static bool TryGetJsonForExpressionSyntax(this string anonymousTypeString, out string json) { json = null; diff --git a/src/CodeGeneration/DocGenerator/SyntaxNodeExtensions.cs b/src/CodeGeneration/DocGenerator/SyntaxNodeExtensions.cs index e57c2bf3ea6..28316f49a48 100644 --- a/src/CodeGeneration/DocGenerator/SyntaxNodeExtensions.cs +++ b/src/CodeGeneration/DocGenerator/SyntaxNodeExtensions.cs @@ -72,11 +72,10 @@ public static bool TryGetJsonForSyntaxNode(this SyntaxNode node, out string json json = null; // find the first anonymous object or new object expression - var creationExpressionSyntax = node.DescendantNodes() - .FirstOrDefault(n => n is AnonymousObjectCreationExpressionSyntax || n is ObjectCreationExpressionSyntax); + var syntax = node.DescendantNodes() + .FirstOrDefault(n => n is AnonymousObjectCreationExpressionSyntax || n is ObjectCreationExpressionSyntax || n is LiteralExpressionSyntax); - return creationExpressionSyntax != null && - creationExpressionSyntax.ToFullString().TryGetJsonForAnonymousType(out json); + return syntax != null && syntax.ToFullString().TryGetJsonForExpressionSyntax(out json); } /// diff --git a/src/Elasticsearch.Net/Connection/Content/RequestDataContent.cs b/src/Elasticsearch.Net/Connection/Content/RequestDataContent.cs index d624e90f4cc..a85afcce514 100644 --- a/src/Elasticsearch.Net/Connection/Content/RequestDataContent.cs +++ b/src/Elasticsearch.Net/Connection/Content/RequestDataContent.cs @@ -25,8 +25,7 @@ namespace Elasticsearch.Net internal class RequestDataContent : HttpContent { private readonly RequestData _requestData; - private readonly Func _onStreamAvailable; - + private readonly Func _onStreamAvailable; public RequestDataContent(RequestData requestData) { @@ -35,12 +34,17 @@ public RequestDataContent(RequestData requestData) if (requestData.HttpCompression) Headers.ContentEncoding.Add("gzip"); - Task OnStreamAvailable(PostData data, Stream stream, HttpContent content, TransportContext context) + Task OnStreamAvailable(RequestData data, Stream stream, HttpContent content, TransportContext context) { + if (data.HttpCompression) + stream = new GZipStream(stream, CompressionMode.Compress, false); + using(stream) - data.Write(stream, requestData.ConnectionSettings); + data.PostData.Write(stream, data.ConnectionSettings); + return Task.CompletedTask; } + _onStreamAvailable = OnStreamAvailable; } public RequestDataContent(RequestData requestData, CancellationToken token) @@ -50,11 +54,15 @@ public RequestDataContent(RequestData requestData, CancellationToken token) if (requestData.HttpCompression) Headers.ContentEncoding.Add("gzip"); - async Task OnStreamAvailable(PostData data, Stream stream, HttpContent content, TransportContext context) + async Task OnStreamAvailable(RequestData data, Stream stream, HttpContent content, TransportContext context) { + if (data.HttpCompression) + stream = new GZipStream(stream, CompressionMode.Compress, false); + using (stream) - await data.WriteAsync(stream, requestData.ConnectionSettings, token).ConfigureAwait(false); + await data.PostData.WriteAsync(stream, data.ConnectionSettings, token).ConfigureAwait(false); } + _onStreamAvailable = OnStreamAvailable; } @@ -69,16 +77,9 @@ async Task OnStreamAvailable(PostData data, Stream stream, HttpContent content, [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is passed as task result.")] protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) { - - var data = _requestData.PostData; - if (data == null) return; - var serializeToStreamTask = new TaskCompletionSource(); - - if (_requestData.HttpCompression) - stream = new GZipStream(stream, CompressionMode.Compress, false); var wrappedStream = new CompleteTaskOnCloseStream(stream, serializeToStreamTask); - await _onStreamAvailable(data, wrappedStream, this, context).ConfigureAwait(false); + await _onStreamAvailable(_requestData, wrappedStream, this, context).ConfigureAwait(false); await serializeToStreamTask.Task.ConfigureAwait(false); } @@ -111,7 +112,6 @@ protected override void Dispose(bool disposing) base.Dispose(); } - public override void Close() => _serializeToStreamTask.TrySetResult(true); } @@ -193,6 +193,8 @@ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, As public override void EndWrite(IAsyncResult asyncResult) => _innerStream.EndWrite(asyncResult); public override void WriteByte(byte value) => _innerStream.WriteByte(value); + + public override void Close() => _innerStream.Close(); } } } diff --git a/src/Elasticsearch.Net/Connection/HttpConnection.cs b/src/Elasticsearch.Net/Connection/HttpConnection.cs index 734e7e6d3b0..c9b36d209b0 100644 --- a/src/Elasticsearch.Net/Connection/HttpConnection.cs +++ b/src/Elasticsearch.Net/Connection/HttpConnection.cs @@ -57,7 +57,10 @@ public virtual TResponse Request(RequestData requestData) try { var requestMessage = CreateHttpRequestMessage(requestData); - SetContent(requestMessage, requestData); + + if (requestData.PostData != null) + SetContent(requestMessage, requestData); + using(requestMessage?.Content ?? (IDisposable)Stream.Null) using (var d = DiagnosticSource.Diagnose(DiagnosticSources.HttpConnection.SendAndReceiveHeaders, requestData)) { @@ -107,8 +110,11 @@ public virtual async Task RequestAsync(RequestData request try { var requestMessage = CreateHttpRequestMessage(requestData); - SetAsyncContent(requestMessage, requestData, cancellationToken); - using(requestMessage?.Content ?? (IDisposable)Stream.Null) + + if (requestData.PostData != null) + SetAsyncContent(requestMessage, requestData, cancellationToken); + + using(requestMessage?.Content ?? (IDisposable)Stream.Null) using (var d = DiagnosticSource.Diagnose(DiagnosticSources.HttpConnection.SendAndReceiveHeaders, requestData)) { responseMessage = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); diff --git a/src/Elasticsearch.Net/Extensions/StringBuilderCache.cs b/src/Elasticsearch.Net/Extensions/StringBuilderCache.cs new file mode 100644 index 00000000000..873dbd9f8d0 --- /dev/null +++ b/src/Elasticsearch.Net/Extensions/StringBuilderCache.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; + +namespace Elasticsearch.Net.Extensions +{ + /// Provide a cached reusable instance of stringbuilder per thread. + internal static class StringBuilderCache + { + private const int DefaultCapacity = 16; // == StringBuilder.DefaultCapacity + + // The value 360 was chosen in discussion with performance experts as a compromise between using + // as little memory per thread as possible and still covering a large part of short-lived + // StringBuilder creations on the startup path of VS designers. + private const int MaxBuilderSize = 360; + + // WARNING: We allow diagnostic tools to directly inspect this member (t_cachedInstance). + // See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details. + // Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools. + // Get in touch with the diagnostics team if you have questions. + [ThreadStatic] + private static StringBuilder _cachedInstance; + + /// Get a StringBuilder for the specified capacity. + /// If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied. + public static StringBuilder Acquire(int capacity = DefaultCapacity) + { + if (capacity <= MaxBuilderSize) + { + var sb = _cachedInstance; + if (sb != null) + { + // Avoid stringbuilder block fragmentation by getting a new StringBuilder + // when the requested size is larger than the current capacity + if (capacity <= sb.Capacity) + { + _cachedInstance = null; + sb.Clear(); + return sb; + } + } + } + + return new StringBuilder(capacity); + } + + /// Place the specified builder in the cache if it is not too big. + public static void Release(StringBuilder sb) + { + if (sb.Capacity <= MaxBuilderSize) _cachedInstance = sb; + } + + /// ToString() the stringbuilder, Release it to the cache, and return the resulting string. + public static string GetStringAndRelease(StringBuilder sb) + { + var result = sb.ToString(); + Release(sb); + return result; + } + } +} diff --git a/src/Nest/Aggregations/Metric/WeightedAverage/WeightedAverageValue.cs b/src/Nest/Aggregations/Metric/WeightedAverage/WeightedAverageValue.cs index aa0a4d83d13..5dba5a6d7de 100644 --- a/src/Nest/Aggregations/Metric/WeightedAverage/WeightedAverageValue.cs +++ b/src/Nest/Aggregations/Metric/WeightedAverage/WeightedAverageValue.cs @@ -7,7 +7,7 @@ namespace Nest { /// - /// The configuration for a field or scrip that provides a value or weight + /// The configuration for a field or script that provides a value or weight /// for /// [InterfaceDataContract] @@ -104,6 +104,9 @@ public enum ValueType /// A date value [EnumMember(Value = "date")] Date, + /// A date nanos value + [EnumMember(Value = "date_nanos")] DateNanos, + /// An IP value [EnumMember(Value = "ip")] Ip, diff --git a/src/Nest/CommonOptions/DateMath/DateMath.cs b/src/Nest/CommonOptions/DateMath/DateMath.cs index ee32d7d0b8e..c08436564d5 100644 --- a/src/Nest/CommonOptions/DateMath/DateMath.cs +++ b/src/Nest/CommonOptions/DateMath/DateMath.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; using System.Text.RegularExpressions; using Elasticsearch.Net.Extensions; @@ -109,21 +110,50 @@ public override string ToString() } /// - /// Formats a to have a minimum of 3 decimal places if there - /// are sub second values + /// Formats a to have a minimum of 3 decimal places if there are sub second values /// - /// Fixes bug in Elasticsearch: https://github.com/elastic/elasticsearch/pull/41871 private static string ToMinThreeDecimalPlaces(DateTime dateTime) { - var format = dateTime.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFF"); + var builder = StringBuilderCache.Acquire(33); + var format = dateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFF", CultureInfo.InvariantCulture); + builder.Append(format); + // Fixes bug in Elasticsearch: https://github.com/elastic/elasticsearch/pull/41871 if (format.Length > 20 && format.Length < 23) { var diff = 23 - format.Length; - return $"{format}{new string('0', diff)}"; + for (int i = 0; i < diff; i++) + builder.Append('0'); } - return format; + switch (dateTime.Kind) + { + case DateTimeKind.Local: + var offset = TimeZoneInfo.Local.GetUtcOffset(dateTime); + if (offset >= TimeSpan.Zero) + builder.Append('+'); + else + { + builder.Append('-'); + offset = offset.Negate(); + } + + AppendTwoDigitNumber(builder, offset.Hours); + builder.Append(':'); + AppendTwoDigitNumber(builder, offset.Minutes); + break; + case DateTimeKind.Utc: + builder.Append('Z'); + break; + } + + return StringBuilderCache.GetStringAndRelease(builder); + } + + private static void AppendTwoDigitNumber(StringBuilder result, int val) + { + result.Append((char)('0' + (val / 10))); + result.Append((char)('0' + (val % 10))); } } diff --git a/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs b/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs index 72a09cece37..be72f29cf0d 100644 --- a/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs +++ b/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs @@ -18,6 +18,9 @@ public IProperty Completion(Func, ICompletionPro public IProperty Date(Func, IDateProperty> selector) => selector?.Invoke(new DatePropertyDescriptor()); + public IProperty DateNanos(Func, IDateNanosProperty> selector) => + selector?.Invoke(new DateNanosPropertyDescriptor()); + public IProperty DateRange(Func, IDateRangeProperty> selector) => selector?.Invoke(new DateRangePropertyDescriptor()); @@ -247,6 +250,30 @@ public IProperty Scalar(Expression>> field, ) => selector.InvokeOrDefault(new DatePropertyDescriptor().Name(field)); + public IProperty ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null) => + selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + + public IProperty ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null) => + selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + + public IProperty ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null) => + selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + + public IProperty ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null) => + selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + + public IProperty ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null) => + selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + + public IProperty ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null) => + selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + + public IProperty ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null + ) => selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + + public IProperty ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null + ) => selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field)); + public IProperty Scalar(Expression> field, Func, IBooleanProperty> selector = null) => selector.InvokeOrDefault(new BooleanPropertyDescriptor().Name(field)); diff --git a/src/Nest/Mapping/Types/Core/DateNanos/DateNanosAttribute.cs b/src/Nest/Mapping/Types/Core/DateNanos/DateNanosAttribute.cs new file mode 100644 index 00000000000..52e1e3ae373 --- /dev/null +++ b/src/Nest/Mapping/Types/Core/DateNanos/DateNanosAttribute.cs @@ -0,0 +1,47 @@ +using System; + +namespace Nest +{ + public class DateNanosAttribute : ElasticsearchDocValuesPropertyAttributeBase, IDateNanosProperty + { + public DateNanosAttribute() : base(FieldType.DateNanos) { } + + public double Boost + { + get => Self.Boost.GetValueOrDefault(); + set => Self.Boost = value; + } + + public string Format + { + get => Self.Format; + set => Self.Format = value; + } + + public bool IgnoreMalformed + { + get => Self.IgnoreMalformed.GetValueOrDefault(); + set => Self.IgnoreMalformed = value; + } + + public bool Index + { + get => Self.Index.GetValueOrDefault(); + set => Self.Index = value; + } + + public DateTime NullValue + { + get => Self.NullValue.GetValueOrDefault(); + set => Self.NullValue = value; + } + + double? IDateNanosProperty.Boost { get; set; } + string IDateNanosProperty.Format { get; set; } + bool? IDateNanosProperty.IgnoreMalformed { get; set; } + + bool? IDateNanosProperty.Index { get; set; } + DateTime? IDateNanosProperty.NullValue { get; set; } + private IDateNanosProperty Self => this; + } +} diff --git a/src/Nest/Mapping/Types/Core/DateNanos/DateNanosProperty.cs b/src/Nest/Mapping/Types/Core/DateNanos/DateNanosProperty.cs new file mode 100644 index 00000000000..7ed498797b8 --- /dev/null +++ b/src/Nest/Mapping/Types/Core/DateNanos/DateNanosProperty.cs @@ -0,0 +1,102 @@ +using System; +using System.Diagnostics; +using System.Runtime.Serialization; +using Elasticsearch.Net.Utf8Json; + +namespace Nest +{ + [InterfaceDataContract] + public interface IDateNanosProperty : IDocValuesProperty + { + /// + /// Mapping field-level query time boosting. Accepts a floating point number, defaults to 1.0. + /// + [DataMember(Name = "boost")] + double? Boost { get; set; } + + /// + /// The date format(s) that can be parsed. Defaults to strict_date_optional_time||epoch_millis. + /// + /// + [DataMember(Name = "format")] + string Format { get; set; } + + /// + /// If true, malformed numbers are ignored. If false (default), malformed numbers throw an exception + /// and reject the whole document. + /// + [DataMember(Name = "ignore_malformed")] + bool? IgnoreMalformed { get; set; } + + /// + /// Should the field be searchable? Accepts true (default) and false. + /// + [DataMember(Name = "index")] + bool? Index { get; set; } + + /// + /// Accepts a date value in one of the configured format's + /// as the field which is substituted for any explicit null values. Defaults to null, + /// which means the field is treated as missing. + /// + [DataMember(Name = "null_value")] + DateTime? NullValue { get; set; } + } + + [DebuggerDisplay("{DebugDisplay}")] + public class DateNanosProperty : DocValuesPropertyBase, IDateNanosProperty + { + public DateNanosProperty() : base(FieldType.DateNanos) { } + + /// + public double? Boost { get; set; } + + /// + public string Format { get; set; } + + /// + public bool? IgnoreMalformed { get; set; } + + /// + public bool? Index { get; set; } + + /// + public DateTime? NullValue { get; set; } + + /// + public int? PrecisionStep { get; set; } + } + + [DebuggerDisplay("{DebugDisplay}")] + public class DateNanosPropertyDescriptor + : DocValuesPropertyDescriptorBase, IDateNanosProperty, T>, IDateNanosProperty + where T : class + { + public DateNanosPropertyDescriptor() : base(FieldType.DateNanos) { } + + double? IDateNanosProperty.Boost { get; set; } + + string IDateNanosProperty.Format { get; set; } + + bool? IDateNanosProperty.IgnoreMalformed { get; set; } + + bool? IDateNanosProperty.Index { get; set; } + + DateTime? IDateNanosProperty.NullValue { get; set; } + + /// + public DateNanosPropertyDescriptor Index(bool? index = true) => Assign(index, (a, v) => a.Index = v); + + /// + public DateNanosPropertyDescriptor Boost(double? boost) => Assign(boost, (a, v) => a.Boost = v); + + /// + public DateNanosPropertyDescriptor NullValue(DateTime? nullValue) => Assign(nullValue, (a, v) => a.NullValue = v); + + /// + public DateNanosPropertyDescriptor IgnoreMalformed(bool? ignoreMalformed = true) => Assign(ignoreMalformed, (a, v) => a.IgnoreMalformed = v); + + /// + public DateNanosPropertyDescriptor Format(string format) => Assign(format, (a, v) => a.Format = v); + } +} diff --git a/src/Nest/Mapping/Types/FieldType.cs b/src/Nest/Mapping/Types/FieldType.cs index e219461e885..f6588b32806 100644 --- a/src/Nest/Mapping/Types/FieldType.cs +++ b/src/Nest/Mapping/Types/FieldType.cs @@ -43,6 +43,9 @@ public enum FieldType [EnumMember(Value = "date")] Date, + [EnumMember(Value = "date_nanos")] + DateNanos, + [EnumMember(Value = "boolean")] Boolean, diff --git a/src/Nest/Mapping/Types/Properties-Scalar.cs b/src/Nest/Mapping/Types/Properties-Scalar.cs index 99997efb8bb..dfc970c21e5 100644 --- a/src/Nest/Mapping/Types/Properties-Scalar.cs +++ b/src/Nest/Mapping/Types/Properties-Scalar.cs @@ -115,6 +115,22 @@ public partial interface IPropertiesDescriptor TReturnType Scalar(Expression>> field, Func, IDateProperty> selector = null); + TReturnType ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null); + + TReturnType ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null); + + TReturnType ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null); + + TReturnType ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null); + + TReturnType ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null); + + TReturnType ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null); + + TReturnType ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null); + + TReturnType ScalarNanos(Expression>> field, Func, IDateNanosProperty> selector = null); + TReturnType Scalar(Expression> field, Func, IBooleanProperty> selector = null); TReturnType Scalar(Expression> field, Func, IBooleanProperty> selector = null); @@ -388,6 +404,42 @@ public PropertiesDescriptor Scalar(Expression SetProperty(selector.InvokeOrDefault(new DatePropertyDescriptor().Name(field))); + public PropertiesDescriptor ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + + public PropertiesDescriptor ScalarNanos(Expression> field, Func, IDateNanosProperty> selector = null) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + + public PropertiesDescriptor ScalarNanos(Expression>> field, + Func, IDateNanosProperty> selector = null + ) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + + public PropertiesDescriptor ScalarNanos(Expression>> field, + Func, IDateNanosProperty> selector = null + ) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + + public PropertiesDescriptor ScalarNanos(Expression> field, + Func, IDateNanosProperty> selector = null + ) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + + public PropertiesDescriptor ScalarNanos(Expression> field, + Func, IDateNanosProperty> selector = null + ) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + + public PropertiesDescriptor ScalarNanos(Expression>> field, + Func, IDateNanosProperty> selector = null + ) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + + public PropertiesDescriptor ScalarNanos(Expression>> field, + Func, IDateNanosProperty> selector = null + ) => + SetProperty(selector.InvokeOrDefault(new DateNanosPropertyDescriptor().Name(field))); + public PropertiesDescriptor Scalar(Expression> field, Func, IBooleanProperty> selector = null ) => SetProperty(selector.InvokeOrDefault(new BooleanPropertyDescriptor().Name(field))); diff --git a/src/Nest/Mapping/Types/Properties.cs b/src/Nest/Mapping/Types/Properties.cs index 591e8946832..068a5c011f8 100644 --- a/src/Nest/Mapping/Types/Properties.cs +++ b/src/Nest/Mapping/Types/Properties.cs @@ -58,6 +58,8 @@ public partial interface IPropertiesDescriptor TReturnType Date(Func, IDateProperty> selector); + TReturnType DateNanos(Func, IDateNanosProperty> selector); + TReturnType Boolean(Func, IBooleanProperty> selector); TReturnType Binary(Func, IBinaryProperty> selector); @@ -109,6 +111,8 @@ public PropertiesDescriptor() : base(new Properties()) { } public PropertiesDescriptor Date(Func, IDateProperty> selector) => SetProperty(selector); + public PropertiesDescriptor DateNanos(Func, IDateNanosProperty> selector) => SetProperty(selector); + public PropertiesDescriptor DateRange(Func, IDateRangeProperty> selector) => SetProperty(selector); public PropertiesDescriptor DoubleRange(Func, IDoubleRangeProperty> selector) => SetProperty(selector); diff --git a/src/Nest/Mapping/Types/PropertyFormatter.cs b/src/Nest/Mapping/Types/PropertyFormatter.cs index e1cf0d657ad..874a8538615 100644 --- a/src/Nest/Mapping/Types/PropertyFormatter.cs +++ b/src/Nest/Mapping/Types/PropertyFormatter.cs @@ -68,6 +68,7 @@ public IProperty Deserialize(ref JsonReader reader, IJsonFormatterResolver forma ((IProperty)numberProperty).Type = typeString; return numberProperty; case FieldType.Date: return Deserialize(ref segmentReader, formatterResolver); + case FieldType.DateNanos: return Deserialize(ref segmentReader, formatterResolver); case FieldType.Boolean: return Deserialize(ref segmentReader, formatterResolver); case FieldType.Binary: return Deserialize(ref segmentReader, formatterResolver); case FieldType.Object: return Deserialize(ref segmentReader, formatterResolver); @@ -124,6 +125,9 @@ public void Serialize(ref JsonWriter writer, IProperty value, IJsonFormatterReso case "date": Serialize(ref writer, value, formatterResolver); break; + case "date_nanos": + Serialize(ref writer, value, formatterResolver); + break; case "boolean": Serialize(ref writer, value, formatterResolver); break; diff --git a/src/Nest/Mapping/Visitor/IMappingVisitor.cs b/src/Nest/Mapping/Visitor/IMappingVisitor.cs index 31e0ba12750..48736caedb6 100644 --- a/src/Nest/Mapping/Visitor/IMappingVisitor.cs +++ b/src/Nest/Mapping/Visitor/IMappingVisitor.cs @@ -12,6 +12,8 @@ public interface IMappingVisitor void Visit(IDateProperty property); + void Visit(IDateNanosProperty property); + void Visit(IBooleanProperty property); void Visit(IBinaryProperty property); @@ -63,6 +65,8 @@ public virtual void Visit(IKeywordProperty property) { } public virtual void Visit(IDateProperty property) { } + public virtual void Visit(IDateNanosProperty property) { } + public virtual void Visit(IBooleanProperty property) { } public virtual void Visit(IBinaryProperty property) { } diff --git a/src/Nest/Mapping/Visitor/IPropertyVisitor.cs b/src/Nest/Mapping/Visitor/IPropertyVisitor.cs index c32cc1fc2e7..4425f0abf8e 100644 --- a/src/Nest/Mapping/Visitor/IPropertyVisitor.cs +++ b/src/Nest/Mapping/Visitor/IPropertyVisitor.cs @@ -14,6 +14,8 @@ public interface IPropertyVisitor void Visit(IDateProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); + void Visit(IDateNanosProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); + void Visit(IBinaryProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); void Visit(INestedProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); diff --git a/src/Nest/Mapping/Visitor/MappingWalker.cs b/src/Nest/Mapping/Visitor/MappingWalker.cs index ed52327b5df..57c4421e4e6 100644 --- a/src/Nest/Mapping/Visitor/MappingWalker.cs +++ b/src/Nest/Mapping/Visitor/MappingWalker.cs @@ -87,6 +87,13 @@ public void Accept(IProperties properties) Accept(t.Fields); }); break; + case FieldType.DateNanos: + Visit(field, t => + { + _visitor.Visit(t); + Accept(t.Fields); + }); + break; case FieldType.Boolean: Visit(field, t => { diff --git a/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs b/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs index 24d5bf79d1d..c589a22163f 100644 --- a/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs +++ b/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs @@ -44,6 +44,8 @@ public virtual void Visit(INestedProperty type, PropertyInfo propertyInfo, Elast public virtual void Visit(IDateProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } + public virtual void Visit(IDateNanosProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } + public virtual void Visit(INumberProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } public virtual void Visit(ITextProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } @@ -71,6 +73,9 @@ public void Visit(IProperty type, PropertyInfo propertyInfo, ElasticsearchProper case IDateProperty dateType: Visit(dateType, propertyInfo, attribute); break; + case IDateNanosProperty dateNanosType: + Visit(dateNanosType, propertyInfo, attribute); + break; case INumberProperty numberType: Visit(numberType, propertyInfo, attribute); break; diff --git a/src/Nest/QueryDsl/Compound/FunctionScore/Functions/ScoreFunctionJsonFormatter.cs b/src/Nest/QueryDsl/Compound/FunctionScore/Functions/ScoreFunctionJsonFormatter.cs index 71ca0ec758f..85167ca50f8 100644 --- a/src/Nest/QueryDsl/Compound/FunctionScore/Functions/ScoreFunctionJsonFormatter.cs +++ b/src/Nest/QueryDsl/Compound/FunctionScore/Functions/ScoreFunctionJsonFormatter.cs @@ -158,8 +158,11 @@ public void Serialize(ref JsonWriter writer, IScoreFunction value, IJsonFormatte private static void WriteScriptScore(ref JsonWriter writer, IScriptScoreFunction value, IJsonFormatterResolver formatterResolver) { writer.WritePropertyName("script_score"); - var scriptFormatter = formatterResolver.GetFormatter(); - scriptFormatter.Serialize(ref writer, value, formatterResolver); + writer.WriteBeginObject(); + writer.WritePropertyName("script"); + var scriptFormatter = formatterResolver.GetFormatter(); + scriptFormatter.Serialize(ref writer, value?.Script, formatterResolver); + writer.WriteEndObject(); } private static void WriteRandomScore(ref JsonWriter writer, IRandomScoreFunction value, IJsonFormatterResolver formatterResolver) diff --git a/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs b/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs index 924304b2294..b5f551b26cc 100644 --- a/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs +++ b/src/Nest/QueryDsl/Geo/WKT/GeoWKTReader.cs @@ -259,14 +259,14 @@ private static TokenType NextCloserOrComma(WellKnownTextTokenizer tokenizer) private static double NextNumber(WellKnownTextTokenizer tokenizer) { - if (tokenizer.NextToken() == TokenType.Number) + if (tokenizer.NextToken() == TokenType.Word) { if (string.Equals(tokenizer.TokenValue, WellKnownTextTokenizer.NaN, StringComparison.OrdinalIgnoreCase)) return double.NaN; if (double.TryParse( tokenizer.TokenValue, - NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, + NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var d)) return d; } @@ -278,7 +278,7 @@ private static double NextNumber(WellKnownTextTokenizer tokenizer) private static bool IsNumberNext(WellKnownTextTokenizer tokenizer) { var token = tokenizer.PeekToken(); - return token == TokenType.Number; + return token == TokenType.Word; } } @@ -288,7 +288,6 @@ private static bool IsNumberNext(WellKnownTextTokenizer tokenizer) internal enum CharacterType : byte { Whitespace, - Digit, Alpha, Comment } @@ -300,7 +299,6 @@ internal enum TokenType : byte { None, Word, - Number, LParen, RParen, Comma @@ -339,15 +337,14 @@ static WellKnownTextTokenizer() // build a map of ASCII chars and their types // Any unmapped ASCII will be considered whitespace // and anything > 0 outside of ASCII will be considered alpha. - // Treat + - and . as digit characters to make parsing numbers easier. Chars('a', 'z', CharacterType.Alpha); Chars('A', 'Z', CharacterType.Alpha); Chars(128 + 32, 255, CharacterType.Alpha); - Chars('0', '9', CharacterType.Digit); + Chars('0', '9', CharacterType.Alpha); Chars(LParen, RParen, CharacterType.Alpha); - Chars(Plus, Plus, CharacterType.Digit); + Chars(Plus, Plus, CharacterType.Alpha); Chars(Comma, Comma, CharacterType.Alpha); - Chars(Minus, Dot, CharacterType.Digit); + Chars(Minus, Dot, CharacterType.Alpha); Chars(Comment, Comment, CharacterType.Comment); } @@ -399,7 +396,6 @@ public string TokenString() switch (TokenType) { case TokenType.Word: - case TokenType.Number: return TokenValue; case TokenType.None: return "END-OF-STREAM"; @@ -514,33 +510,6 @@ public TokenType NextToken() { var i = 0; - do - { - _buffer.Insert(i++, (char)c); - c = Read(); - - if (c < 0) - characterType = CharacterType.Whitespace; - else if (c < CharacterTypesLength) - characterType = CharacterTypes[c]; - else - characterType = CharacterType.Alpha; - } while (characterType == CharacterType.Alpha); - - _peekChar = c; - TokenValue = new string(_buffer.ToArray(), 0, i); - - // special case for NaN - if (string.Equals(TokenValue, NaN, StringComparison.OrdinalIgnoreCase)) - return TokenType = TokenType.Number; - - return TokenType = TokenType.Word; - } - - if (characterType == CharacterType.Digit) - { - var i = 0; - var dots = 0; do { _buffer.Insert(i++, (char)c); @@ -550,20 +519,19 @@ public TokenType NextToken() characterType = CharacterType.Whitespace; else if (c < CharacterTypesLength) { + if (c == LParen || c == RParen || c == Comma) + break; + characterType = CharacterTypes[c]; - if (c == Dot) - dots++; } else characterType = CharacterType.Alpha; - } while (characterType == CharacterType.Digit); + } while (characterType == CharacterType.Alpha); _peekChar = c; TokenValue = new string(_buffer.ToArray(), 0, i); - return dots > 1 - ? TokenType = TokenType.Word - : TokenType = TokenType.Number; + return TokenType = TokenType.Word; } if (characterType == CharacterType.Comment) diff --git a/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs b/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs index 1fa540a92e2..a16d95676b1 100644 --- a/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs +++ b/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs @@ -33,6 +33,7 @@ public class FieldTypes : IsADictionaryBase public FieldCapabilities Byte => BackingDictionary.TryGetValue("byte", out var f) ? f : null; public FieldCapabilities Completion => BackingDictionary.TryGetValue("completion", out var f) ? f : null; public FieldCapabilities Date => BackingDictionary.TryGetValue("date", out var f) ? f : null; + public FieldCapabilities DateNanos => BackingDictionary.TryGetValue("date_nanos", out var f) ? f : null; public FieldCapabilities DateRange => BackingDictionary.TryGetValue("date_range", out var f) ? f : null; public FieldCapabilities Double => BackingDictionary.TryGetValue("double", out var f) ? f : null; public FieldCapabilities DoubleRange => BackingDictionary.TryGetValue("double_range", out var f) ? f : null; diff --git a/src/Tests/Tests.Reproduce/GithubIssue3907.cs b/src/Tests/Tests.Reproduce/GithubIssue3907.cs new file mode 100644 index 00000000000..e958e5f7f34 --- /dev/null +++ b/src/Tests/Tests.Reproduce/GithubIssue3907.cs @@ -0,0 +1,32 @@ +using System; +using System.Net; +using Elastic.Xunit.XunitPlumbing; +using FluentAssertions; +using Nest; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; + +namespace Tests.Reproduce +{ + public class GithubIssue3907 : IClusterFixture + { + private readonly IntrusiveOperationCluster _cluster; + + // use intrusive operation cluster because we're changing the underlying http handler + // and this cluster runs with a max concurrency of 1, so changing http handler + // will not affect other integration tests + public GithubIssue3907(IntrusiveOperationCluster cluster) => _cluster = cluster; + + [I] + public void NotUsingSocketsHttpHandlerDoesNotCauseException() + { + AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false); + + var response = _cluster.Client.Indices.Exists("non_existent_index"); + response.ApiCall.HttpStatusCode.Should().Be(404); + response.OriginalException.Should().BeNull(); + + AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", true); + } + } +} diff --git a/src/Tests/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs b/src/Tests/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs index 144e06d54b7..3d039b4fe36 100644 --- a/src/Tests/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs +++ b/src/Tests/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs @@ -61,7 +61,15 @@ [U] public void SimpleExpressions() * anchor will be an actual `DateTime`, even after a serialization/deserialization round trip */ var date = new DateTime(2015, 05, 05); - Expect("2015-05-05T00:00:00") + + /** + * will serialize to + */ + //json + var expected = "2015-05-05T00:00:00"; + + // hide + Expect(expected) .WhenSerializing(date) .AssertSubject(dateMath => ((IDateMath)dateMath) .Anchor.Match( @@ -69,6 +77,28 @@ [U] public void SimpleExpressions() s => s.Should().BeNull() ) ); + + /** + * When the `DateTime` is local or UTC, the time zone information is included. + * For example, for a UTC `DateTime` + */ + var utcDate = new DateTime(2015, 05, 05, 0, 0, 0, DateTimeKind.Utc); + + /** + * will serialize to + */ + //json + expected = "2015-05-05T00:00:00Z"; + + // hide + Expect(expected) + .WhenSerializing(utcDate) + .AssertSubject(dateMath => ((IDateMath)dateMath) + .Anchor.Match( + d => d.Should().Be(utcDate), + s => s.Should().BeNull() + ) + ); } [U] public void ComplexExpressions() diff --git a/src/Tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs b/src/Tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs index e660b6474fa..b0d1570f46d 100644 --- a/src/Tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs +++ b/src/Tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs @@ -147,6 +147,8 @@ internal class TestVisitor : IMappingVisitor public void Visit(IDateProperty mapping) => Increment("date"); + public void Visit(IDateNanosProperty mapping) => Increment("date_nanos"); + public void Visit(IBinaryProperty mapping) => Increment("binary"); public void Visit(INestedProperty mapping) => Increment("nested"); diff --git a/src/Tests/Tests/Mapping/Scalar/ScalarUsageTests.cs b/src/Tests/Tests/Mapping/Scalar/ScalarUsageTests.cs index d86f7a49e27..39b0d85a9a5 100644 --- a/src/Tests/Tests/Mapping/Scalar/ScalarUsageTests.cs +++ b/src/Tests/Tests/Mapping/Scalar/ScalarUsageTests.cs @@ -36,6 +36,14 @@ public enum ScalarEnum dateTimeOffsets = new { type = "date" }, dateTimeOffsetNullable = new { type = "date" }, dateTimeOffsetNullables = new { type = "date" }, + dateTimeNanos = new { type = "date_nanos" }, + dateTimeNanoss = new { type = "date_nanos" }, + dateTimeNanosNullable = new { type = "date_nanos" }, + dateTimeNanosNullables = new { type = "date_nanos" }, + dateTimeNanosOffset = new { type = "date_nanos" }, + dateTimeNanosOffsets = new { type = "date_nanos" }, + dateTimeNanosOffsetNullable = new { type = "date_nanos" }, + dateTimeNanosOffsetNullables = new { type = "date_nanos" }, @decimal = new { type = "double" }, decimals = new { type = "double" }, decimalNullable = new { type = "double" }, @@ -145,6 +153,14 @@ public enum ScalarEnum .Scalar(p => p.DateTimeOffsets, m => m) .Scalar(p => p.DateTimeOffsetNullable, m => m) .Scalar(p => p.DateTimeOffsetNullables, m => m) + .ScalarNanos(p => p.DateTimeNanos, m => m) + .ScalarNanos(p => p.DateTimeNanoss, m => m) + .ScalarNanos(p => p.DateTimeNanosNullable, m => m) + .ScalarNanos(p => p.DateTimeNanosNullables, m => m) + .ScalarNanos(p => p.DateTimeNanosOffset, m => m) + .ScalarNanos(p => p.DateTimeNanosOffsets, m => m) + .ScalarNanos(p => p.DateTimeNanosOffsetNullable, m => m) + .ScalarNanos(p => p.DateTimeNanosOffsetNullables, m => m) .Scalar(p => p.Bool, m => m) .Scalar(p => p.Bools, m => m) .Scalar(p => p.BoolNullable, m => m) @@ -200,6 +216,16 @@ public class ScalarPoco public IEnumerable DateTimeOffsets { get; set; } public IEnumerable DateTimes { get; set; } + public DateTime DateTimeNanos { get; set; } + public DateTime? DateTimeNanosNullable { get; set; } + public IEnumerable DateTimeNanosNullables { get; set; } + + public DateTimeOffset DateTimeNanosOffset { get; set; } + public DateTimeOffset? DateTimeNanosOffsetNullable { get; set; } + public IEnumerable DateTimeNanosOffsetNullables { get; set; } + public IEnumerable DateTimeNanosOffsets { get; set; } + public IEnumerable DateTimeNanoss { get; set; } + public decimal Decimal { get; set; } public decimal? DecimalNullable { get; set; } public IEnumerable DecimalNullables { get; set; } diff --git a/src/Tests/Tests/Mapping/Types/Core/DateNanos/DateNanosAttributeTests.cs b/src/Tests/Tests/Mapping/Types/Core/DateNanos/DateNanosAttributeTests.cs new file mode 100644 index 00000000000..33616267081 --- /dev/null +++ b/src/Tests/Tests/Mapping/Types/Core/DateNanos/DateNanosAttributeTests.cs @@ -0,0 +1,46 @@ +using System; +using Nest; + +namespace Tests.Mapping.Types.Core.DateNanos +{ + public class DateNanosTest + { + [DateNanos( + DocValues = true, + Similarity = "classic", + Store = true, + Index = false, + Boost = 1.2, + IgnoreMalformed = true, + Format = "MM/dd/yyyy")] + public DateTime Full { get; set; } + + [DateNanos] + public DateTime Minimal { get; set; } + } + + public class DateNanosAttributeTests : AttributeTestsBase + { + protected override object ExpectJson => new + { + properties = new + { + full = new + { + type = "date_nanos", + doc_values = true, + similarity = "classic", + store = true, + index = false, + boost = 1.2, + ignore_malformed = true, + format = "MM/dd/yyyy" + }, + minimal = new + { + type = "date_nanos" + } + } + }; + } +} diff --git a/src/Tests/Tests/Mapping/Types/Core/DateNanos/DateNanosPropertyTests.cs b/src/Tests/Tests/Mapping/Types/Core/DateNanos/DateNanosPropertyTests.cs new file mode 100644 index 00000000000..e651592027e --- /dev/null +++ b/src/Tests/Tests/Mapping/Types/Core/DateNanos/DateNanosPropertyTests.cs @@ -0,0 +1,62 @@ +using System; +using Nest; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework.EndpointTests.TestState; + +namespace Tests.Mapping.Types.Core.Date +{ + public class DateNanosPropertyTests : PropertyTestsBase + { + public DateNanosPropertyTests(WritableCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override object ExpectJson => new + { + properties = new + { + lastActivity = new + { + type = "date_nanos", + doc_values = false, + similarity = "BM25", + store = true, + index = false, + boost = 1.2, + ignore_malformed = true, + format = "MM/dd/yyyy", + null_value = DateTime.MinValue + } + } + }; + + protected override Func, IPromise> FluentProperties => f => f + .DateNanos(b => b + .Name(p => p.LastActivity) + .DocValues(false) + .Similarity("BM25") + .Store() + .Index(false) + .Boost(1.2) + .IgnoreMalformed() + .Format("MM/dd/yyyy") + .NullValue(DateTime.MinValue) + ); + + protected override IProperties InitializerProperties => new Properties + { + { + "lastActivity", new DateNanosProperty + { + DocValues = false, + Similarity = "BM25", + Store = true, + Index = false, + Boost = 1.2, + IgnoreMalformed = true, + Format = "MM/dd/yyyy", + NullValue = DateTime.MinValue + } + } + }; + } +} diff --git a/src/Tests/Tests/QueryDsl/Compound/FunctionScore/FunctionScoreQueryUsageTests.cs b/src/Tests/Tests/QueryDsl/Compound/FunctionScore/FunctionScoreQueryUsageTests.cs index 52a46f11f11..c447de19448 100644 --- a/src/Tests/Tests/QueryDsl/Compound/FunctionScore/FunctionScoreQueryUsageTests.cs +++ b/src/Tests/Tests/QueryDsl/Compound/FunctionScore/FunctionScoreQueryUsageTests.cs @@ -37,7 +37,19 @@ public FunctionScoreQueryUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba MinScore = 1.0, Functions = new List { - new ExponentialDecayFunction { Origin = 1.0, Decay = 0.5, Field = Field(p => p.NumberOfCommits), Scale = 0.1, Weight = 2.1 }, + new ExponentialDecayFunction + { + Origin = 1.0, + Decay = 0.5, + Field = Field(p => p.NumberOfCommits), + Scale = 0.1, + Weight = 2.1, + Filter = new NumericRangeQuery + { + Field = Field(f => f.NumberOfContributors), + GreaterThan = 10 + } + }, new GaussDateDecayFunction { Origin = DateMath.Now, Field = Field(p => p.LastActivity), Decay = 0.5, Scale = TimeSpan.FromDays(1) }, new LinearGeoDecayFunction @@ -52,7 +64,7 @@ public FunctionScoreQueryUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba new RandomScoreFunction { Seed = 1337, Field = "_seq_no" }, new RandomScoreFunction { Seed = "randomstring", Field = "_seq_no" }, new WeightFunction { Weight = 1.0 }, - new ScriptScoreFunction { Script = new InlineScript("Math.log(2 + doc['numberOfCommits'].value)") } + new ScriptScoreFunction { Script = new InlineScript("Math.log(2 + doc['numberOfCommits'].value)"), Weight = 2.0 } } }; @@ -76,7 +88,17 @@ public FunctionScoreQueryUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba decay = 0.5 } }, - weight = 2.1 + weight = 2.1, + filter = new + { + range = new + { + numberOfContributors = new + { + gt = 10.0 + } + } + } }, new { @@ -127,7 +149,8 @@ public FunctionScoreQueryUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba { source = "Math.log(2 + doc['numberOfCommits'].value)" } - } + }, + weight = 2.0 } }, max_boost = 20.0, @@ -150,15 +173,36 @@ protected override QueryContainer QueryFluent(QueryContainerDescriptor .MaxBoost(20.0) .MinScore(1.0) .Functions(f => f - .Exponential(b => b.Field(p => p.NumberOfCommits).Decay(0.5).Origin(1.0).Scale(0.1).Weight(2.1)) + .Exponential(b => b + .Field(p => p.NumberOfCommits) + .Decay(0.5) + .Origin(1.0) + .Scale(0.1) + .Weight(2.1) + .Filter(fi => fi + .Range(r => r + .Field(p => p.NumberOfContributors) + .GreaterThan(10) + ) + ) + ) .GaussDate(b => b.Field(p => p.LastActivity).Origin(DateMath.Now).Decay(0.5).Scale("1d")) - .LinearGeoLocation(b => - b.Field(p => p.LocationPoint).Origin(new GeoLocation(70, -70)).Scale(Distance.Miles(1)).MultiValueMode(MultiValueMode.Average)) + .LinearGeoLocation(b => b + .Field(p => p.LocationPoint) + .Origin(new GeoLocation(70, -70)) + .Scale(Distance.Miles(1)) + .MultiValueMode(MultiValueMode.Average) + ) .FieldValueFactor(b => b.Field(p => p.NumberOfContributors).Factor(1.1).Missing(0.1).Modifier(FieldValueFactorModifier.Square)) .RandomScore(r => r.Seed(1337).Field("_seq_no")) .RandomScore(r => r.Seed("randomstring").Field("_seq_no")) .Weight(1.0) - .ScriptScore(s => s.Script(ss => ss.Source("Math.log(2 + doc['numberOfCommits'].value)"))) + .ScriptScore(s => s + .Script(ss => ss + .Source("Math.log(2 + doc['numberOfCommits'].value)") + ) + .Weight(2) + ) ) ); } diff --git a/src/Tests/Tests/QueryDsl/Geo/Shape/GeoWKTTests.cs b/src/Tests/Tests/QueryDsl/Geo/Shape/GeoWKTTests.cs index b941cf94ae7..dbbbecce7f8 100644 --- a/src/Tests/Tests/QueryDsl/Geo/Shape/GeoWKTTests.cs +++ b/src/Tests/Tests/QueryDsl/Geo/Shape/GeoWKTTests.cs @@ -23,6 +23,22 @@ public void ReadAndWritePoint() GeoWKTWriter.Write(point).Should().Be(wkt); } + [U] + public void ReadAndWritePointWithExponent() + { + var wkt = "POINT (1.2E2 -2.5E-05)"; + var shape = GeoWKTReader.Read(wkt); + + shape.Should().BeOfType(); + var point = (PointGeoShape)shape; + + point.Coordinates.Latitude.Should().Be(-0.000025); + point.Coordinates.Longitude.Should().Be(120); + + // 1.2E2 will be expanded + GeoWKTWriter.Write(point).Should().Be("POINT (120 -2.5E-05)"); + } + [U] public void ReadAndWriteMultiPoint() {