Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/sql/oracle.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ grant resource to k with admin option;
grant connect to k with admin option;
grant unlimited tablespace to k with admin option;
grant select on v_$session to k with grant option;
grant select on sys.gv_$session to k with grant option;
grant alter system to k;

exit;
30 changes: 20 additions & 10 deletions src/Migrator.Tests/Database/DatabaseName/DatabaseNameService.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using Migrator.Tests.Database.DatabaseName.Interfaces;
using Migrator.Tests.Database.GuidServices.Interfaces;

namespace Migrator.Test.Shared.Database;

public partial class DatabaseNameService(TimeProvider timeProvider, IGuidService guidService) : IDatabaseNameService
public partial class DatabaseNameService(TimeProvider timeProvider) : IDatabaseNameService
{
private const string TestDatabaseString = "Test";
private const string TestDatabaseString = "T";
private const string TimeStampPattern = "yyyyMMddHHmmssfff";

public DateTime? ReadTimeStampFromString(string name)
Expand All @@ -33,14 +32,25 @@ public string CreateDatabaseName()
var dateTimePattern = timeProvider.GetUtcNow()
.ToString(TimeStampPattern);

var randomString = string.Concat(guidService.NewGuid()
.ToString("N")
.Reverse()
.Take(9));
var randomString = CreateRandomChars(7);

return $"{dateTimePattern}{TestDatabaseString}{randomString}";
}

[GeneratedRegex(@"^(\d+)(?=Test.{9}$)")]
private string CreateRandomChars(int length)
{
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[length];

for (var i = 0; i < length; i++)
{
var index = RandomNumberGenerator.GetInt32(chars.Length);
stringChars[i] = chars[index];
}

return new string(stringChars);
}

[GeneratedRegex(@"^([\d]+)(?=T.{7}$)")]
private static partial Regex DateTimeRegex();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DotNetProjects.Migrator.Framework.Data.Common;
Expand All @@ -19,17 +18,21 @@

namespace Migrator.Tests.Database.DerivedDatabaseIntegrationTestServices;


