1- using System ;
1+ using System ;
22using System . Data ;
33using System . Security . Cryptography . X509Certificates ;
44using System . Text . RegularExpressions ;
1616/// </summary>
1717public static class PostgresqlExtensions
1818{
19- private static readonly string pattern = @"(?i)Search\s?Path=([^;]+)" ;
19+ private static readonly string pattern = @"(?i)Search\s?Path=([^;]+)" ;
2020 /// <summary>
2121 /// Creates an upgrader for PostgreSQL databases.
2222 /// </summary>
@@ -175,7 +175,7 @@ public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase s
175175 public static void PostgresqlDatabase ( this SupportedDatabasesForEnsureDatabase supported , string connectionString , IUpgradeLog logger , X509Certificate2 certificate )
176176 {
177177 var options = new PostgresqlConnectionOptions
178- {
178+ {
179179 ClientCertificate = certificate
180180 } ;
181181 PostgresqlDatabase ( supported , connectionString , logger , options ) ;
@@ -189,11 +189,30 @@ public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase s
189189 /// <param name="logger">The <see cref="DbUp.Engine.Output.IUpgradeLog"/> used to record actions.</param>
190190 /// <param name="connectionOptions">Connection options to set SSL parameters</param>
191191 public static void PostgresqlDatabase (
192- this SupportedDatabasesForEnsureDatabase supported ,
193- string connectionString ,
194- IUpgradeLog logger ,
192+ this SupportedDatabasesForEnsureDatabase supported ,
193+ string connectionString ,
194+ IUpgradeLog logger ,
195195 PostgresqlConnectionOptions connectionOptions
196196 )
197+ {
198+ PostgresqlDatabase ( supported , connectionString , logger , connectionOptions , null ) ;
199+ }
200+
201+ /// <summary>
202+ /// Ensures that the database specified in the connection string exists, assigning an owner at creation time.
203+ /// </summary>
204+ /// <param name="supported">Fluent helper type.</param>
205+ /// <param name="connectionString">The connection string.</param>
206+ /// <param name="logger">The <see cref="DbUp.Engine.Output.IUpgradeLog"/> used to record actions.</param>
207+ /// <param name="connectionOptions">Connection options to set SSL parameters</param>
208+ /// <param name="owner">Role to own the new database during creation (adds 'WITH OWNER = "role"').</param>
209+ public static void PostgresqlDatabase (
210+ this SupportedDatabasesForEnsureDatabase supported ,
211+ string connectionString ,
212+ IUpgradeLog logger ,
213+ PostgresqlConnectionOptions connectionOptions ,
214+ string owner
215+ )
197216 {
198217 if ( supported == null ) throw new ArgumentNullException ( "supported" ) ;
199218
@@ -205,7 +224,7 @@ PostgresqlConnectionOptions connectionOptions
205224 if ( logger == null ) throw new ArgumentNullException ( "logger" ) ;
206225
207226 var masterConnectionStringBuilder = new NpgsqlConnectionStringBuilder ( connectionString ) ;
208-
227+
209228 var databaseName = masterConnectionStringBuilder . Database ;
210229
211230 if ( string . IsNullOrEmpty ( databaseName ) || databaseName . Trim ( ) == string . Empty )
@@ -232,9 +251,9 @@ PostgresqlConnectionOptions connectionOptions
232251
233252 // check to see if the database already exists..
234253 using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
235- {
236- CommandType = CommandType . Text
237- } )
254+ {
255+ CommandType = CommandType . Text
256+ } )
238257 {
239258 var results = Convert . ToInt32 ( command . ExecuteScalar ( ) ) ;
240259
@@ -245,18 +264,56 @@ PostgresqlConnectionOptions connectionOptions
245264 }
246265 }
247266
248- sqlCommandText = $ "create database \" { databaseName } \" ;";
249-
250- // Create the database...
251- using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
252- {
253- CommandType = CommandType . Text
254- } )
267+ if ( string . IsNullOrEmpty ( owner ) )
255268 {
256- command . ExecuteNonQuery ( ) ;
269+ sqlCommandText = $ "create database \" { databaseName } \" ;";
270+
271+ // Create the database...
272+ using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
273+ {
274+ CommandType = CommandType . Text
275+ } )
276+ {
277+ command . ExecuteNonQuery ( ) ;
278+ }
279+
280+ logger . LogInformation ( @"Created database {0}" , databaseName ) ;
257281 }
282+ else
283+ {
284+ sqlCommandText = "select exists (select 1 from pg_roles where rolname = @owner);" ;
285+ // check to see if the owner exists..
286+ using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
287+ {
288+ CommandType = CommandType . Text
289+ } )
290+ {
291+ command . Parameters . AddWithValue ( "@owner" , owner ) ;
292+
293+ var roleExists = ( bool ) command . ExecuteScalar ( ) ;
294+ // if the owner role does not exist, we throw an exception.
295+ if ( ! roleExists )
296+ {
297+ throw new InvalidOperationException ( $ "PostgreSQL role '{ owner } ' does not exist.") ;
298+ }
299+ }
258300
259- logger . LogInformation ( @"Created database {0}" , databaseName ) ;
301+ using var formattedSql = new NpgsqlCommand ( "select format('create database %I with owner = %I', @databaseName, @owner);" , connection ) ;
302+ formattedSql . Parameters . AddWithValue ( "databaseName" , databaseName ) ;
303+ formattedSql . Parameters . AddWithValue ( "owner" , owner ) ;
304+ sqlCommandText = ( string ) formattedSql . ExecuteScalar ( ) ;
305+
306+ // Create the database..
307+ using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
308+ {
309+ CommandType = CommandType . Text ,
310+ } )
311+ {
312+ command . ExecuteNonQuery ( ) ;
313+ }
314+
315+ logger . LogInformation ( @"Created database {0} with owner {1}" , databaseName , owner ) ;
316+ }
260317 }
261318
262319 /// <summary>
@@ -347,7 +404,7 @@ PostgresqlConnectionOptions connectionOptions
347404
348405 var masterConnectionStringBuilder = new NpgsqlConnectionStringBuilder ( connectionString ) ;
349406
350- var databaseName = masterConnectionStringBuilder . Database ;
407+ var databaseName = masterConnectionStringBuilder . Database ;
351408
352409 if ( string . IsNullOrEmpty ( databaseName ) || databaseName . Trim ( ) == string . Empty )
353410 {
@@ -379,9 +436,9 @@ PostgresqlConnectionOptions connectionOptions
379436
380437 // check to see if the database already exists..
381438 using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
382- {
383- CommandType = CommandType . Text
384- } )
439+ {
440+ CommandType = CommandType . Text
441+ } )
385442 {
386443 var results = Convert . ToInt32 ( command . ExecuteScalar ( ) ) ;
387444
@@ -396,9 +453,9 @@ PostgresqlConnectionOptions connectionOptions
396453 // prevent new connections to the database
397454 sqlCommandText = $ "alter database \" { databaseName } \" with ALLOW_CONNECTIONS false;";
398455 using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
399- {
400- CommandType = CommandType . Text
401- } )
456+ {
457+ CommandType = CommandType . Text
458+ } )
402459 {
403460 command . ExecuteNonQuery ( ) ;
404461 }
@@ -408,9 +465,9 @@ PostgresqlConnectionOptions connectionOptions
408465 // terminate all existing connections to the database
409466 sqlCommandText = $ "select pg_terminate_backend(pg_stat_activity.pid) from pg_stat_activity where pg_stat_activity.datname = \' { databaseName } \' ;";
410467 using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
411- {
412- CommandType = CommandType . Text
413- } )
468+ {
469+ CommandType = CommandType . Text
470+ } )
414471 {
415472 command . ExecuteNonQuery ( ) ;
416473 }
@@ -421,9 +478,9 @@ PostgresqlConnectionOptions connectionOptions
421478
422479 // drop the database
423480 using ( var command = new NpgsqlCommand ( sqlCommandText , connection )
424- {
425- CommandType = CommandType . Text
426- } )
481+ {
482+ CommandType = CommandType . Text
483+ } )
427484 {
428485 command . ExecuteNonQuery ( ) ;
429486 }
0 commit comments