@@ -10,6 +10,8 @@ namespace ServiceControl.Audit.Persistence.Tests;
1010using Raven . Client . Documents . Operations . Indexes ;
1111using Raven . Client . Exceptions ;
1212using Raven . Client . Exceptions . Documents . Indexes ;
13+ using Raven . Client . ServerWide . Operations ;
14+ using Raven . Client . ServerWide . Operations . Configuration ;
1315using ServiceControl . RavenDB ;
1416
1517[ TestFixture ]
@@ -70,7 +72,7 @@ public async Task Free_text_search_index_can_be_opted_out_from()
7072 }
7173
7274 [ Test ]
73- public async Task New_indexes_should_be_created_with_lucene ( )
75+ public async Task New_index_should_inherit_lucene_from_a_lucene_database ( )
7476 {
7577 var index = new FailedAuditImportIndex ( ) ;
7678
@@ -79,9 +81,27 @@ public async Task New_indexes_should_be_created_with_lucene()
7981 await DatabaseSetup . CreateIndexes ( configuration . DocumentStore , true , TestTimeoutCancellationToken ) ;
8082
8183 var definition = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
84+ var stats = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexStatisticsOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
8285
83- // The search engine is set on the index, not inherited. It then also applies to databases that default to Corax.
84- Assert . That ( definition . Configuration , Does . ContainKey ( IndexDeployment . StaticSearchEngineTypeKey ) . WithValue ( SearchEngineType . Lucene . ToString ( ) ) ) ;
86+ Assert . That ( stats . SearchEngineType , Is . EqualTo ( SearchEngineType . Lucene ) ) ;
87+ Assert . That ( definition . Configuration , Does . Not . ContainKey ( IndexDeployment . StaticSearchEngineTypeKey ) , "The database is set to Lucene, so the index inherits it" ) ;
88+ }
89+
90+ [ Test ]
91+ public async Task New_index_in_a_corax_database_should_be_pinned_to_lucene ( )
92+ {
93+ var index = new FailedAuditImportIndex ( ) ;
94+
95+ await configuration . DocumentStore . Maintenance . SendAsync ( new DeleteIndexOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
96+ await SetDatabaseSearchEngine ( SearchEngineType . Corax ) ;
97+
98+ await DatabaseSetup . CreateIndexes ( configuration . DocumentStore , true , TestTimeoutCancellationToken ) ;
99+
100+ var definition = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
101+ var stats = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexStatisticsOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
102+
103+ Assert . That ( stats . SearchEngineType , Is . EqualTo ( SearchEngineType . Lucene ) ) ;
104+ Assert . That ( definition . Configuration , Does . ContainKey ( IndexDeployment . StaticSearchEngineTypeKey ) . WithValue ( nameof ( SearchEngineType . Lucene ) ) , "The database is set to Corax, so Lucene is set on the index" ) ;
85105 }
86106
87107 [ TestCase ( true , TestName = "Search engine set in the index definition through SearchEngineType should take precedence over the one on the server" ) ]
@@ -169,6 +189,65 @@ public async Task Pending_replacement_using_the_database_default_should_be_disca
169189 }
170190 }
171191
192+ [ Test ]
193+ public async Task Changed_index_without_a_search_engine_setting_in_a_corax_database_should_be_pinned_to_lucene ( )
194+ {
195+ // The index runs on Lucene, the database default, without a setting of its own: the state after an operator migrated
196+ // the whole database and reset the indexes.
197+ var statsBefore = await PutIndexWithoutSearchEngineSetting ( customize : true ) ;
198+
199+ Assert . That ( statsBefore . SearchEngineType , Is . EqualTo ( SearchEngineType . Lucene ) ) ;
200+
201+ // Versions 6.20.0 to 6.21.2 then set the database default back to Corax.
202+ await SetDatabaseSearchEngine ( SearchEngineType . Corax ) ;
203+
204+ await DatabaseSetup . CreateIndexes ( configuration . DocumentStore , true , TestTimeoutCancellationToken ) ;
205+
206+ var statsAfter = await WaitForIndexDefinitionUpdate ( statsBefore ) ;
207+ var definitionAfter = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( statsBefore . Name ) , TestTimeoutCancellationToken ) ;
208+
209+ Assert . That ( definitionAfter . Fields , Does . Not . ContainKey ( CustomizedField ) , "The definition change should be applied" ) ;
210+ Assert . That ( statsAfter . SearchEngineType , Is . EqualTo ( SearchEngineType . Lucene ) , "The rebuilt index should use Lucene, not the database default" ) ;
211+ Assert . That ( definitionAfter . Configuration , Does . ContainKey ( IndexDeployment . StaticSearchEngineTypeKey ) . WithValue ( nameof ( SearchEngineType . Lucene ) ) ) ;
212+ }
213+
214+ [ Test ]
215+ public async Task Changed_corax_index_without_a_search_engine_setting_in_a_lucene_database_should_inherit_lucene ( )
216+ {
217+ // A Corax index without a setting of its own, the state of an existing database before any migration.
218+ await SetDatabaseSearchEngine ( SearchEngineType . Corax ) ;
219+ var statsBefore = await PutIndexWithoutSearchEngineSetting ( customize : true ) ;
220+
221+ Assert . That ( statsBefore . SearchEngineType , Is . EqualTo ( SearchEngineType . Corax ) ) ;
222+
223+ // The operator sets the database to Lucene. The next definition change moves the index to Lucene.
224+ await SetDatabaseSearchEngine ( SearchEngineType . Lucene ) ;
225+
226+ await DatabaseSetup . CreateIndexes ( configuration . DocumentStore , true , TestTimeoutCancellationToken ) ;
227+
228+ var statsAfter = await WaitForIndexDefinitionUpdate ( statsBefore ) ;
229+ var definitionAfter = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( statsBefore . Name ) , TestTimeoutCancellationToken ) ;
230+
231+ Assert . That ( statsAfter . SearchEngineType , Is . EqualTo ( SearchEngineType . Lucene ) ) ;
232+ Assert . That ( definitionAfter . Configuration , Does . Not . ContainKey ( IndexDeployment . StaticSearchEngineTypeKey ) , "The database is set to Lucene, so the rebuilt index inherits it" ) ;
233+ }
234+
235+ [ Test ]
236+ public async Task Unchanged_index_without_a_search_engine_setting_should_not_be_rebuilt_on_setup ( )
237+ {
238+ var statsBefore = await PutIndexWithoutSearchEngineSetting ( customize : false ) ;
239+
240+ await DatabaseSetup . CreateIndexes ( configuration . DocumentStore , true , TestTimeoutCancellationToken ) ;
241+
242+ var replacement = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( Constants . Documents . Indexing . SideBySideIndexNamePrefix + statsBefore . Name ) , TestTimeoutCancellationToken ) ;
243+ var statsAfter = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexStatisticsOperation ( statsBefore . Name ) , TestTimeoutCancellationToken ) ;
244+ var definitionAfter = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( statsBefore . Name ) , TestTimeoutCancellationToken ) ;
245+
246+ Assert . That ( replacement , Is . Null , "Adding a search engine setting to an unchanged index would make RavenDB rebuild it" ) ;
247+ Assert . That ( statsAfter . CreatedTimestamp , Is . EqualTo ( statsBefore . CreatedTimestamp ) ) ;
248+ Assert . That ( definitionAfter . Configuration , Does . Not . ContainKey ( IndexDeployment . StaticSearchEngineTypeKey ) ) ;
249+ }
250+
172251 [ Test ]
173252 public async Task Indexes_should_not_be_reset_on_setup_when_locked_as_ignore ( )
174253 {
@@ -211,6 +290,45 @@ async Task<IndexStats> PutCustomizedIndex(SearchEngineType searchEngineType, Ind
211290 return await WaitForIndexDefinitionUpdate ( statsBefore ) ;
212291 }
213292
293+ // Simulates an index that inherits the search engine from the database, for example after a reset in RavenDB Studio.
294+ async Task < IndexStats > PutIndexWithoutSearchEngineSetting ( bool customize )
295+ {
296+ var index = new MessagesViewIndexWithFullTextSearch { Conventions = configuration . DocumentStore . Conventions } ;
297+ var definition = index . CreateIndexDefinition ( ) ;
298+ definition . Name = index . IndexName ;
299+
300+ if ( customize )
301+ {
302+ definition . Fields [ CustomizedField ] = new IndexFieldOptions { Storage = FieldStorage . Yes } ;
303+ }
304+
305+ var statsBefore = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexStatisticsOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
306+ var definitionBefore = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
307+
308+ await configuration . DocumentStore . Maintenance . SendAsync ( new PutIndexesOperation ( definition ) , TestTimeoutCancellationToken ) ;
309+
310+ // In a Lucene database setup creates the index without a setting of its own. The put is then a no-op and there is no update to wait for.
311+ var stats = definitionBefore . Compare ( definition ) == IndexDefinitionCompareDifferences . None ? statsBefore : await WaitForIndexDefinitionUpdate ( statsBefore ) ;
312+ var storedDefinition = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
313+
314+ Assert . That ( storedDefinition . Configuration , Does . Not . ContainKey ( IndexDeployment . StaticSearchEngineTypeKey ) ) ;
315+
316+ return stats ;
317+ }
318+
319+ async Task SetDatabaseSearchEngine ( SearchEngineType searchEngineType )
320+ {
321+ var store = configuration . DocumentStore ;
322+ var record = await store . Maintenance . Server . SendAsync ( new GetDatabaseRecordOperation ( store . Database ) , TestTimeoutCancellationToken ) ;
323+
324+ record . Settings [ "Indexing.Auto.SearchEngineType" ] = searchEngineType . ToString ( ) ;
325+ record . Settings [ "Indexing.Static.SearchEngineType" ] = searchEngineType . ToString ( ) ;
326+
327+ await store . Maintenance . ForDatabase ( store . Database ) . SendAsync ( new PutDatabaseSettingsOperation ( store . Database , record . Settings ) , TestTimeoutCancellationToken ) ;
328+ await store . Maintenance . Server . SendAsync ( new ToggleDatabasesStateOperation ( store . Database , true ) , TestTimeoutCancellationToken ) ;
329+ await store . Maintenance . Server . SendAsync ( new ToggleDatabasesStateOperation ( store . Database , false ) , TestTimeoutCancellationToken ) ;
330+ }
331+
214332 async Task < IndexStats > UpdateIndex ( IAbstractIndexCreationTask index )
215333 {
216334 var statsBefore = await configuration . DocumentStore . Maintenance . SendAsync ( new GetIndexStatisticsOperation ( index . IndexName ) , TestTimeoutCancellationToken ) ;
0 commit comments