/// <summary>
/// We use the tablespace users since the server container is recreated before the test runs (once per github workflow run)
/// </summary>
/// <param name="timeProvider"></param>
/// <param name="databaseNameService"></param>
public class OracleDatabaseIntegrationTestService(
TimeProvider timeProvider,
IDatabaseNameService databaseNameService)
: DatabaseIntegrationTestServiceBase(databaseNameService), IDatabaseIntegrationTestService
{
private const string TableSpacePrefix = "TS_";
private const string UserStringKey = "User Id";
private const string PasswordStringKey = "Password";
private const string ReplaceString = "RandomStringThatIsNotQuotedByTheBuilderDoNotChange";
private readonly MappingSchema _mappingSchema = new MappingSchemaFactory().CreateOracleMappingSchema();
private Regex _tablespaceRegex = new("^TS_TESTS_");

/// <summary>
/// Creates an oracle database for test purposes.
Expand Down Expand Up @@ -61,8 +64,6 @@ public class OracleDatabaseIntegrationTestService(
/// <exception cref="NotImplementedException"></exception>
public override async Task<DatabaseInfo> CreateTestDatabaseAsync(DatabaseConnectionConfig databaseConnectionConfig, CancellationToken cancellationToken)
{
DataConnection context;

var tempDatabaseConnectionConfig = databaseConnectionConfig.Adapt<DatabaseConnectionConfig>();

var connectionStringBuilder = new OracleConnectionStringBuilder()
Expand All @@ -82,15 +83,12 @@ public override async Task<DatabaseInfo> CreateTestDatabaseAsync(DatabaseConnect

var tempUserName = DatabaseNameService.CreateDatabaseName();

List<string> userNames;

var dataOptions = new DataOptions().UseOracle(databaseConnectionConfig.ConnectionString)
.UseMappingSchema(_mappingSchema);

using (context = new DataConnection(dataOptions))
{
userNames = await context.QueryToListAsync<string>("SELECT username FROM all_users", cancellationToken);
}
using var context = new DataConnection(dataOptions);

var userNames = await context.GetTable<AllUsers>().Select(x => x.UserName).ToListAsync(cancellationToken);

var toBeDeletedUsers = userNames.Where(x =>
{
Expand All @@ -112,49 +110,33 @@ await Parallel.ForEachAsync(
};

await DropDatabaseAsync(databaseInfoToBeDeleted, cancellationTokenInner);

});

using (context = new DataConnection(dataOptions))
{
// 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
// no transaction for DDL in Oracle etc.).
var tableSpaceNames = await context.GetTable<DBADataFiles>()
.Select(x => x.TablespaceName)
.ToListAsync(cancellationToken);

var toBeDeletedTableSpaces = tableSpaceNames
.Where(x =>
{
var replacedTablespaceString = _tablespaceRegex.Replace(x, "");
var creationDate = DatabaseNameService.ReadTimeStampFromString(replacedTablespaceString);
return creationDate.HasValue && creationDate.Value < timeProvider.GetUtcNow().Subtract(_MinTimeSpanBeforeDatabaseDeletion);
});
var stringBuilder = new StringBuilder();
stringBuilder.Append($"CREATE USER \"{tempUserName}\" IDENTIFIED BY \"{tempUserName}\"");
stringBuilder.AppendLine($"DEFAULT TABLESPACE users");
stringBuilder.AppendLine($"TEMPORARY TABLESPACE TEMP");
stringBuilder.AppendLine($"QUOTA UNLIMITED ON users");

foreach (var toBeDeletedTableSpace in toBeDeletedTableSpaces)
{
await context.ExecuteAsync($"DROP TABLESPACE {toBeDeletedTableSpace} INCLUDING CONTENTS AND DATAFILES", cancellationToken);
}
await context.ExecuteAsync(stringBuilder.ToString(), cancellationToken);

await context.ExecuteAsync($"CREATE USER \"{tempUserName}\" IDENTIFIED BY \"{tempUserName}\"", cancellationToken);
var privileges = new[]
{
"CONNECT",
"CREATE SESSION",
"RESOURCE",
"UNLIMITED TABLESPACE"
};

var privileges = new[]
{
"CONNECT",
"CREATE SESSION",
"RESOURCE",
"UNLIMITED TABLESPACE"
};

await context.ExecuteAsync($"GRANT {string.Join(", ", privileges)} TO \"{tempUserName}\"", cancellationToken);
await context.ExecuteAsync($"GRANT SELECT ON SYS.V_$SESSION TO \"{tempUserName}\"", cancellationToken);
}
await context.ExecuteAsync($"GRANT {string.Join(", ", privileges)} TO \"{tempUserName}\"", cancellationToken);
await context.ExecuteAsync($"GRANT SELECT ON SYS.GV_$SESSION TO \"{tempUserName}\"", cancellationToken);

connectionStringBuilder.Add(UserStringKey, ReplaceString);
connectionStringBuilder.Add(PasswordStringKey, ReplaceString);

tempDatabaseConnectionConfig.ConnectionString = connectionStringBuilder.ConnectionString;
tempDatabaseConnectionConfig.ConnectionString = tempDatabaseConnectionConfig.ConnectionString.Replace(ReplaceString, $"\"{tempUserName}\"");
tempDatabaseConnectionConfig.Schema = tempUserName;

var databaseInfo = new DatabaseInfo
{
Expand All @@ -168,16 +150,18 @@ await Parallel.ForEachAsync(

public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(databaseInfo);

var creationDate = ReadTimeStampFromDatabaseName(databaseInfo.SchemaName);

var dataOptions = new DataOptions().UseOracle(databaseInfo.DatabaseConnectionConfigMaster.ConnectionString)
.UseMappingSchema(_mappingSchema);

using var context = new DataConnection(dataOptions);

var maxAttempts = 4;
var delayBetweenAttempts = TimeSpan.FromSeconds(1);

using var context = new DataConnection(dataOptions);

for (var i = 0; i < maxAttempts; i++)
{
try
Expand All @@ -192,6 +176,13 @@ public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, Cancella
await context.ExecuteAsync(killStatement, cancellationToken);
}

var userExists = context.GetTable<AllUsers>().Any(x => x.UserName == databaseInfo.SchemaName);

if (!userExists)
{
break;
}

await context.ExecuteAsync($"DROP USER \"{databaseInfo.SchemaName}\" CASCADE", cancellationToken);
}
catch
Expand All @@ -207,19 +198,13 @@ public override async Task DropDatabaseAsync(DatabaseInfo databaseInfo, Cancella
{
break;
}
}

await Task.Delay(delayBetweenAttempts, cancellationToken);
await Task.Delay(delayBetweenAttempts, cancellationToken);

delayBetweenAttempts = delayBetweenAttempts.Add(TimeSpan.FromSeconds(1));
delayBetweenAttempts = delayBetweenAttempts.Add(TimeSpan.FromSeconds(1));
}
}

var tablespaceName = $"{TableSpacePrefix}{databaseInfo.SchemaName}";

var tablespaces = await context.GetTable<DBADataFiles>().ToListAsync(cancellationToken);

await context.ExecuteAsync($"DROP TABLESPACE {tablespaceName} INCLUDING CONTENTS AND DATAFILES", cancellationToken);

await context.ExecuteAsync($"PURGE RECYCLEBIN", cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,9 @@ protected void DropTestTables()
}
}


protected async Task BeginOracleTransactionAsync()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
var configReader = new ConfigurationReader();

var databaseConnectionConfig = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.OracleId);
Expand Down
82 changes: 82 additions & 0 deletions src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using DotNetProjects.Migrator.Framework;
using Migrator.Tests.Providers.Base;
using NUnit.Framework;
Expand Down Expand Up @@ -30,6 +32,86 @@ public void AddTable_PrimaryKeyWithIdentity_Success()
Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True);
}

