Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Commit 7a0ee3f

Browse files
Merge pull request #77 from thefringeninja/separate-initialization
Separate Database / Schema Initializaiton from Running Server
2 parents 87570d2 + 730bb4d commit 7a0ee3f

File tree

6 files changed

+340
-200
lines changed

6 files changed

+340
-200
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace SqlStreamStore.Server
2+
{
3+
internal static class Constants
4+
{
5+
public const string postgres = nameof(postgres);
6+
public const string mssql = nameof(mssql);
7+
public const string mysql = nameof(mysql);
8+
public const string inmemory = nameof(inmemory);
9+
}
10+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using System;
2+
using System.Data.SqlClient;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using MySql.Data.MySqlClient;
6+
using Npgsql;
7+
using Serilog;
8+
using SqlStreamStore.Infrastructure;
9+
10+
namespace SqlStreamStore.Server
11+
{
12+
internal class DatabaseInitializer
13+
{
14+
private readonly SqlStreamStoreServerConfiguration _configuration;
15+
16+
public DatabaseInitializer(SqlStreamStoreServerConfiguration configuration)
17+
{
18+
if (configuration == null)
19+
{
20+
throw new ArgumentNullException(nameof(configuration));
21+
}
22+
23+
_configuration = configuration;
24+
}
25+
26+
public Task Initialize(CancellationToken cancellationToken = default)
27+
{
28+
switch (_configuration.Provider)
29+
{
30+
case Constants.mssql:
31+
return InitializeMsSql(cancellationToken);
32+
case Constants.mysql:
33+
return InitializeMySql(cancellationToken);
34+
case Constants.postgres:
35+
return InitializePostgres(cancellationToken);
36+
default:
37+
Log.Warning("Provider {provider} has no database initializer.", _configuration.Provider);
38+
return Task.CompletedTask;
39+
}
40+
}
41+
42+
private async Task InitializeMySql(CancellationToken cancellationToken)
43+
{
44+
var connectionStringBuilder = new MySqlConnectionStringBuilder(_configuration.ConnectionString);
45+
46+
var cmdText = $"CREATE DATABASE IF NOT EXISTS `{connectionStringBuilder.Database}`";
47+
48+
Log.Information(
49+
"Creating database '{database}' at server '{server}' with the statement: {cmdText}",
50+
connectionStringBuilder.Database,
51+
connectionStringBuilder.Server,
52+
cmdText);
53+
54+
using (var connection = new MySqlConnection(
55+
new MySqlConnectionStringBuilder(_configuration.ConnectionString)
56+
{
57+
Database = null
58+
}.ConnectionString))
59+
{
60+
await connection.OpenAsync(cancellationToken).NotOnCapturedContext();
61+
62+
using (var command = new MySqlCommand(
63+
cmdText,
64+
connection))
65+
{
66+
await command.ExecuteNonQueryAsync(cancellationToken).NotOnCapturedContext();
67+
}
68+
}
69+
}
70+
71+
private async Task InitializeMsSql(CancellationToken cancellationToken)
72+
{
73+
var connectionStringBuilder = new SqlConnectionStringBuilder(_configuration.ConnectionString);
74+
75+
var cmdText = $@"
76+
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'{connectionStringBuilder.InitialCatalog}')
77+
BEGIN
78+
CREATE DATABASE [{connectionStringBuilder.InitialCatalog}]
79+
END;
80+
";
81+
Log.Information(
82+
"Creating database '{database}' at server '{server}' with the statement: {cmdText}",
83+
connectionStringBuilder.InitialCatalog,
84+
connectionStringBuilder.DataSource,
85+
cmdText);
86+
87+
using (var connection = new SqlConnection(
88+
new SqlConnectionStringBuilder(_configuration.ConnectionString)
89+
{
90+
InitialCatalog = "master"
91+
}.ConnectionString))
92+
{
93+
await connection.OpenAsync(cancellationToken).NotOnCapturedContext();
94+
95+
using (var command = new SqlCommand(
96+
cmdText,
97+
connection))
98+
{
99+
await command.ExecuteNonQueryAsync(cancellationToken).NotOnCapturedContext();
100+
}
101+
}
102+
}
103+
104+
private async Task InitializePostgres(CancellationToken cancellationToken)
105+
{
106+
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(_configuration.ConnectionString);
107+
108+
var cmdText = $"CREATE DATABASE {connectionStringBuilder.Database}";
109+
110+
Log.Information(
111+
"Creating database '{database}' at server '{server}' with the statement: {cmdText}",
112+
connectionStringBuilder.Database,
113+
connectionStringBuilder.Host,
114+
cmdText);
115+
116+
using (var connection = new NpgsqlConnection(
117+
new NpgsqlConnectionStringBuilder(_configuration.ConnectionString)
118+
{
119+
Database = null
120+
}.ConnectionString))
121+
{
122+
await connection.OpenAsync(cancellationToken).NotOnCapturedContext();
123+
124+
async Task<bool> DatabaseExists()
125+
{
126+
using (var command = new NpgsqlCommand(
127+
$"SELECT 1 FROM pg_database WHERE datname = '{connectionStringBuilder.Database}'",
128+
connection))
129+
{
130+
return await command.ExecuteScalarAsync(cancellationToken).NotOnCapturedContext()
131+
!= null;
132+
}
133+
}
134+
135+
if (!await DatabaseExists())
136+
{
137+
using (var command = new NpgsqlCommand(
138+
cmdText,
139+
connection))
140+
{
141+
await command.ExecuteNonQueryAsync(cancellationToken).NotOnCapturedContext();
142+
}
143+
}
144+
}
145+
}
146+
}
147+
}

