diff --git a/src/Elastic.Clients.Elasticsearch/Common/Infer/Indices/Indices.cs b/src/Elastic.Clients.Elasticsearch/Common/Infer/Indices/Indices.cs index e41855c8d81..74050207b1e 100644 --- a/src/Elastic.Clients.Elasticsearch/Common/Infer/Indices/Indices.cs +++ b/src/Elastic.Clients.Elasticsearch/Common/Infer/Indices/Indices.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -14,132 +15,178 @@ namespace Elastic.Clients.Elasticsearch; [DebuggerDisplay("{DebugDisplay,nq}")] [JsonConverter(typeof(IndicesJsonConverter))] -public sealed class Indices : Union, IUrlParameter +public sealed class Indices : IUrlParameter, IEnumerable, IEquatable { - internal Indices(AllIndicesMarker all) : base(all) { } + private static readonly HashSet AllIndexList = new() { AllValue }; - internal Indices(ManyIndices indices) : base(indices) { } + internal const string AllValue = "_all"; + internal const string WildcardValue = "*"; - internal Indices(IEnumerable indices) : base(new ManyIndices(indices)) { } + private readonly HashSet? _indices; + private readonly bool _isAllIndices; - internal Indices(IEnumerable indices) : base(new ManyIndices(indices)) { } + internal Indices(IndexName indexName) + { + if (indexName.Equals(AllValue) || indexName.Equals(WildcardValue)) + { + _isAllIndices = true; + _indices = AllIndexList; + return; + } - /// All indices. Represents _all - public static Indices All { get; } = new Indices(new AllIndicesMarker()); + _indices = new HashSet + { + indexName + }; + } - /// - public static Indices AllIndices { get; } = All; + internal Indices(IEnumerable indices) + { + var enumerated = indices.NotEmpty(nameof(indices)); - private string DebugDisplay => Match( - all => "_all", - types => $"Count: {types.Indices.Count} [" + string.Join(",", types.Indices.Select((t, i) => $"({i + 1}: {t.DebugDisplay})")) + "]" - ); + foreach (var index in enumerated) + { + if (index.Equals(AllValue) || index.Equals(WildcardValue)) + { + _isAllIndices = true; + _indices = AllIndexList; + return; + } + } - public override string ToString() => DebugDisplay; + _indices = new HashSet(enumerated); + } - string IUrlParameter.GetString(ITransportConfiguration? settings) => Match( - all => "_all", - many => - { - if (settings is not IElasticsearchClientSettings clientSettings) - throw new Exception( - "Tried to pass index names on querysting but it could not be resolved because no nest settings are available"); + internal Indices(IEnumerable indices) + { + var enumerated = indices.NotEmpty(nameof(indices)); - var infer = clientSettings.Inferrer; - var indices = many.Indices.Select(i => infer.IndexName(i)).Distinct(); - return string.Join(",", indices); + foreach (var index in enumerated) + { + if (index.Equals(AllValue) || index.Equals(WildcardValue)) + { + _isAllIndices = true; + _indices = AllIndexList; + return; + } } - ); - public static IndexName Index(string index) => index; + _indices = new HashSet(enumerated.Select(s => (IndexName)s)); + } - public static IndexName Index(IndexName index) => index; + public Indices And() + { + if (_isAllIndices) + return this; - public static IndexName Index() => typeof(T); + _indices.Add(typeof(T)); + + return this; + } + + internal HashSet IndexNames => _indices; + + public static Indices All { get; } = new Indices(AllValue); + + private string DebugDisplay => _isAllIndices ? "_all" : $"Count: {_indices.Count} [" + string.Join(",", _indices.Select((t, i) => $"({i + 1}: {t.DebugDisplay})")) + "]"; - public static ManyIndices Index(IEnumerable indices) => new(indices); + public override string ToString() => DebugDisplay; + + string IUrlParameter.GetString(ITransportConfiguration? settings) + { + if (settings is not IElasticsearchClientSettings clientSettings) + throw new Exception( + "Tried to pass index names on querysting but it could not be resolved because no nest settings are available."); + + if (_isAllIndices) + return "_all"; - public static ManyIndices Index(params IndexName[] indices) => new(indices); + var inferrer = clientSettings.Inferrer; - public static ManyIndices Index(IEnumerable indices) => new(indices); + if (_indices.Count == 1) + { + var value = inferrer.IndexName(_indices.First()); + return value; + } + + var indices = _indices.Select(i => inferrer.IndexName(i)); + return string.Join(",", indices); + } - public static ManyIndices Index(params string[] indices) => new(indices); + public static IndexName Index(string index) => index; + + public static IndexName Index(IndexName index) => index; + + public static IndexName Index() => typeof(T); public static Indices Parse(string indicesString) { if (indicesString.IsNullOrEmptyCommaSeparatedList(out var indices)) return null; - return indices.Contains("_all") ? All : Index(indices.Select(i => (IndexName)i)); + return indices.Contains(AllValue) || indices.Contains(WildcardValue) ? All : new Indices(indices); } public static implicit operator Indices(string indicesString) => Parse(indicesString); - public static implicit operator Indices(ManyIndices many) => many == null ? null : new Indices(many); + public static implicit operator Indices(string[] indices) => indices.IsEmpty() ? null : new Indices(indices); - public static implicit operator Indices(string[] many) => many.IsEmpty() ? null : new ManyIndices(many); + public static implicit operator Indices(IndexName[] indices) => indices.IsEmpty() ? null : new Indices(indices); - public static implicit operator Indices(IndexName[] many) => many.IsEmpty() ? null : new ManyIndices(many); + public static implicit operator Indices(IndexName index) => index == null ? null : new Indices(new[] { index }); - public static implicit operator Indices(IndexName index) => index == null ? null : new ManyIndices(new[] { index }); - - public static implicit operator Indices(Type type) => type == null ? null : new ManyIndices(new IndexName[] { type }); + public static implicit operator Indices(Type type) => type == null ? null : new Indices(new IndexName[] { type }); public static bool operator ==(Indices left, Indices right) => Equals(left, right); public static bool operator !=(Indices left, Indices right) => !Equals(left, right); + public bool Equals(Indices other) => EqualsAllIndices(IndexNames, other.IndexNames); + public override bool Equals(object obj) { - if (!(obj is Indices other)) + if (obj is not Indices other) return false; - return Match( - all => other.Match(a => true, m => false), - many => other.Match( - a => false, - m => EqualsAllIndices(m.Indices, many.Indices) - ) - ); + return EqualsAllIndices(IndexNames, other.IndexNames); } - private static bool EqualsAllIndices(IReadOnlyList thisIndices, IReadOnlyList otherIndices) + private static bool EqualsAllIndices(HashSet thisIndices, HashSet otherIndices) { if (thisIndices == null && otherIndices == null) return true; + if (thisIndices == null || otherIndices == null) return false; return thisIndices.Count == otherIndices.Count && !thisIndices.Except(otherIndices).Any(); } - public override int GetHashCode() => Match( - all => "_all".GetHashCode(), - many => string.Concat(many.Indices.OrderBy(i => i.ToString())).GetHashCode() - ); - - public class AllIndicesMarker - { - internal AllIndicesMarker() { } - } - - public class ManyIndices + public override int GetHashCode() { - private readonly List _indices = new(); + var hashCodes = new List(IndexNames.Count); - internal ManyIndices(IEnumerable indices) => _indices.AddRange(indices.NotEmpty(nameof(indices))); - - internal ManyIndices(IEnumerable indices) => - _indices.AddRange(indices.NotEmpty(nameof(indices)).Select(s => (IndexName)s)); + foreach (var item in IndexNames.OrderBy(s => s)) + { + hashCodes.Add(item.GetHashCode()); + } - public IReadOnlyList Indices => _indices; + hashCodes.Sort(); - public ManyIndices And() + unchecked { - _indices.Add(typeof(T)); - return this; + var hash = 17; + foreach (var hashCode in hashCodes) + { + hash = hash * 23 + hashCode; + } + return typeof(IndexName).GetHashCode() ^ hash; } } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() => IndexNames.GetEnumerator(); } internal sealed class IndicesJsonConverter : JsonConverter @@ -178,14 +225,6 @@ public override void Write(Utf8JsonWriter writer, Indices value, JsonSerializerO return; } - switch (value.Tag) - { - case 0: - writer.WriteStringValue("_all"); - break; - case 1: - writer.WriteStringValue(((IUrlParameter)value).GetString(_settings)); - break; - } + writer.WriteStringValue(((IUrlParameter)value).GetString(_settings)); } } diff --git a/src/Elastic.Clients.Elasticsearch/Common/Infer/Indices/IndicesExtensions.cs b/src/Elastic.Clients.Elasticsearch/Common/Infer/Indices/IndicesExtensions.cs deleted file mode 100644 index 86b9e22a646..00000000000 --- a/src/Elastic.Clients.Elasticsearch/Common/Infer/Indices/IndicesExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 Elastic.Clients.Elasticsearch -{ - //public static class IndicesExtensions - //{ - // public static string Resolve(this Indices marker, IElasticsearchClientSettings elasticsearchClientSettings) - // { - // elasticsearchClientSettings.ThrowIfNull(nameof(elasticsearchClientSettings)); - // return elasticsearchClientSettings.Inferrer.Resolve(marker); - // } - //} -} diff --git a/tests/Tests.Configuration/tests.default.yaml b/tests/Tests.Configuration/tests.default.yaml index 0b8b217a073..5f02d03f173 100644 --- a/tests/Tests.Configuration/tests.default.yaml +++ b/tests/Tests.Configuration/tests.default.yaml @@ -5,7 +5,7 @@ # tracked by git). # mode either u (unit test), i (integration test) or m (mixed mode) -mode: i +mode: u # the elasticsearch version that should be started # Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype elasticsearch_version: latest-8 diff --git a/tests/Tests/ClientConcepts/HighLevel/Inference/Equality/IndicesEqualityTests.cs b/tests/Tests/ClientConcepts/HighLevel/Inference/Equality/IndicesEqualityTests.cs index c1aeb9b53dc..e6f4d3b2aac 100644 --- a/tests/Tests/ClientConcepts/HighLevel/Inference/Equality/IndicesEqualityTests.cs +++ b/tests/Tests/ClientConcepts/HighLevel/Inference/Equality/IndicesEqualityTests.cs @@ -18,7 +18,7 @@ [U] public void Eq() foreach (var t in equal) { (t == types).ShouldBeTrue(t); - t.Should().Be(types); + t.Should().BeEquivalentTo(types); } (Indices.All == "_all").Should().BeTrue(); @@ -32,7 +32,7 @@ [U] public void NotEq() foreach (var t in notEqual) { (t != types).ShouldBeTrue(t); - t.Should().NotBe(types); + t.Should().NotBeEquivalentTo(types); } } diff --git a/tests/Tests/ClientConcepts/HighLevel/Inference/ImplicitConversionTests.cs b/tests/Tests/ClientConcepts/HighLevel/Inference/ImplicitConversionTests.cs index 570b3e3475d..25740b4ab5d 100644 --- a/tests/Tests/ClientConcepts/HighLevel/Inference/ImplicitConversionTests.cs +++ b/tests/Tests/ClientConcepts/HighLevel/Inference/ImplicitConversionTests.cs @@ -55,10 +55,10 @@ [U] public void Fields() Implicit((Field[])null).Should().BeNull(); Implicit("").Should().BeNull(); Implicit(" ").Should().BeNull(); - Implicit(new string[] { }).Should().BeNull(); - Implicit(new Expression[] { }).Should().BeNull(); - Implicit(new PropertyInfo[] { }).Should().BeNull(); - Implicit(new Field[] { }).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); Implicit(new Expression[] { null, null }).Should().BeNull(); Implicit(new PropertyInfo[] { null, null }).Should().BeNull(); Implicit(new Field[] { null, null }).Should().BeNull(); @@ -82,7 +82,6 @@ [U] public void IndexName() [U] public void Indices() { Implicit((string)null).Should().BeNull(); - Implicit((Indices.ManyIndices)null).Should().BeNull(); Implicit((string[])null).Should().BeNull(); Implicit((IndexName)null).Should().BeNull(); Implicit((IndexName[])null).Should().BeNull(); @@ -90,8 +89,8 @@ [U] public void Indices() Implicit("").Should().BeNull(); Implicit(" ").Should().BeNull(); Implicit(",, ,, ").Should().BeNull(); - Implicit(new string[] { }).Should().BeNull(); - Implicit(new IndexName[] { }).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); Implicit(new string[] { null, null }).Should().BeNull(); Implicit(new IndexName[] { null, null }).Should().BeNull(); } @@ -104,7 +103,7 @@ [U] public void Names() Implicit(",,").Should().BeNull(); Implicit(", ,").Should().BeNull(); Implicit(" ").Should().BeNull(); - Implicit(new string[] { }).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); Implicit(new string[] { null, null }).Should().BeNull(); } @@ -116,7 +115,7 @@ [U] public void Routing() Implicit(",,").Should().BeNull(); Implicit(", ,").Should().BeNull(); Implicit(" ").Should().BeNull(); - Implicit(new string[] { }).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); Implicit(new string[] { null, null }).Should().BeNull(); } @@ -138,7 +137,7 @@ [U] public void NodeId() Implicit("").Should().BeNull(); Implicit(" ").Should().BeNull(); Implicit(" ,, , ,,").Should().BeNull(); - Implicit(new string[] { }).Should().BeNull(); + Implicit(Array.Empty()).Should().BeNull(); Implicit(new string[] { null, null }).Should().BeNull(); } diff --git a/tests/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs b/tests/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs index d65eb1c68f9..89d9bcf113c 100644 --- a/tests/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs +++ b/tests/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs @@ -260,7 +260,7 @@ [U] public void EqualsValidation() Indices indices1 = "foo,bar"; Indices indices2 = "bar,foo"; - indices1.Should().Be(indices2); + indices1.Should().BeEquivalentTo(indices2); (indices1 == indices2).Should().BeTrue(); } diff --git a/tests/Tests/Common/Infer/Indices/IndicesTests.cs b/tests/Tests/Common/Infer/Indices/IndicesTests.cs new file mode 100644 index 00000000000..8326ad0d329 --- /dev/null +++ b/tests/Tests/Common/Infer/Indices/IndicesTests.cs @@ -0,0 +1,133 @@ +// 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.Linq; +using System.Threading.Tasks; +using Tests.Core.Xunit; +using Tests.Domain; +using VerifyXunit; + +namespace Tests.Serialization +{ + public class IndicesTests + { + private const string TestIndexName = "an-index"; + + [U] + public void StoresOnlyAllValue_WhenIndexNamesContainsAll() + { + Indices indices = new[] { "index-1", "_all", "index-2" }; + + indices.Should().HaveCount(1); + indices.First().Should().Be(Indices.AllValue); + } + + [U] + public void StoresOnlyAllValue_WhenIndexNamesAllWildcard() + { + Indices indices = new[] { "index-1", "index-2", "*" }; + + indices.Should().HaveCount(1); + indices.First().Should().Be(Indices.AllValue); + } + + [U] + public void StoresOnlyAllValue_WhenParseStringContainsAll() + { + var indices = Indices.Parse("index-1,index-2,_all,index-3"); + indices.Should().HaveCount(1); + indices.First().Should().Be(Indices.AllValue); + } + + [U] + public void StoresOnlyAllValue_WhenParseStringContainsAllWildcard() + { + var indices = Indices.Parse("index-1,index-2,*,index-3"); + indices.Should().HaveCount(1); + indices.First().Should().Be(Indices.AllValue); + } + + [U] + public void DoesNotAdd_WhenIndicesAll() + { + var indices = Indices.All; + + indices = indices.And(); + + indices.Should().HaveCount(1); + indices.First().Should().Be(Indices.AllValue); + } + + [U] + public void GetHashCode_Matches_ForAll() + { + var indices1 = Indices.Parse("*"); + var indices2 = Indices.All; + + indices1.GetHashCode().Should().Be(indices2.GetHashCode()); + } + + [U] + public void GetHashCode_Matches() + { + Indices indices1 = TestIndexName; + Indices indices2 = TestIndexName; + + indices1.GetHashCode().Should().Be(indices2.GetHashCode()); + } + + [U] + public void Equals_IsTrue() + { + Indices indices1 = TestIndexName; + Indices indices2 = TestIndexName; + + indices1.Equals(indices2).Should().BeTrue(); + } + + [U] + public void Equals_IsTrueForString() + { + Indices indices = TestIndexName; + + indices.Equals(Indices.Parse(TestIndexName)).Should().BeTrue(); + } + } + + [UsesVerify] + [SystemTextJsonOnly] + public class IndicesSerializationTests : SerializerTestBase + { + [U] + public async Task Serializes_All_Correctly() + { + var obj = new TestThing(Indices.All); + var serialisedJson = await SerializeAndGetJsonStringAsync(obj); + await Verifier.VerifyJson(serialisedJson); + } + + [U] + public async void Deserializes_All_Correctly() + { + const string json = @"{""indices"":""_all""}"; + var obj = DeserializeJsonString(json); + await Verifier.Verify(obj); + } + + [U] + public async Task Serializes_Correctly() + { + var obj = new TestThing(Indices.Parse("index-a,index-b")); + var serialisedJson = await SerializeAndGetJsonStringAsync(obj); + await Verifier.VerifyJson(serialisedJson); + } + + private class TestThing + { + public TestThing(Indices indices) => Indices = indices; + + public Indices Indices { get; init; } + } + } +} diff --git a/tests/Tests/Common/TimeUnit/TimeUnits.doc.cs b/tests/Tests/Common/TimeUnit/TimeUnits.cs similarity index 100% rename from tests/Tests/Common/TimeUnit/TimeUnits.doc.cs rename to tests/Tests/Common/TimeUnit/TimeUnits.cs diff --git a/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Deserializes_All_Correctly.verified.txt b/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Deserializes_All_Correctly.verified.txt new file mode 100644 index 00000000000..4015f2aea58 --- /dev/null +++ b/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Deserializes_All_Correctly.verified.txt @@ -0,0 +1,7 @@ +{ + Indices: [ + { + Name: _all + } + ] +} \ No newline at end of file diff --git a/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Serializes_All_Correctly.verified.txt b/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Serializes_All_Correctly.verified.txt new file mode 100644 index 00000000000..889502d3e7e --- /dev/null +++ b/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Serializes_All_Correctly.verified.txt @@ -0,0 +1,3 @@ +{ + indices: _all +} \ No newline at end of file diff --git a/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Serializes_Correctly.verified.txt b/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Serializes_Correctly.verified.txt new file mode 100644 index 00000000000..da6d042c5b6 --- /dev/null +++ b/tests/Tests/_VerifySnapshots/IndicesSerializationTests.Serializes_Correctly.verified.txt @@ -0,0 +1,3 @@ +{ + indices: index-a,index-b +} \ No newline at end of file