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

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 Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Npgsql" Version="7.0.1" />
<PackageReference Include="Spectre.Console" Version="0.46.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
</ItemGroup>

Expand Down
50 changes: 50 additions & 0 deletions Helpers/SpectreConsoleHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Spectre.Console;

namespace Application.Helpers;

internal class SpectreConsoleHelper
{
public static void Log(string message) => Write($"LOG: {message}", _messageTypes[MessageTypeEnum.Log]);

public static void Error(string error) => Write($"ERROR: {error}", _messageTypes[MessageTypeEnum.Error]);

public static void Information(string information) => Write($"INFO: {information}", _messageTypes[MessageTypeEnum.Information]);

public static void Success(string success) => Write($"LOG: {success}", _messageTypes[MessageTypeEnum.Success]);

public static void Warning(string warning) => Write($"WARN: {warning}", _messageTypes[MessageTypeEnum.Warning]);

private static void Write(string message, string color)
{
AnsiConsole.MarkupLine(string.Format("[{0}]{1}[/]", color, message));
}

public static void WriteHeader(string header, Color color)
{
AnsiConsole.Write(new FigletText(header)
.Centered()
.Color(color));
}

#region Privates

private enum MessageTypeEnum
{
Log,
Error,
Information,
Success,
Warning
}

private static readonly Dictionary<MessageTypeEnum, string> _messageTypes = new()
{
[MessageTypeEnum.Log] = "white",
[MessageTypeEnum.Error] = "red",
[MessageTypeEnum.Information] = "blue",
[MessageTypeEnum.Success] = "green",
[MessageTypeEnum.Warning] = "yellow"
};

#endregion
}
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,45 @@
# postgresql-to-mssql

Migrate data from postgresql to sql server on the fly!

### Description

This is a .NET console application that handles data migration (data from tables and views) from postgresql to sql server. It aims to make the process of migrating data from one database to another as seamless and easy as possible.

### Motivations

As an open source enthusiast, I noticed the lack of workable open source tools for migrating data between databases, and the high cost of some paid tools. I wanted to make a contribution to the open source community by creating a tool that is both effective and accessible to all.

### Features
- Migrates data from tables and views in PostgreSQL to SQL Server
- Written in .NET for high performance and compatibility with a wide range of systems
- Easy to use console interface
- Open source and free to use

### Getting Started

1. Clone or download the repository
2. Open the solution in Visual Studio
3. Build the solution
4. Run the executable
5. Follow the prompts to configure the migration

### Contributing

If you would like to contribute to this project, please fork the repository and submit a pull request.

### License

This project is licensed under the MIT License

### Acknowledgements

Thank you to the open source community for all of the support and resources that made this project possible.

### Support

If you have any questions or issues, please open an discussion on GitHub or contact me directly.

#### Happy migrating!

<sub>PS. This README is generated by ChatGPT</sub>
119 changes: 69 additions & 50 deletions Services/Service.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Data;
using System.Data.SqlClient;
using Application.Helpers;
using Application.Providers.Interfaces;
using Application.Services.Interfaces;
using Application.Validators.Interfaces;
using Dapper;
using Spectre.Console;

namespace Application;

