diff --git a/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs b/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs index ce2e94cf2f9..a970a2dc13f 100644 --- a/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs +++ b/src/Nest/Mapping/DynamicTemplate/SingleMapping.cs @@ -133,6 +133,10 @@ public IProperty Generic(Func, IGenericProperty> se public IProperty SearchAsYouType(Func, ISearchAsYouTypeProperty> selector) => selector?.Invoke(new SearchAsYouTypePropertyDescriptor()); + /// + public IProperty ConstantKeyword(Func, IConstantKeywordProperty> selector) => + selector?.Invoke(new ConstantKeywordPropertyDescriptor()); + #pragma warning disable CS3001 // Argument type is not CLS-compliant public IProperty Scalar(Expression> field, Func, INumberProperty> selector = null) => selector.InvokeOrDefault(new NumberPropertyDescriptor().Name(field).Type(NumberType.Integer)); diff --git a/src/Nest/Mapping/Types/FieldType.cs b/src/Nest/Mapping/Types/FieldType.cs index e249fd21068..0fc9060a7d6 100644 --- a/src/Nest/Mapping/Types/FieldType.cs +++ b/src/Nest/Mapping/Types/FieldType.cs @@ -141,6 +141,9 @@ public enum FieldType Shape, [EnumMember(Value = "histogram")] - Histogram + Histogram, + + [EnumMember(Value = "constant_keyword")] + ConstantKeyword } } diff --git a/src/Nest/Mapping/Types/Properties.cs b/src/Nest/Mapping/Types/Properties.cs index d3d397cce9e..4c5e017c80b 100644 --- a/src/Nest/Mapping/Types/Properties.cs +++ b/src/Nest/Mapping/Types/Properties.cs @@ -138,6 +138,9 @@ TReturnType Nested(Func, INestedProp /// TReturnType SearchAsYouType(Func, ISearchAsYouTypeProperty> selector); + + /// + TReturnType ConstantKeyword(Func, IConstantKeywordProperty> selector); } public partial class PropertiesDescriptor where T : class @@ -218,6 +221,10 @@ public PropertiesDescriptor Object(Func public PropertiesDescriptor Histogram(Func, IHistogramProperty> selector) => SetProperty(selector); + /// + public PropertiesDescriptor ConstantKeyword(Func, IConstantKeywordProperty> selector) => + SetProperty(selector); + public PropertiesDescriptor Custom(IProperty customType) => SetProperty(customType); private PropertiesDescriptor SetProperty(Func selector) diff --git a/src/Nest/Mapping/Types/PropertyFormatter.cs b/src/Nest/Mapping/Types/PropertyFormatter.cs index f2f9cd6fe59..46ae338835b 100644 --- a/src/Nest/Mapping/Types/PropertyFormatter.cs +++ b/src/Nest/Mapping/Types/PropertyFormatter.cs @@ -95,6 +95,7 @@ public IProperty Deserialize(ref JsonReader reader, IJsonFormatterResolver forma case FieldType.RankFeatures: return Deserialize(ref segmentReader, formatterResolver); case FieldType.Flattened: return Deserialize(ref segmentReader, formatterResolver); case FieldType.Histogram: return Deserialize(ref segmentReader, formatterResolver); + case FieldType.ConstantKeyword: 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); @@ -203,6 +204,9 @@ public void Serialize(ref JsonWriter writer, IProperty value, IJsonFormatterReso case IHistogramProperty histogramProperty: Serialize(ref writer, histogramProperty, formatterResolver); break; + case IConstantKeywordProperty constantKeywordProperty: + Serialize(ref writer, constantKeywordProperty, formatterResolver); + break; case IGenericProperty genericProperty: Serialize(ref writer, genericProperty, formatterResolver); break; diff --git a/src/Nest/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordAttribute.cs b/src/Nest/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordAttribute.cs new file mode 100644 index 00000000000..eb8d798786c --- /dev/null +++ b/src/Nest/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordAttribute.cs @@ -0,0 +1,11 @@ +namespace Nest +{ + /// + public class ConstantKeywordAttribute : ElasticsearchPropertyAttributeBase, IConstantKeywordProperty + { + public ConstantKeywordAttribute() : base(FieldType.ConstantKeyword) { } + + /// + public object Value { get; set; } + } +} diff --git a/src/Nest/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordProperty.cs b/src/Nest/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordProperty.cs new file mode 100644 index 00000000000..fa1153c6c4a --- /dev/null +++ b/src/Nest/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordProperty.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; +using System.Runtime.Serialization; +using Elasticsearch.Net.Utf8Json; + +namespace Nest +{ + /// + /// Constant keyword is a specialization of the keyword field for the case that all documents in the index have the same value. + /// + /// Available in Elasticsearch 7.7.0+ with at least a basic license level + /// + [InterfaceDataContract] + public interface IConstantKeywordProperty : IProperty + { + /// + /// The value to associate with all documents in the index. + /// If this parameter is not provided, it is set based on the first document that gets indexed. + /// + /// Value must be a string or a numeric value + /// + [DataMember(Name ="value")] + object Value { get; set; } + } + + /// + [DebuggerDisplay("{DebugDisplay}")] + public class ConstantKeywordProperty : PropertyBase, IConstantKeywordProperty + { + public ConstantKeywordProperty() : base(FieldType.ConstantKeyword) { } + + /// + public object Value { get; set; } + } + + /// + [DebuggerDisplay("{DebugDisplay}")] + public class ConstantKeywordPropertyDescriptor + : PropertyDescriptorBase, IConstantKeywordProperty, T>, IConstantKeywordProperty + where T : class + { + public ConstantKeywordPropertyDescriptor() : base(FieldType.ConstantKeyword) { } + + object IConstantKeywordProperty.Value { get; set; } + + /// + public ConstantKeywordPropertyDescriptor Value(object value) => Assign(value, (a, v) => a.Value = v); + } +} diff --git a/src/Nest/Mapping/Visitor/IMappingVisitor.cs b/src/Nest/Mapping/Visitor/IMappingVisitor.cs index 32075ab3222..20106336e81 100644 --- a/src/Nest/Mapping/Visitor/IMappingVisitor.cs +++ b/src/Nest/Mapping/Visitor/IMappingVisitor.cs @@ -63,6 +63,8 @@ public interface IMappingVisitor void Visit(IFlattenedProperty property); void Visit(IHistogramProperty property); + + void Visit(IConstantKeywordProperty property); } public class NoopMappingVisitor : IMappingVisitor @@ -128,5 +130,7 @@ public virtual void Visit(ISearchAsYouTypeProperty property) { } public virtual void Visit(IFlattenedProperty property) { } public virtual void Visit(IHistogramProperty property) { } + + public virtual void Visit(IConstantKeywordProperty property) { } } } diff --git a/src/Nest/Mapping/Visitor/IPropertyVisitor.cs b/src/Nest/Mapping/Visitor/IPropertyVisitor.cs index f8749c0ca68..f91c272675c 100644 --- a/src/Nest/Mapping/Visitor/IPropertyVisitor.cs +++ b/src/Nest/Mapping/Visitor/IPropertyVisitor.cs @@ -62,6 +62,8 @@ public interface IPropertyVisitor void Visit(IHistogramProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute); + void Visit(IConstantKeywordProperty 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 65803aea6b5..176f2240245 100644 --- a/src/Nest/Mapping/Visitor/MappingWalker.cs +++ b/src/Nest/Mapping/Visitor/MappingWalker.cs @@ -257,6 +257,12 @@ public void Accept(IProperties properties) _visitor.Visit(t); }); break; + case FieldType.ConstantKeyword: + 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 250e5b49786..b9a1b24c7f5 100644 --- a/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs +++ b/src/Nest/Mapping/Visitor/NoopPropertyVisitor.cs @@ -62,6 +62,8 @@ public virtual void Visit(IFlattenedProperty type, PropertyInfo propertyInfo, El public virtual void Visit(IHistogramProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } + public virtual void Visit(IConstantKeywordProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { } + public virtual IProperty Visit(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) => null; public void Visit(IProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) @@ -149,6 +151,9 @@ public void Visit(IProperty type, PropertyInfo propertyInfo, ElasticsearchProper case IHistogramProperty histogram: Visit(histogram, propertyInfo, attribute); break; + case IConstantKeywordProperty constantKeyword: + Visit(constantKeyword, propertyInfo, attribute); + break; } } } diff --git a/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs b/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs index c4e53e26a7a..3c469b59bd1 100644 --- a/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs +++ b/tests/Tests.Core/ManagedElasticsearch/NodeSeeders/DefaultSeeder.cs @@ -377,6 +377,15 @@ public static PropertiesDescriptor ProjectProperties(Propert .Enabled(false) ); + if (TestConfiguration.Instance.InRange(">=7.7.0")) + props.ConstantKeyword(f => f + .Name(p => p.VersionControl) + ); + else + props.Keyword(f => f + .Name(p => p.VersionControl) + ); + return props; } diff --git a/tests/Tests.Domain/Project.cs b/tests/Tests.Domain/Project.cs index 72bd37bee07..ebda4b26ea6 100644 --- a/tests/Tests.Domain/Project.cs +++ b/tests/Tests.Domain/Project.cs @@ -28,6 +28,8 @@ public class Project { public static string TypeName = "project"; + public static string VersionControlConstant = "git"; + public IEnumerable Branches { get; set; } public IList CuratedTags { get; set; } public string DateString { get; set; } @@ -66,6 +68,8 @@ public class Project //the first applies when using internal source serializer the latter when using JsonNetSourceSerializer public Visibility Visibility { get; set; } + public string VersionControl { get; set; } + // @formatter:off — enable formatter after this line public static Faker Generator { get; } = new Faker() @@ -112,7 +116,8 @@ public class Project Closed = closedDate.ToUnixTime() } }; - }); + }) + .RuleFor(p => p.VersionControl, VersionControlConstant); public static IList Projects { get; } = Generator.Clone().Generate(100); diff --git a/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs b/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs index 01a826cfe5b..6c27b63691c 100644 --- a/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs +++ b/tests/Tests/Indices/MappingManagement/GetMapping/GetMappingApiTest.cs @@ -213,6 +213,8 @@ internal class TestVisitor : IMappingVisitor public void Visit(IHistogramProperty property) => Increment("histogram"); + public void Visit(IConstantKeywordProperty property) => Increment("constant_keyword"); + private void Increment(string key) { if (!Counts.ContainsKey(key)) Counts.Add(key, 0); diff --git a/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.cs b/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.cs index a1b76dce106..f3f8a4a1111 100644 --- a/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.cs +++ b/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.cs @@ -132,6 +132,10 @@ public PutMappingApiTests(WritableCluster cluster, EndpointUsage usage) : base(c { type = "flattened", depth_limit = 4 + }, + versionControl = new + { + type = "keyword" } } }; @@ -196,6 +200,9 @@ public PutMappingApiTests(WritableCluster cluster, EndpointUsage usage) : base(c .Name(p => p.Labels) .DepthLimit(4) ) + .Keyword(k => k + .Name(n => n.VersionControl) + ) ); protected override HttpMethod HttpMethod => HttpMethod.PUT; @@ -314,6 +321,7 @@ public PutMappingApiTests(WritableCluster cluster, EndpointUsage usage) : base(c { DepthLimit = 4 } }, + { p => p.VersionControl, new KeywordProperty() } } }; diff --git a/tests/Tests/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordAttributeTests.cs b/tests/Tests/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordAttributeTests.cs new file mode 100644 index 00000000000..4031e66c9de --- /dev/null +++ b/tests/Tests/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordAttributeTests.cs @@ -0,0 +1,35 @@ +using Elastic.Xunit.XunitPlumbing; +using Nest; + +namespace Tests.Mapping.Types.Specialized.ConstantKeyword +{ + public class ConstantKeywordTest + { + [ConstantKeyword(Value = "constant_string")] + public string ConstantString { get; set; } + + [ConstantKeyword(Value = 42)] + public int ConstantInt { get; set; } + } + + [SkipVersion("<7.7.0", "introduced in 7.7.0")] + public class ConstantKeywordAttributeTests : AttributeTestsBase + { + protected override object ExpectJson => new + { + properties = new + { + constantString = new + { + type = "constant_keyword", + value = "constant_string" + }, + constantInt = new + { + type = "constant_keyword", + value = 42 + } + } + }; + } +} diff --git a/tests/Tests/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordPropertyTests.cs b/tests/Tests/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordPropertyTests.cs new file mode 100644 index 00000000000..52412da2ff4 --- /dev/null +++ b/tests/Tests/Mapping/Types/Specialized/ConstantKeyword/ConstantKeywordPropertyTests.cs @@ -0,0 +1,43 @@ +using System; +using Elastic.Xunit.XunitPlumbing; +using Nest; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework.EndpointTests.TestState; + +namespace Tests.Mapping.Types.Specialized.ConstantKeyword +{ + [SkipVersion("<7.7.0", "introduced in 7.7.0")] + public class ConstantKeywordPropertyTests : PropertyTestsBase + { + public ConstantKeywordPropertyTests(WritableCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override object ExpectJson => new + { + properties = new + { + versionControl = new + { + type = "constant_keyword", + value = Project.VersionControlConstant, + } + } + }; + + protected override Func, IPromise> FluentProperties => f => f + .ConstantKeyword(s => s + .Name(n => n.VersionControl) + .Value(Project.VersionControlConstant) + ); + + protected override IProperties InitializerProperties => new Properties + { + { + "versionControl", new ConstantKeywordProperty + { + Value = Project.VersionControlConstant + } + } + }; + } +}