Skip to content

Commit 7fe4fba

Browse files
aashikgowdaAashik Nagadikeri HarishdroyadCopilot
authored
Added a function to allow creation of database with owner (#37)
* Added function to create postgres database with owner * Remonved using statement in NoPublicApiChanges.Run.approved.cs * Fixed test * Apply suggestion from @droyad * Update src/dbup-postgresql/PostgresqlExtensions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Addressed PR comments around SQLi and using if/else for creating a database with or without owner * Flipped boolean logic around * formatting --------- Co-authored-by: Aashik Nagadikeri Harish <aashik@StriVR-HQ.local> Co-authored-by: Robert Wagner <robert@wagner.id.au> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 2ebbc1a commit 7fe4fba

2 files changed

Lines changed: 90 additions & 32 deletions

File tree

src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabas
2525
public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { }
2626
public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { }
2727
public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { }
28+
public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions, string owner) { }
2829
}
2930
namespace DbUp.Postgresql
3031
{

src/dbup-postgresql/PostgresqlExtensions.cs

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Data;
33
using System.Security.Cryptography.X509Certificates;
44
using System.Text.RegularExpressions;
@@ -16,7 +16,7 @@
1616
/// </summary>
1717
public 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

Comments
 (0)