11using System ;
2- using System . Collections . Generic ;
32using System . Linq ;
3+ using System . Text ;
44using System . Text . RegularExpressions ;
55using System . Threading ;
66using System . Threading . Tasks ;
1919
2020namespace Migrator . Tests . Database . DerivedDatabaseIntegrationTestServices ;
2121
22- public class OracleDatabaseIntegrationTestService (
22+ public partial class OracleDatabaseIntegrationTestService (
2323 TimeProvider timeProvider ,
2424 IDatabaseNameService databaseNameService )
2525 : DatabaseIntegrationTestServiceBase ( databaseNameService ) , IDatabaseIntegrationTestService
@@ -61,8 +61,6 @@ public class OracleDatabaseIntegrationTestService(
6161 /// <exception cref="NotImplementedException"></exception>
6262 public override async Task < DatabaseInfo > CreateTestDatabaseAsync ( DatabaseConnectionConfig databaseConnectionConfig , CancellationToken cancellationToken )
6363 {
64- DataConnection context ;
65-
6664 var tempDatabaseConnectionConfig = databaseConnectionConfig . Adapt < DatabaseConnectionConfig > ( ) ;
6765
6866 var connectionStringBuilder = new OracleConnectionStringBuilder ( )
@@ -82,15 +80,12 @@ public override async Task<DatabaseInfo> CreateTestDatabaseAsync(DatabaseConnect
8280
8381 var tempUserName = DatabaseNameService . CreateDatabaseName ( ) ;
8482
85- List < string > userNames ;
86-
8783 var dataOptions = new DataOptions ( ) . UseOracle ( databaseConnectionConfig . ConnectionString )
8884 . UseMappingSchema ( _mappingSchema ) ;
8985
90- using ( context = new DataConnection ( dataOptions ) )
91- {
92- userNames = await context . QueryToListAsync < string > ( "SELECT username FROM all_users" , cancellationToken ) ;
93- }
86+ using var context = new DataConnection ( dataOptions ) ;
87+
88+ var userNames = await context . GetTable < AllUsers > ( ) . Select ( x => x . UserName ) . ToListAsync ( cancellationToken ) ;
9489
9590 var toBeDeletedUsers = userNames . Where ( x =>
9691 {
@@ -112,49 +107,84 @@ await Parallel.ForEachAsync(
112107 } ;
113108
114109 await DropDatabaseAsync ( databaseInfoToBeDeleted , cancellationTokenInner ) ;
110+ } ) ;
111+
112+ // To be on the safe side we check for table spaces used in tests that have not been deleted for any reason (possible connection issues/concurrent deletion attempts - there is
113+ // no transaction for DDL in Oracle etc.).
114+ var tableSpaceNames = await context . GetTable < DBADataFiles > ( )
115+ . Select ( x => x . TablespaceName )
116+ . ToListAsync ( cancellationToken ) ;
115117
118+ var toBeDeletedTableSpaces = tableSpaceNames
119+ . Where ( x =>
120+ {
121+ var replacedTablespaceString = TableSpacePrefixRegex ( ) . Replace ( x , "" ) ;
122+ var creationDate = DatabaseNameService . ReadTimeStampFromString ( replacedTablespaceString ) ;
123+ return creationDate . HasValue && creationDate . Value < timeProvider . GetUtcNow ( ) . Subtract ( _MinTimeSpanBeforeDatabaseDeletion ) ;
116124 } ) ;
117125
118- using ( context = new DataConnection ( dataOptions ) )
126+ foreach ( var toBeDeletedTableSpace in toBeDeletedTableSpaces )
119127 {
120- // To be on the safe side we check for table spaces used in tests that have not been deleted for any reason (possible connection issues/concurrent deletion attempts - there is
121- // no transaction for DDL in Oracle etc.).
122- var tableSpaceNames = await context . GetTable < DBADataFiles > ( )
123- . Select ( x => x . TablespaceName )
124- . ToListAsync ( cancellationToken ) ;
125-
126- var toBeDeletedTableSpaces = tableSpaceNames
127- . Where ( x =>
128- {
129- var replacedTablespaceString = _tablespaceRegex . Replace ( x , "" ) ;
130- var creationDate = DatabaseNameService . ReadTimeStampFromString ( replacedTablespaceString ) ;
131- return creationDate . HasValue && creationDate . Value < timeProvider . GetUtcNow ( ) . Subtract ( _MinTimeSpanBeforeDatabaseDeletion ) ;
132- } ) ;
128+ var maxAttempts = 4 ;
129+ var delayBetweenAttempts = TimeSpan . FromSeconds ( 1 ) ;
133130
134- foreach ( var toBeDeletedTableSpace in toBeDeletedTableSpaces )
131+ for ( var i = 0 ; i < maxAttempts ; i ++ )
135132 {
136- await context . ExecuteAsync ( $ "DROP TABLESPACE { toBeDeletedTableSpace } INCLUDING CONTENTS AND DATAFILES", cancellationToken ) ;
137- }
133+ try
134+ {
135+ await context . ExecuteAsync ( $ "DROP TABLESPACE { toBeDeletedTableSpace } INCLUDING CONTENTS AND DATAFILES", cancellationToken ) ;
136+ }
137+ catch
138+ {
139+ var exists = await context . GetTable < DBADataFiles > ( ) . AnyAsync ( x => x . TablespaceName == toBeDeletedTableSpace , cancellationToken ) ;
138140
139- await context . ExecuteAsync ( $ "CREATE USER \" { tempUserName } \" IDENTIFIED BY \" { tempUserName } \" ", cancellationToken ) ;
141+ if ( ! exists )
142+ {
143+ break ;
144+ }
140145
141- var privileges = new [ ]
142- {
143- "CONNECT" ,
144- "CREATE SESSION" ,
145- "RESOURCE" ,
146- "UNLIMITED TABLESPACE"
147- } ;
148-
149- await context . ExecuteAsync ( $ "GRANT { string . Join ( ", " , privileges ) } TO \" { tempUserName } \" " , cancellationToken ) ;
150- await context . ExecuteAsync ( $ "GRANT SELECT ON SYS.V_$SESSION TO \" { tempUserName } \" " , cancellationToken ) ;
146+ if ( i + 1 == maxAttempts )
147+ {
148+ throw ;
149+ }
150+
151+ await Task . Delay ( delayBetweenAttempts , cancellationToken ) ;
152+
153+ delayBetweenAttempts = delayBetweenAttempts . Add ( TimeSpan . FromSeconds ( 1 ) ) ;
154+ }
155+ }
151156 }
152157
158+ var tableSpaceName = $ "{ TableSpacePrefix } { tempUserName } ";
159+
160+ var createTablespaceSql = $ "CREATE TABLESPACE { tableSpaceName } ";
161+ await context . ExecuteAsync ( createTablespaceSql , cancellationToken : cancellationToken ) ;
162+
163+ var stringBuilder = new StringBuilder ( ) ;
164+ stringBuilder . Append ( $ "CREATE USER \" { tempUserName } \" IDENTIFIED BY \" { tempUserName } \" ") ;
165+ stringBuilder . AppendLine ( $ "DEFAULT TABLESPACE { tableSpaceName } ") ;
166+ stringBuilder . AppendLine ( $ "TEMPORARY TABLESPACE TEMP") ;
167+ stringBuilder . AppendLine ( $ "QUOTA UNLIMITED ON { tableSpaceName } ") ;
168+
169+ await context . ExecuteAsync ( stringBuilder . ToString ( ) , cancellationToken ) ;
170+
171+ var privileges = new [ ]
172+ {
173+ "CONNECT" ,
174+ "CREATE SESSION" ,
175+ "RESOURCE" ,
176+ "UNLIMITED TABLESPACE"
177+ } ;
178+
179+ await context . ExecuteAsync ( $ "GRANT { string . Join ( ", " , privileges ) } TO \" { tempUserName } \" ", cancellationToken ) ;
180+ await context . ExecuteAsync ( $ "GRANT SELECT ON SYS.GV_$SESSION TO \" { tempUserName } \" ", cancellationToken ) ;
181+
153182 connectionStringBuilder . Add ( UserStringKey , ReplaceString ) ;
154183 connectionStringBuilder . Add ( PasswordStringKey , ReplaceString ) ;
155184
156185 tempDatabaseConnectionConfig . ConnectionString = connectionStringBuilder . ConnectionString ;
157186 tempDatabaseConnectionConfig . ConnectionString = tempDatabaseConnectionConfig . ConnectionString . Replace ( ReplaceString , $ "\" { tempUserName } \" ") ;
187+ tempDatabaseConnectionConfig . Schema = tempUserName ;
158188
159189 var databaseInfo = new DatabaseInfo
160190 {
@@ -168,16 +198,18 @@ await Parallel.ForEachAsync(
168198
169199 public override async Task DropDatabaseAsync ( DatabaseInfo databaseInfo , CancellationToken cancellationToken )
170200 {
201+ ArgumentNullException . ThrowIfNull ( databaseInfo ) ;
202+
171203 var creationDate = ReadTimeStampFromDatabaseName ( databaseInfo . SchemaName ) ;
172204
173205 var dataOptions = new DataOptions ( ) . UseOracle ( databaseInfo . DatabaseConnectionConfigMaster . ConnectionString )
174206 . UseMappingSchema ( _mappingSchema ) ;
175207
208+ using var context = new DataConnection ( dataOptions ) ;
209+
176210 var maxAttempts = 4 ;
177211 var delayBetweenAttempts = TimeSpan . FromSeconds ( 1 ) ;
178212
179- using var context = new DataConnection ( dataOptions ) ;
180-
181213 for ( var i = 0 ; i < maxAttempts ; i ++ )
182214 {
183215 try
@@ -192,6 +224,13 @@ public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, Cancella
192224 await context . ExecuteAsync ( killStatement , cancellationToken ) ;
193225 }
194226
227+ var userExists = context . GetTable < AllUsers > ( ) . Any ( x => x . UserName == databaseInfo . SchemaName ) ;
228+
229+ if ( ! userExists )
230+ {
231+ break ;
232+ }
233+
195234 await context . ExecuteAsync ( $ "DROP USER \" { databaseInfo . SchemaName } \" CASCADE", cancellationToken ) ;
196235 }
197236 catch
@@ -207,19 +246,47 @@ public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, Cancella
207246 {
208247 break ;
209248 }
210- }
211249
212- await Task . Delay ( delayBetweenAttempts , cancellationToken ) ;
250+ await Task . Delay ( delayBetweenAttempts , cancellationToken ) ;
213251
214- delayBetweenAttempts = delayBetweenAttempts . Add ( TimeSpan . FromSeconds ( 1 ) ) ;
252+ delayBetweenAttempts = delayBetweenAttempts . Add ( TimeSpan . FromSeconds ( 1 ) ) ;
253+ }
215254 }
216255
217256 var tablespaceName = $ "{ TableSpacePrefix } { databaseInfo . SchemaName } ";
218257
219- var tablespaces = await context . GetTable < DBADataFiles > ( ) . ToListAsync ( cancellationToken ) ;
258+ maxAttempts = 4 ;
259+ delayBetweenAttempts = TimeSpan . FromSeconds ( 1 ) ;
260+
261+ for ( var i = 0 ; i < maxAttempts ; i ++ )
262+ {
263+ try
264+ {
265+ await context . ExecuteAsync ( $ "DROP TABLESPACE { tablespaceName } INCLUDING CONTENTS AND DATAFILES", cancellationToken ) ;
266+ }
267+ catch
268+ {
269+ var exists = await context . GetTable < DBADataFiles > ( ) . AnyAsync ( x => x . TablespaceName == tablespaceName , cancellationToken ) ;
270+
271+ if ( ! exists )
272+ {
273+ break ;
274+ }
220275
221- await context . ExecuteAsync ( $ "DROP TABLESPACE { tablespaceName } INCLUDING CONTENTS AND DATAFILES", cancellationToken ) ;
276+ if ( i + 1 == maxAttempts )
277+ {
278+ throw ;
279+ }
280+
281+ await Task . Delay ( delayBetweenAttempts , cancellationToken ) ;
282+
283+ delayBetweenAttempts = delayBetweenAttempts . Add ( TimeSpan . FromSeconds ( 1 ) ) ;
284+ }
285+ }
222286
223287 await context . ExecuteAsync ( $ "PURGE RECYCLEBIN", cancellationToken ) ;
224288 }
225- }
289+
290+ [ GeneratedRegex ( "^TS_TESTS_" ) ]
291+ private static partial Regex TableSpacePrefixRegex ( ) ;
292+ }
0 commit comments