From e65f5857b04f5892658c4d20c3aed282b26e74c0 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Thu, 7 Jan 2021 13:17:12 +0000 Subject: [PATCH] Implement version field type (#5200) * Implement version field type * Fix failing test * Add skip version to test --- .../Mapping/DynamicTemplate/SingleMapping.cs | 4 ++ src/Nest/Mapping/Types/FieldType.cs | 6 +++ src/Nest/Mapping/Types/Properties.cs | 9 +++- src/Nest/Mapping/Types/PropertyFormatter.cs | 4 ++ .../Specialized/Version/VersionAttribute.cs | 13 ++++++ .../Specialized/Version/VersionProperty.cs | 33 ++++++++++++++ src/Nest/Mapping/Visitor/IMappingVisitor.cs | 4 ++ src/Nest/Mapping/Visitor/IPropertyVisitor.cs | 2 + src/Nest/Mapping/Visitor/MappingWalker.cs | 6 +++ .../Mapping/Visitor/NoopPropertyVisitor.cs | 7 ++- .../FieldCapabilitiesResponse.cs | 2 +- .../GetMapping/GetMappingApiTest.cs | 2 + .../Version/VersionAttributeTests.cs | 28 ++++++++++++ .../Version/VersionPropertyTests.cs | 44 +++++++++++++++++++ 14 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/Nest/Mapping/Types/Specialized/Version/VersionAttribute.cs create mode 100644 src/Nest/Mapping/Types/Specialized/Version/VersionProperty.cs create mode 100644 tests/Tests/Mapping/Types/Specialized/Version/VersionAttributeTests.cs create mode 100644 tests/Tests/Mapping/Types/Specialized/Version/VersionPropertyTests.cs diff --git a/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs b/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs index a6379289a81..03c164cbcad 100644 --- a/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs +++ b/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs @@ -148,6 +148,10 @@ public IProperty ConstantKeyword(Func, ICon /// public IProperty Wildcard(Func, IWildcardProperty> selector) => selector?.Invoke(new WildcardPropertyDescriptor()); + + /// + public IProperty Version(Func, IVersionProperty> selector) => + selector?.Invoke(new VersionPropertyDescriptor()); #pragma warning disable CS3001 // Argument type is not CLS-compliant public IProperty Scalar(Expression> field, Func, INumberProperty> selector = null) => diff --git a/src/Nest/Mapping/Types/FieldType.cs b/src/Nest/Mapping/Types/FieldType.cs index 6323698daa0..16db2bdce5c 100644 --- a/src/Nest/Mapping/Types/FieldType.cs +++ b/src/Nest/Mapping/Types/FieldType.cs @@ -154,5 +154,11 @@ public enum FieldType [EnumMember(Value = "point")] Point, + + /// + /// Version field type for storing semver compatible version numbers. + /// + [EnumMember(Value = "version")] + Version } } diff --git a/src/Nest/Mapping/Types/Properties.cs b/src/Nest/Mapping/Types/Properties.cs index 43c3cbd4b28..f553e7e273e 100644 --- a/src/Nest/Mapping/Types/Properties.cs +++ b/src/Nest/Mapping/Types/Properties.cs @@ -151,6 +151,9 @@ TReturnType Nested(Func, INestedProp /// TReturnType Wildcard(Func, IWildcardProperty> selector); + + /// + TReturnType Version(Func, IVersionProperty> selector); } public partial class PropertiesDescriptor where T : class @@ -263,8 +266,10 @@ public PropertiesDescriptor ConstantKeyword(Func - public PropertiesDescriptor Wildcard(Func, IWildcardProperty> selector) => - SetProperty(selector); + public PropertiesDescriptor Wildcard(Func, IWildcardProperty> selector) => SetProperty(selector); + + /// + public PropertiesDescriptor Version(Func, IVersionProperty> selector) => SetProperty(selector); /// /// Map a custom property. diff --git a/src/Nest/Mapping/Types/PropertyFormatter.cs b/src/Nest/Mapping/Types/PropertyFormatter.cs index c700f7958b6..70e7de9e106 100644 --- a/src/Nest/Mapping/Types/PropertyFormatter.cs +++ b/src/Nest/Mapping/Types/PropertyFormatter.cs @@ -100,6 +100,7 @@ public IProperty Deserialize(ref JsonReader reader, IJsonFormatterResolver forma case FieldType.Histogram: return Deserialize(ref segmentReader, formatterResolver); case FieldType.ConstantKeyword: return Deserialize(ref segmentReader, formatterResolver); case FieldType.Wildcard: return Deserialize(ref segmentReader, formatterResolver); + case FieldType.Version: return Deserialize(ref segmentReader, formatterResolver); case FieldType.None: // no "type" field in the property mapping, or FieldType enum could not be parsed from typeString return Deserialize(ref segmentReader, formatterResolver); @@ -220,6 +221,9 @@ public void Serialize(ref JsonWriter writer, IProperty value, IJsonFormatterReso case IGenericProperty genericProperty: Serialize(ref writer, genericProperty, formatterResolver); break; + case IVersionProperty versionProperty: + Serialize(ref writer, versionProperty, formatterResolver); + break; default: var formatter = formatterResolver.GetFormatter(); formatter.Serialize(ref writer, value, formatterResolver); diff --git a/src/Nest/Mapping/Types/Specialized/Version/VersionAttribute.cs b/src/Nest/Mapping/Types/Specialized/Version/VersionAttribute.cs new file mode 100644 index 00000000000..161549c8be7 --- /dev/null +++ b/src/Nest/Mapping/Types/Specialized/Version/VersionAttribute.cs @@ -0,0 +1,13 @@ +// 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 + +namespace Nest +{ + public class VersionAttribute : ElasticsearchPropertyAttributeBase, IVersionProperty + { + public VersionAttribute() : base(FieldType.Version) { } + + private IVersionProperty Self => this; + } +} diff --git a/src/Nest/Mapping/Types/Specialized/Version/VersionProperty.cs b/src/Nest/Mapping/Types/Specialized/Version/VersionProperty.cs new file mode 100644 index 00000000000..dcc7f205737 --- /dev/null +++ b/src/Nest/Mapping/Types/Specialized/Version/VersionProperty.cs @@ -0,0 +1,33 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Diagnostics; +using Elasticsearch.Net.Utf8Json; + +namespace Nest +{ + /// + /// A version field can index/store semver version numbers. + /// + [InterfaceDataContract] + public interface IVersionProperty : IProperty + { + } + + /// + [DebuggerDisplay("{DebugDisplay}")] + public class VersionProperty : PropertyBase, IVersionProperty + { + public VersionProperty() : base(FieldType.Version) { } + } + + /// + [DebuggerDisplay("{DebugDisplay}")] + public class VersionPropertyDescriptor + : PropertyDescriptorBase, IVersionProperty, T>, IVersionProperty + where T : class + { + public VersionPropertyDescriptor() : base(FieldType.Version) { } + } +} diff --git a/src/Nest/Mapping/Visitor/IMappingVisitor.cs b/src/Nest/Mapping/Visitor/IMappingVisitor.cs index 01fb10b6bf8..b5b8816df4d 100644 --- a/src/Nest/Mapping/Visitor/IMappingVisitor.cs +++ b/src/Nest/Mapping/Visitor/IMappingVisitor.cs @@ -71,6 +71,8 @@ public interface IMappingVisitor void Visit(IHistogramProperty property); void Visit(IConstantKeywordProperty property); + + void Visit(IVersionProperty property); } public class NoopMappingVisitor : IMappingVisitor @@ -140,5 +142,7 @@ public virtual void Visit(IFlattenedProperty property) { } public virtual void Visit(IHistogramProperty property) { } public virtual void Visit(IConstantKeywordProperty property) { } + + public virtual void Visit(IVersionProperty property) { } } } diff --git a/src/Nest/Mapping/Visitor/IPropertyVisitor.cs b/src/Nest/Mapping/Visitor/IPropertyVisitor.cs index 9d8809ad244..f557c7420a4 100644 --- a/src/Nest/Mapping/Visitor/IPropertyVisitor.cs +++ b/src/Nest/Mapping/Visitor/IPropertyVisitor.cs @@ -76,6 +76,8 @@ public interface IPropertyVisitor void Visit(IWildcardProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); + void Visit(IVersionProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); + IProperty Visit(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); bool SkipProperty(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); diff --git a/src/Nest/Mapping/Visitor/MappingWalker.cs b/src/Nest/Mapping/Visitor/MappingWalker.cs index 04802d77c64..3ad5bd2d481 100644 --- a/src/Nest/Mapping/Visitor/MappingWalker.cs +++ b/src/Nest/Mapping/Visitor/MappingWalker.cs @@ -273,6 +273,12 @@ public void Accept(IProperties properties) _visitor.Visit(t); }); break; + case FieldType.Version: + Visit(field, t => + { + _visitor.Visit(t); + }); + break; case FieldType.None: continue; } diff --git a/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs b/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs index 32281de535a..e18eb7bf6e2 100644 --- a/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs +++ b/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs @@ -77,6 +77,8 @@ public virtual void Visit(IFieldAliasProperty type, PropertyInfo propertyInfo, E public virtual void Visit(IWildcardProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } + public virtual void Visit(IVersionProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } + public virtual IProperty Visit(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) => null; public void Visit(IProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) @@ -169,7 +171,7 @@ public void Visit(IProperty type, PropertyInfo propertyInfo, ElasticsearchProper break; case IPointProperty point: Visit(point, propertyInfo, attribute); - break; + break; case ISearchAsYouTypeProperty searchAsYouType: Visit(searchAsYouType, propertyInfo, attribute); break; @@ -179,6 +181,9 @@ public void Visit(IProperty type, PropertyInfo propertyInfo, ElasticsearchProper case IFieldAliasProperty fieldAlias: Visit(fieldAlias, propertyInfo, attribute); break; + case IVersionProperty version: + Visit(version, propertyInfo, attribute); + break; } } } diff --git a/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs b/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs index 6948c6a4146..4c32deb3f64 100644 --- a/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs +++ b/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs @@ -53,7 +53,6 @@ public class FieldTypes : IsADictionaryBase public FieldCapabilities Integer => BackingDictionary.TryGetValue("integer", out var f) ? f : null; public FieldCapabilities IntegerRange => BackingDictionary.TryGetValue("integer_range", out var f) ? f : null; public FieldCapabilities Ip => BackingDictionary.TryGetValue("ip", out var f) ? f : null; - public FieldCapabilities Keyword => BackingDictionary.TryGetValue("keyword", out var f) ? f : null; public FieldCapabilities Long => BackingDictionary.TryGetValue("long", out var f) ? f : null; public FieldCapabilities LongRange => BackingDictionary.TryGetValue("long_range", out var f) ? f : null; @@ -71,6 +70,7 @@ public class FieldTypes : IsADictionaryBase public FieldCapabilities Uid => BackingDictionary.TryGetValue("_uid", out var f) ? f : null; public FieldCapabilities ParentJoin => BackingDictionary.TryGetValue("_parent_join", out var f) ? f : null; public FieldCapabilities Version => BackingDictionary.TryGetValue("_version", out var f) ? f : null; + public FieldCapabilities VersionField => BackingDictionary.TryGetValue("version", out var f) ? f : null; } public class FieldCapabilities diff --git a/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs b/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs index 645f0c90719..c1e1cd82e9e 100644 --- a/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs +++ b/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs @@ -229,6 +229,8 @@ internal class TestVisitor : IMappingVisitor public void Visit(IConstantKeywordProperty property) => Increment("constant_keyword"); + public void Visit(IVersionProperty property) => Increment("version"); + private void Increment(string key) { if (!Counts.ContainsKey(key)) Counts.Add(key, 0); diff --git a/tests/Tests/Mapping/Types/Specialized/Version/VersionAttributeTests.cs b/tests/Tests/Mapping/Types/Specialized/Version/VersionAttributeTests.cs new file mode 100644 index 00000000000..c71f6caa807 --- /dev/null +++ b/tests/Tests/Mapping/Types/Specialized/Version/VersionAttributeTests.cs @@ -0,0 +1,28 @@ +// 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 Nest; + +namespace Tests.Mapping.Types.Specialized.Version +{ + public class VersionTest + { + [Version] + public string VersionNumber { get; set; } + } + + public class VersionAttributeTests : AttributeTestsBase + { + protected override object ExpectJson => new + { + properties = new + { + versionNumber = new + { + type = "version" + } + } + }; + } +} diff --git a/tests/Tests/Mapping/Types/Specialized/Version/VersionPropertyTests.cs b/tests/Tests/Mapping/Types/Specialized/Version/VersionPropertyTests.cs new file mode 100644 index 00000000000..b2dccfcaf7e --- /dev/null +++ b/tests/Tests/Mapping/Types/Specialized/Version/VersionPropertyTests.cs @@ -0,0 +1,44 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Nest; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework.EndpointTests.TestState; + +namespace Tests.Mapping.Types.Specialized.Version +{ + [SkipVersion("<7.10.0", "Version property introduced in 7.10.0")] + public class VersionPropertyTests : PropertyTestsBase + { + public VersionPropertyTests(WritableCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override object ExpectJson => new + { + properties = new + { + name = new + { + type = "version" + } + } + }; + + protected override Func, IPromise> FluentProperties => f => f + .Version(s => s + .Name(p => p.Name) + ); + + protected override IProperties InitializerProperties => new Properties + { + { + "name", new VersionProperty + { + } + } + }; + } +}