@@ -28,11 +28,40 @@ public class CatalogIndexChannelOptionsBase<TDocument>(ITransport transport) : I
2828/// <inheritdoc cref="CatalogIndexChannel{TDocument}" />
2929public class CatalogIndexChannelOptions < TDocument > ( ITransport transport ) : CatalogIndexChannelOptionsBase < TDocument > ( transport )
3030{
31+ private readonly Func < string > ? _getMapping ;
32+
3133 /// A function that returns the mapping for <typeparamref name="TDocument"/>.
32- public Func < string > ? GetMapping { get ; init ; }
34+ public Func < string > ? GetMapping
35+ {
36+ get => _getMapping ;
37+ init
38+ {
39+ _getMapping = value ;
40+ _channelHash = HashedBulkUpdate . CreateHash ( base . ChannelHash , ActiveSearchAlias ,
41+ _getMapping ? . Invoke ( ) ?? string . Empty , _getMapping ? . Invoke ( ) ?? string . Empty
42+ ) ;
43+ }
44+ }
45+
46+ private readonly Func < string > ? _getMappingSettings ;
3347
3448 /// A function that returns settings to accompany <see cref="GetMapping"/>.
35- public Func < string > ? GetMappingSettings { get ; init ; }
49+ public Func < string > ? GetMappingSettings
50+ {
51+ get => _getMappingSettings ;
52+ init
53+ {
54+ _getMappingSettings = value ;
55+ _channelHash = HashedBulkUpdate . CreateHash ( base . ChannelHash , ActiveSearchAlias ,
56+ _getMapping ? . Invoke ( ) ?? string . Empty , _getMapping ? . Invoke ( ) ?? string . Empty
57+ ) ;
58+ }
59+ }
60+
61+ private readonly string _channelHash = string . Empty ;
62+
63+ /// <inheritdoc />
64+ public override string ChannelHash => string . IsNullOrEmpty ( _channelHash ) ? base . ChannelHash : _channelHash ;
3665}
3766
3867/// <inheritdoc cref="CatalogIndexChannel{TDocument}" />
@@ -61,7 +90,7 @@ public class CatalogIndexChannel<TDocument, TChannelOptions> : IndexChannel<TDoc
6190 where TChannelOptions : CatalogIndexChannelOptionsBase < TDocument >
6291 where TDocument : class
6392{
64- private readonly string _url ;
93+ private string _url ;
6594
6695 /// <inheritdoc cref="CatalogIndexChannel{TDocument}"/>
6796 public CatalogIndexChannel ( TChannelOptions options , ICollection < IChannelCallbacks < TDocument , BulkResponse > > ? callbackListeners = null )
@@ -82,7 +111,7 @@ public CatalogIndexChannel(TChannelOptions options, ICollection<IChannelCallback
82111
83112 /// The index name used for indexing. This the configured <see cref="IndexChannelOptions{TDocument}.IndexFormat"/> to compute a single index name for all operations.
84113 /// <para>Since this is catalog data and not time series data, all data needs to end up in a single index</para>
85- public string IndexName { get ; }
114+ public string IndexName { get ; private set ; }
86115
87116 /// <inheritdoc cref="ElasticsearchChannelBase{TEvent,TChannelOptions}.CreateBulkOperationHeader"/>
88117 protected override BulkOperationHeader CreateBulkOperationHeader ( TDocument @event ) =>
@@ -97,6 +126,38 @@ protected override BulkOperationHeader CreateBulkOperationHeader(TDocument @even
97126 /// <inheritdoc cref="ElasticsearchChannelBase{TEvent,TChannelOptions}.AlwaysBootstrapComponentTemplates"/>
98127 protected override bool AlwaysBootstrapComponentTemplates => true ;
99128
129+ /// <inheritdoc />
130+ public override async Task < bool > BootstrapElasticsearchAsync ( BootstrapMethod bootstrapMethod , string ? ilmPolicy = null , CancellationToken ctx = default )
131+ {
132+ if ( Options . ScriptedHashBulkUpsertLookup is null )
133+ return await base . BootstrapElasticsearchAsync ( bootstrapMethod , ilmPolicy , ctx ) . ConfigureAwait ( false ) ;
134+ var latestAlias = string . Format ( Options . IndexFormat , "latest" ) ;
135+ var matchingIndices = string . Format ( Options . IndexFormat , "*" ) ;
136+ var currentIndex = await ShouldRemovePreviousAliasAsync ( matchingIndices , latestAlias , ctx ) . ConfigureAwait ( false ) ;
137+ if ( string . IsNullOrEmpty ( currentIndex ) )
138+ return await base . BootstrapElasticsearchAsync ( bootstrapMethod , ilmPolicy , ctx ) . ConfigureAwait ( false ) ;
139+
140+ IndexName = currentIndex ;
141+ _url = $ "{ IndexName } /{ base . BulkPathAndQuery } ";
142+ return await base . BootstrapElasticsearchAsync ( bootstrapMethod , ilmPolicy , ctx ) . ConfigureAwait ( false ) ;
143+ }
144+
145+ /// <inheritdoc />
146+ public override bool BootstrapElasticsearch ( BootstrapMethod bootstrapMethod , string ? ilmPolicy = null )
147+ {
148+ if ( Options . ScriptedHashBulkUpsertLookup is null )
149+ return base . BootstrapElasticsearch ( bootstrapMethod , ilmPolicy ) ;
150+ var latestAlias = string . Format ( Options . IndexFormat , "latest" ) ;
151+ var matchingIndices = string . Format ( Options . IndexFormat , "*" ) ;
152+ var currentIndex = ShouldRemovePreviousAlias ( matchingIndices , latestAlias ) ;
153+ if ( string . IsNullOrEmpty ( currentIndex ) )
154+ return base . BootstrapElasticsearch ( bootstrapMethod , ilmPolicy ) ;
155+
156+ IndexName = currentIndex ;
157+ _url = $ "{ IndexName } /{ base . BulkPathAndQuery } ";
158+ return base . BootstrapElasticsearch ( bootstrapMethod , ilmPolicy ) ;
159+ }
160+
100161 /// Applies the latest alias to the index.
101162 public async Task < bool > ApplyLatestAliasAsync ( CancellationToken ctx = default )
102163 {
@@ -192,6 +253,12 @@ private async Task<string> ShouldRemovePreviousAliasAsync(string matchingIndices
192253 . Trim ( Environment . NewLine . ToCharArray ( ) ) ;
193254 return hasPreviousVersions . ApiCallDetails . HttpStatusCode == 200 ? queryAliasIndex : string . Empty ;
194255 }
256+ private string ShouldRemovePreviousAlias ( string matchingIndices , string alias )
257+ {
258+ var hasPreviousVersions = Options . Transport . Head ( $ "{ matchingIndices } ?allow_no_indices=false") ;
259+ var queryAliasIndex = Cat ( $ "_cat/aliases/{ alias } ?h=index") . Trim ( Environment . NewLine . ToCharArray ( ) ) ;
260+ return hasPreviousVersions . ApiCallDetails . HttpStatusCode == 200 ? queryAliasIndex : string . Empty ;
261+ }
195262
196263 private async Task < string > CatAsync ( string url , CancellationToken ctx )
197264 {
@@ -201,6 +268,13 @@ private async Task<string> CatAsync(string url, CancellationToken ctx)
201268 return catResponse . Body ;
202269 }
203270
271+ private string Cat ( string url )
272+ {
273+ var rq = new RequestConfiguration { Accept = "text/plain" } ;
274+ var catResponse = Options . Transport . Request < StringResponse > ( new EndpointPath ( HttpMethod . GET , url ) , postData : null , null , rq ) ;
275+ return catResponse . Body ;
276+ }
277+
204278
205279 /// Applies the latest and current aliases to <see cref="IndexName"/>. Use this if you want to ensure that the latest index is always the active index
206280 /// immediately after writing to the index.
0 commit comments