Expand All @@ -27,60 +29,77 @@ public void Migrate()
{
_validator.ValidateProviders();

using var postgresConnection = _provider.GetPostgresqlConnection();
using var sqlServerConnection = _provider.GetSqlServerConnection();

postgresConnection.Open();
sqlServerConnection.Open();

// get list of schemas
var getSchemasQuery = "SELECT schema_name FROM information_schema.schemata";
var schemas = postgresConnection.Query<string>(getSchemasQuery).ToList();

// remove postgres schemas
schemas.Remove("information_schema");
schemas.Remove("pg_catalog");
schemas.Remove("pg_toast");

foreach (var sourceSchema in schemas)
{
// modify unsupported schemas
string destinationSchema = $"{sourceSchema}_new";

// create schema
var createDestinationSchemaQuery = $"CREATE SCHEMA [{destinationSchema}];";
sqlServerConnection.Execute(createDestinationSchemaQuery);

// get list of tables
var getTablesQuery = $"SELECT table_name FROM information_schema.tables WHERE table_schema = '{sourceSchema}'";
var tables = postgresConnection.Query<string>(getTablesQuery).ToList();

foreach (var table in tables)
SpectreConsoleHelper.WriteHeader("postgresql to mssql", Color.Blue);
SpectreConsoleHelper.Log("Initializing...");
AnsiConsole.Status()
.Spinner(Spinner.Known.Arc)
.SpinnerStyle(Style.Parse("green"))
.Start("Starting the migration...", ctx =>
{
// get the table column's definition
var getColumnsQuery = $"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{table}' AND table_schema = '{sourceSchema}'";
var columns = postgresConnection.Query(getColumnsQuery);

// create the table in sql server
var createTableQuery = $"CREATE TABLE {destinationSchema}.{table} (";
createTableQuery += string.Join(", ", columns.Select(c => $"{c.column_name} {MapPostgresToSqlServerType(c.data_type)}"));
createTableQuery += ")";
sqlServerConnection.Execute(createTableQuery);

// fetch data from postgres
var data = postgresConnection.Query<dynamic>($"SELECT * FROM {sourceSchema}.{table}").ToList();

// bulk copy to sql server
using var bulkCopy = new SqlBulkCopy(sqlServerConnection);
var dataTable = ToDataTable(data);
bulkCopy.DestinationTableName = $"[{destinationSchema}].[{table}]";
bulkCopy.WriteToServer(dataTable);
}
}
using var postgresConnection = _provider.GetPostgresqlConnection();
using var sqlServerConnection = _provider.GetSqlServerConnection();

ctx.Status("Fetching postgresql schemas");
ctx.Spinner(Spinner.Known.BouncingBall);
var getSchemasQuery = "SELECT schema_name FROM information_schema.schemata";
var schemas = postgresConnection.Query<string>(getSchemasQuery).ToList();
SpectreConsoleHelper.Log("Fetched schemas from postgresql...");

schemas.Remove("information_schema");
schemas.Remove("pg_catalog");
schemas.Remove("pg_toast");

ctx.Status("Looping through available schemas...");
foreach (var sourceSchema in schemas)
{
string destinationSchema = $"{sourceSchema}_new";

ctx.Status($"Creating [{destinationSchema}] schema in sql server...");
var createDestinationSchemaQuery = $"CREATE SCHEMA [{destinationSchema}];";
sqlServerConnection.Execute(createDestinationSchemaQuery);
SpectreConsoleHelper.Log($"Created [{destinationSchema}] schema in sql server...");

ctx.Status($"Fetching available tables from [{sourceSchema}] schema...");
var getTablesQuery = $"SELECT table_name FROM information_schema.tables WHERE table_schema = '{sourceSchema}'";
var tables = postgresConnection.Query<string>(getTablesQuery).ToList();
SpectreConsoleHelper.Log($"Fetched tables of [{sourceSchema}] schema from postgres");

ctx.Status($"Looping through all tables of [{sourceSchema}] schema...");
foreach (var table in tables)
{
ctx.Status($"Fetching column definition for [{table}] table...");
var getColumnsQuery = $"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{table}' AND table_schema = '{sourceSchema}'";
var columns = postgresConnection.Query(getColumnsQuery);
SpectreConsoleHelper.Log($"Fetched column definition for [{table}] table...");

ctx.Status($"Creating table [{destinationSchema}].[{table}] in sql server...");
var createTableQuery = $"CREATE TABLE {destinationSchema}.{table} (";
createTableQuery += string.Join(", ", columns.Select(c => $"{c.column_name} {MapPostgresToSqlServerType(c.data_type)}"));
createTableQuery += ")";
sqlServerConnection.Execute(createTableQuery);
SpectreConsoleHelper.Log($"Created table [{destinationSchema}].[{table}] in sql server...");

ctx.Status($"Fetching data from [{sourceSchema}].[{table}] from postgresql...");
var data = postgresConnection.Query<dynamic>($"SELECT * FROM {sourceSchema}.{table}").ToList();
SpectreConsoleHelper.Log($"Fetched data from [{sourceSchema}].[{table}] table of postgresql...");

ctx.Status("Coverting the data into proper shape before migrating to sql server...");
var dataTable = ToDataTable(data);
SpectreConsoleHelper.Log("Converted data into proper shape...");

ctx.Status($"Transferring data from [blue][{sourceSchema}].[{table}][/] to [green][{destinationSchema}].[{table}][/]");
using var bulkCopy = new SqlBulkCopy(sqlServerConnection);
bulkCopy.DestinationTableName = $"[{destinationSchema}].[{table}]";
bulkCopy.WriteToServer(dataTable);
SpectreConsoleHelper.Success($"Successfully transferred data from [{sourceSchema}].[{table}] to [{destinationSchema}].[{table}]");
}
}
});
SpectreConsoleHelper.WriteHeader("Success!", Color.Green);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
AnsiConsole.WriteException(ex);
}
}

Expand Down
19 changes: 11 additions & 8 deletions Validators/Validator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Application.Helpers;
using Application.Providers.Interfaces;
using Application.Validators.Interfaces;
using Spectre.Console;

namespace Application.Validators;

Expand All @@ -21,34 +22,36 @@ public void ValidateProviders()
{
if (!IsServerConnected(sqlServerConnection))
{
Console.WriteLine("Invalid SQL Server Connection!");
SpectreConsoleHelper.Warning("Invalid sql server connection..");
SpectreConsoleHelper.Information("Provide the valid sql server connection string...");
AnsiConsole.MarkupLine("[green]PROMPT:[/] [red]sql server connection string sample:[/] [blue]server=serverName; database=databaseName; user=username; password=password;[/]");

Console.WriteLine("Provide the valid SQL Server Connection String...");
var connectionString = Console.ReadLine();
EnvironmentVariableHelper.Set("SqlServerConnectionString", connectionString);

Console.WriteLine("SqlServerConnectionString Set Successfully!");
SpectreConsoleHelper.Success("Sql server connection string set successfully...");

if (!IsServerConnected(sqlServerConnection)) goto sqlServerStart;
}
}
SpectreConsoleHelper.Success("Sql server connected...");

postgreSqlStart:
using (var postgreSqlConnection = _provider.GetPostgresqlConnection())
{
if (!IsServerConnected(postgreSqlConnection))
{
Console.WriteLine("Invalid PostgreSQL Connection!");
SpectreConsoleHelper.Warning("Invalid postgresql connection..");
SpectreConsoleHelper.Information("Provide the valid [green]postgresql connection string");
AnsiConsole.MarkupLine("[green]PROMPT:[/] [red]postgresql connection string sample:[/] [blue]Server=127.0.0.1;Port=5432;Database=databaseName;User Id=postgres;Password=password;[/]");

Console.WriteLine("Provide the valid PostgreSQL Connection String...");
var connectionString = Console.ReadLine();
EnvironmentVariableHelper.Set("PostgresqlConnectionString", connectionString);

Console.WriteLine("PostgresqlConnectionString Set Successfully!");
SpectreConsoleHelper.Success("Postgresql connection string set successfully...");

if (!IsServerConnected(postgreSqlConnection)) goto postgreSqlStart;
}
}
SpectreConsoleHelper.Success("PostgreSQL connected...");
}

#region Private methods
Expand Down