src/SqlStreamStore.Server/Program.cs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using System.Threading;
34
using System.Threading.Tasks;
45
using Microsoft.AspNetCore.Hosting;
@@ -44,25 +45,19 @@ private async Task<int> Run()
4445
{
4546
try
4647
{
47-
using (var streamStore = await _factory.Create(_cts.Token))
48-
using (var host = new WebHostBuilder()
49-
.SuppressStatusMessages(true)
50-
.UseKestrel()
51-
.UseStartup(new SqlStreamStoreServerStartup(
52-
streamStore,
53-
new SqlStreamStoreMiddlewareOptions
54-
{
55-
UseCanonicalUrls = _configuration.UseCanonicalUris,
56-
ServerAssembly = typeof(Program).Assembly
57-
}))
58-
.UseSerilog()
59-
.Build())
48+
switch (_configuration.Args.FirstOrDefault())
6049
{
61-
await Task.WhenAll(
62-
host.RunAsync(_cts.Token),
63-
host.WaitForShutdownAsync(_cts.Token));
64-
65-
return 0;
50+
case "initialize":
51+
case "init":
52+
await RunInitialization();
53+
return 0;
54+
case "initialize-database":
55+
case "init-database":
56+
await RunDatabaseInitialization();
57+
return 0;
58+
default:
59+
await RunServer();
60+
return 0;
6661
}
6762
}
6863
catch (Exception ex)
@@ -76,6 +71,34 @@ await Task.WhenAll(
7671
}
7772
}
7873

74+
private async Task RunServer()
75+
{
76+
using (var streamStore = _factory.Create())
77+
using (var host = new WebHostBuilder()
78+
.SuppressStatusMessages(true)
79+
.UseKestrel()
80+
.UseStartup(new SqlStreamStoreServerStartup(
81+
streamStore,
82+
new SqlStreamStoreMiddlewareOptions
83+
{
84+
UseCanonicalUrls = _configuration.UseCanonicalUris,
85+
ServerAssembly = typeof(Program).Assembly
86+
}))
87+
.UseSerilog()
88+
.Build())
89+
{
90+
await Task.WhenAll(
91+
host.RunAsync(_cts.Token),
92+
host.WaitForShutdownAsync(_cts.Token));
93+
}
94+
}
95+
96+
private Task RunInitialization()
97+
=> new SqlStreamStoreInitializer(_configuration).Initialize(_cts.Token);
98+
99+
private Task RunDatabaseInitialization()
100+
=> new DatabaseInitializer(_configuration).Initialize(_cts.Token);
101+
79102
public void Dispose()
80103
{
81104
_cts?.Dispose();

0 commit comments

Comments
 (0)