[Test]
public void AddTable_PrimaryKeyAndIdentity_Success()
{
// Arrange
var tableName = "TableName";
var column1Name = "Column1";
var column2Name = "Column2";

// Act
Provider.AddTable(tableName,
new Column(column1Name, DbType.Int32, ColumnProperty.NotNull | ColumnProperty.PrimaryKey | ColumnProperty.Identity),
new Column(column2Name, DbType.Int32, ColumnProperty.NotNull)
);

// Assert
var column1 = Provider.GetColumnByName(tableName, column1Name);
var column2 = Provider.GetColumnByName(tableName, column2Name);

Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.PrimaryKeyWithIdentity), Is.True);
Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True);
}

[Test]
public void AddTable_PrimaryKeyAndIdentityWithInsertNull_Success()
{
// Arrange
var tableName = "TableName";
var column1Name = "Column1";
var column2Name = "Column2";

// Act
Provider.AddTable(tableName,
new Column(column1Name, DbType.Int32, ColumnProperty.NotNull | ColumnProperty.PrimaryKey | ColumnProperty.Identity),
new Column(column2Name, DbType.Int32, ColumnProperty.NotNull)
);

Provider.Insert(table: tableName, [column2Name], [999]);

// Assert
var column1 = Provider.GetColumnByName(tableName, column1Name);
var column2 = Provider.GetColumnByName(tableName, column2Name);

using var cmd = Provider.CreateCommand();
using var reader = Provider.Select(cmd: cmd, table: tableName, columns: [column1Name, column2Name]);

List<(int, int)> records = [];

while (reader.Read())
{
records.Add((reader.GetInt32(0), reader.GetInt32(1)));
}

Assert.That(records.Single().Item1, Is.EqualTo(1));

Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.PrimaryKeyWithIdentity), Is.True);
Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True);
}

[Test]
public void AddTable_PrimaryKeyAndIdentityWithoutNotNull_Success()
{
// Arrange
var tableName = "TableName";
var column1Name = "Column1";
var column2Name = "Column2";

// Act
Provider.AddTable(tableName,
new Column(column1Name, DbType.Int32, ColumnProperty.PrimaryKey | ColumnProperty.Identity),
new Column(column2Name, DbType.Int32, ColumnProperty.NotNull)
);

// Assert
var column1 = Provider.GetColumnByName(tableName, column1Name);
var column2 = Provider.GetColumnByName(tableName, column2Name);

Assert.That(column1.ColumnProperty.HasFlag(ColumnProperty.PrimaryKeyWithIdentity), Is.True);
Assert.That(column2.ColumnProperty.HasFlag(ColumnProperty.NotNull), Is.True);
}

[Test]
public void AddTable_NotNull_Success()
{
Expand Down
Loading
Loading