Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public static class ConnectionStrings
public static readonly string MSSQL = @"Server=.;Database=SimpleMigratorTests;Trusted_Connection=True;";
public static readonly string MySQL = @"Server=localhost;Database=SimpleMigrator;Uid=SimpleMigrator;Pwd=SimpleMigrator;";
public static readonly string PostgreSQL = @"Server=localhost;Port=5432;Database=SimpleMigrator;User ID=SimpleMigrator;Password=SimpleMigrator";
public static readonly string Oracle = @"Data Source=localhost:32769/ORCLPDB1.localdomain;User ID=SimpleMigrator;Password=SimpleMigrator";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Simple.Migrations.IntegrationTests.Oracle
{
public class OracleStringsProvider : IMigrationStringsProvider
{
public string CreateUsersTableUp => @"CREATE TABLE Users (
Id INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR2(100) NOT NULL
)";
public string CreateUsersTableDown => "DROP TABLE Users PURGE";
}
}
59 changes: 59 additions & 0 deletions src/Simple.Migrations.IntegrationTests/Oracle/OracleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Data.Common;
using Npgsql;
using NUnit.Framework;
using Oracle.ManagedDataAccess.Client;
using Simple.Migrations.IntegrationTests.Postgresql;
using SimpleMigrations;
using SimpleMigrations.DatabaseProvider;

namespace Simple.Migrations.IntegrationTests.Oracle
{
[TestFixture]
public class OracleTests : TestsBase
{
protected override IDatabaseProvider<DbConnection> CreateDatabaseProvider() => new OracleDatabaseProvider(this.CreateConnection);

protected override IMigrationStringsProvider MigrationStringsProvider { get; } = new OracleStringsProvider();

protected override bool SupportConcurrentMigrators => true;

protected override void Clean()
{
var connection = this.CreateOraConnection();
connection.Open();

using (var cmd = new OracleCommand(@"
BEGIN
FOR CURTAB IN (SELECT TABLE_NAME FROM USER_TABLES) LOOP
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE '||curtab.table_name||' PURGE';
EXCEPTION WHEN OTHERS THEN
IF (SQLCODE = -942) THEN
NULL;
ELSE
RAISE;
END IF;
END;
END LOOP;

BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_VERSION_TABLE';
EXCEPTION WHEN OTHERS THEN
IF (SQLCODE = -2289) THEN
NULL;
ELSE
RAISE;
END IF;
END;
END;
", connection))
{
cmd.ExecuteNonQuery();
}
}

private OracleConnection CreateOraConnection() => new OracleConnection(ConnectionStrings.Oracle);

protected override DbConnection CreateConnection() => this.CreateOraConnection();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Simple.Migrations\Simple.Migrations.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="Oracle.ManagedDataAccess" Version="12.2.1100" />
<PackageReference Include="System.Reflection.Extensions" Version="4.0.1" />
<PackageReference Include="NUnit" Version="3.5.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0-ci-00457-pr-313" />
Expand All @@ -32,4 +38,8 @@
<Folder Include="Properties\" />
</ItemGroup>

<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>

</Project>
121 changes: 121 additions & 0 deletions src/Simple.Migrations/DatabaseProvider/OracleDatabaseProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Data.Common;

namespace SimpleMigrations.DatabaseProvider
{
/// <summary>
/// Class which can read from / write to a version table in an PostgreSQL database
/// </summary>
/// <remarks>
/// PostgreSQL supports advisory locks, so these are used to guard against concurrent migrators.
/// </remarks>
public class OracleDatabaseProvider : DatabaseProviderBaseWithVersionTableLock
{
/// <summary>
/// Name of Oracle Sequence for generating Id in version table
///
/// </summary>
public string VersionSequenceName { get; set; } = "SEQ_VERSION_TABLE";

/// <summary>
/// Controls whether or not to try and create the schema if it does not exist.
/// </summary>
/// <remarks>
/// If this is set to false then no schema is created. It is the user's responsibility to create the schema
/// (if necessary) with the correct name and permissions before running the <see cref="SimpleMigrator"/>. This may be
/// required if the user which Simple.Migrator is running as does not have the correct permissions to check whether the
/// schema has been created.
/// </remarks>
public bool CreateSchema { get; set; } = false;


/// <summary>
/// Initialises a new instance of the <see cref="OracleDatabaseProvider"/> class
/// </summary>
/// <param name="connectionFactory">Connection to use to run migrations. The caller is responsible for closing this.</param>
public OracleDatabaseProvider(Func<DbConnection> connectionFactory)
: base(connectionFactory)
{
TableName = "VERSION_INFO";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is using all caps a particularly strong oracle-ism? I.e. is there a compelling reason to move away from the name used by other database providers?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is standard, oracle names are by default case insensitive and even if you will use small caps it will be stored in dictionary as all caps

}


/// <summary>
/// Returns SQL to create the version table
/// </summary>
/// <returns>SQL to create the version table</returns>
protected override string GetCreateVersionTableSql()
{
return $@"DECLARE
PROCEDURE CREATE_IF_NOT_EXISTS(p_object VARCHAR2, p_sql VARCHAR2)
IS
c_object user_objects.object_name%type;
BEGIN
BEGIN
SELECT object_name INTO c_object FROM user_objects WHERE object_name=p_object;
EXCEPTION WHEN no_data_found then
BEGIN
EXECUTE IMMEDIATE p_sql;
EXCEPTION WHEN OTHERS THEN
IF (SQLCODE = -955) THEN
NULL;
ELSE
RAISE;
END IF;
END;
END;
END;
BEGIN
CREATE_IF_NOT_EXISTS('{this.VersionSequenceName}','CREATE SEQUENCE {this.VersionSequenceName} START WITH 1 NOCACHE');

CREATE_IF_NOT_EXISTS('{this.TableName}','CREATE TABLE {this.TableName}(
ID INTEGER PRIMARY KEY,
VERSION INTEGER NOT NULL,
APPLIED_ON timestamp with time zone,
DESCRIPTION varchar2(4000) NOT NULL
)');
END;
";
}




/// <summary>
/// Returns SQL to fetch the current version from the version table
/// </summary>
/// <returns>SQL to fetch the current version from the version table</returns>
protected override string GetCurrentVersionSql()
{
return $@"SELECT VERSION FROM (SELECT VERSION FROM {this.TableName} ORDER BY ID DESC) WHERE ROWNUM=1";
}

/// <summary>
/// Returns SQL to update the current version in the version table
/// </summary>
/// <returns>SQL to update the current version in the version table</returns>
protected override string GetSetVersionSql()
{
//RETURNING VERSION INTO :OldVersion - required use of bind variable to aviod oracle error
return $@"INSERT INTO {this.TableName} (ID, VERSION, APPLIED_ON, DESCRIPTION) VALUES ({this.VersionSequenceName}.NEXTVAL, :Version, CURRENT_TIMESTAMP, :Description) RETURNING VERSION INTO :OldVersion";
}

protected override void AcquireVersionTableLock()
{
VersionTableLockTransaction = this.VersionTableConnection.BeginTransaction();
using (var cmd = this.VersionTableConnection.CreateCommand())
{
cmd.Transaction = VersionTableLockTransaction;
cmd.CommandText = $"LOCK TABLE {TableName} IN EXCLUSIVE MODE";
cmd.ExecuteNonQuery();
}
}

protected override void ReleaseVersionTableLock()
{
VersionTableLockTransaction.Commit();
VersionTableLockTransaction.Dispose();
VersionTableLockTransaction = null;
}
}
}