diff --git a/src/MiniProfiler.Providers.MySql/MySqlStorage.cs b/src/MiniProfiler.Providers.MySql/MySqlStorage.cs index 5547d77fb..f1867768b 100644 --- a/src/MiniProfiler.Providers.MySql/MySqlStorage.cs +++ b/src/MiniProfiler.Providers.MySql/MySqlStorage.cs @@ -3,6 +3,7 @@ using System.Data.Common; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Dapper; using MySqlConnector; @@ -227,14 +228,14 @@ public override async Task LoadAsync(Guid id) } /// - /// Sets a particular profiler session so it is considered "unviewed" + /// Sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. public override void SetUnviewed(string user, Guid id) => ToggleViewed(user, id, false); /// - /// Asynchronously sets a particular profiler session so it is considered "unviewed" + /// Asynchronously sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. @@ -370,10 +371,21 @@ SELECT Id } /// - /// Returns a connection to MySQL Server. + /// Returns a connection to MySQL. /// protected override DbConnection GetConnection() => new MySqlConnection(ConnectionString); + /// + /// SQL statements to create the MySQL schema names. + /// + protected override IEnumerable GetSchemaNameCreationScripts(IEnumerable schemaNames) + { + foreach (var schemaName in schemaNames) + { + yield return $@"CREATE SCHEMA IF NOT EXISTS [{schemaName}]"; + } + } + /// /// SQL statements to create the MySQL tables. /// @@ -393,8 +405,8 @@ User varchar(100) null, MachineName varchar(100) null, CustomLinksJson longtext, ClientTimingsRedirectCount int null, - UNIQUE INDEX IX_{MiniProfilersTable}_Id (Id), -- displaying results selects everything based on the main MiniProfilers.Id column - INDEX IX_{MiniProfilersTable}_User_HasUserViewed (User, HasUserViewed) -- speeds up a query that is called on every .Stop() + UNIQUE INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id (Id), -- displaying results selects everything based on the main MiniProfilers.Id column + INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_User_HasUserViewed (User, HasUserViewed) -- speeds up a query that is called on every .Stop() ) engine=InnoDB collate utf8mb4_bin;"; yield return $@" CREATE TABLE {MiniProfilerTimingsTable} @@ -409,8 +421,8 @@ StartMilliseconds decimal(15,3) not null, IsRoot bool not null, Depth smallint not null, CustomTimingsJson longtext null, - UNIQUE INDEX IX_{MiniProfilerTimingsTable}_Id (Id), - INDEX IX_{MiniProfilerTimingsTable}_MiniProfilerId (MiniProfilerId) + UNIQUE INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id (Id), + INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId (MiniProfilerId) ) engine=InnoDB collate utf8mb4_bin;"; yield return $@" CREATE TABLE {MiniProfilerClientTimingsTable} @@ -421,8 +433,8 @@ MiniProfilerId char(36) not null collate ascii_general_ci, Name varchar(200) not null, Start decimal(9, 3) not null, Duration decimal(9, 3) not null, - UNIQUE INDEX IX_{MiniProfilerClientTimingsTable}_Id (Id), - INDEX IX_{MiniProfilerClientTimingsTable}_MiniProfilerId (MiniProfilerId) + UNIQUE INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id (Id), + INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId (MiniProfilerId) ) engine=InnoDB collate utf8mb4_bin;"; } diff --git a/src/MiniProfiler.Providers.PostgreSql/PostgreSqlStorage.cs b/src/MiniProfiler.Providers.PostgreSql/PostgreSqlStorage.cs index da5a6762f..c261b24e5 100644 --- a/src/MiniProfiler.Providers.PostgreSql/PostgreSqlStorage.cs +++ b/src/MiniProfiler.Providers.PostgreSql/PostgreSqlStorage.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace StackExchange.Profiling.Storage @@ -242,14 +243,14 @@ public override async Task LoadAsync(Guid id) } /// - /// Sets a particular profiler session so it is considered "unviewed" + /// Sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. public override void SetUnviewed(string user, Guid id) => ToggleViewed(user, id, false); /// - /// Asynchronously sets a particular profiler session so it is considered "unviewed" + /// Asynchronously sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. @@ -386,12 +387,23 @@ Select Id } /// - /// Returns a connection to Sql Server. + /// Returns a connection to PostgreSQL. /// protected override DbConnection GetConnection() => new NpgsqlConnection(ConnectionString); /// - /// SQL statements to create the SQL Server tables. + /// SQL statements to create the PostgreSQL schema names. + /// + protected override IEnumerable GetSchemaNameCreationScripts(IEnumerable schemaNames) + { + foreach (var schemaName in schemaNames) + { + yield return $@"CREATE SCHEMA IF NOT EXISTS [{schemaName}]"; + } + } + + /// + /// SQL statements to create the PostgreSQL tables. /// protected override IEnumerable GetTableCreationScripts() { @@ -411,10 +423,10 @@ MachineName varchar(100) null, ClientTimingsRedirectCount integer null ); -- displaying results selects everything based on the main MiniProfilers.Id column -CREATE UNIQUE INDEX IX_{MiniProfilersTable}_Id ON {MiniProfilersTable} (Id); - +CREATE UNIQUE INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id ON {MiniProfilersTable} (Id); + -- speeds up a query that is called on every .Stop() -CREATE INDEX IX_{MiniProfilersTable}_User_HasUserViewed_Includes ON {MiniProfilersTable} (""User"", HasUserViewed); +CREATE INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_User_HasUserViewed_Includes ON {MiniProfilersTable} (""User"", HasUserViewed); CREATE TABLE {MiniProfilerTimingsTable} ( @@ -430,8 +442,8 @@ StartMilliseconds decimal(15,3) not null, CustomTimingsJson varchar null ); -CREATE UNIQUE INDEX IX_{MiniProfilerTimingsTable}_Id ON {MiniProfilerTimingsTable} (Id); -CREATE INDEX IX_{MiniProfilerTimingsTable}_MiniProfilerId ON {MiniProfilerTimingsTable} (MiniProfilerId); +CREATE UNIQUE INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id ON {MiniProfilerTimingsTable} (Id); +CREATE INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId ON {MiniProfilerTimingsTable} (MiniProfilerId); CREATE TABLE {MiniProfilerClientTimingsTable} ( @@ -443,8 +455,8 @@ Start decimal(9, 3) not null, Duration decimal(9, 3) not null ); -CREATE UNIQUE INDEX IX_{MiniProfilerClientTimingsTable}_Id on {MiniProfilerClientTimingsTable} (Id); -CREATE INDEX IX_{MiniProfilerClientTimingsTable}_MiniProfilerId on {MiniProfilerClientTimingsTable} (MiniProfilerId); +CREATE UNIQUE INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id on {MiniProfilerClientTimingsTable} (Id); +CREATE INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId on {MiniProfilerClientTimingsTable} (MiniProfilerId); "; } } diff --git a/src/MiniProfiler.Providers.SqlServer/SqlServerStorage.cs b/src/MiniProfiler.Providers.SqlServer/SqlServerStorage.cs index 89de67fb6..a62210348 100644 --- a/src/MiniProfiler.Providers.SqlServer/SqlServerStorage.cs +++ b/src/MiniProfiler.Providers.SqlServer/SqlServerStorage.cs @@ -5,6 +5,7 @@ using System.Data.Common; using System.Data.SqlClient; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace StackExchange.Profiling.Storage @@ -242,14 +243,14 @@ public override async Task LoadAsync(Guid id) } /// - /// Sets a particular profiler session so it is considered "unviewed" + /// Sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. public override void SetUnviewed(string user, Guid id) => ToggleViewed(user, id, false); /// - /// Asynchronously sets a particular profiler session so it is considered "unviewed" + /// Asynchronously sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. @@ -272,9 +273,9 @@ public override async Task LoadAsync(Guid id) private string _toggleViewedSql; private string ToggleViewedSql => _toggleViewedSql ??= $@" -Update {MiniProfilersTable} - Set HasUserViewed = @hasUserVeiwed - Where Id = @id +Update {MiniProfilersTable} + Set HasUserViewed = @hasUserVeiwed + Where Id = @id And [User] = @user"; private void ToggleViewed(string user, Guid id, bool hasUserVeiwed) @@ -385,65 +386,85 @@ private string BuildListQuery(DateTime? start = null, DateTime? finish = null, L } /// - /// Returns a connection to Sql Server. + /// Returns a connection to SQL Server. /// protected override DbConnection GetConnection() => new SqlConnection(ConnectionString); + /// + /// SQL statements to create the SQL Server schema names. + /// + protected override IEnumerable GetSchemaNameCreationScripts(IEnumerable schemaNames) + { + foreach (var schemaName in schemaNames) + { + yield return $@"IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = N'{schemaName}') EXEC('CREATE SCHEMA [{schemaName}]');"; + } + } + /// /// SQL statements to create the SQL Server tables. /// protected override IEnumerable GetTableCreationScripts() { yield return $@" -CREATE TABLE {MiniProfilersTable} -( - RowId integer not null identity constraint PK_{MiniProfilersTable} primary key clustered, -- Need a clustered primary key for SQL Azure - Id uniqueidentifier not null, -- don't cluster on a guid - RootTimingId uniqueidentifier null, - Name nvarchar(200) null, - Started datetime not null, - DurationMilliseconds decimal(15,1) not null, - [User] nvarchar(100) null, - HasUserViewed bit not null, - MachineName nvarchar(100) null, - CustomLinksJson nvarchar(max), - ClientTimingsRedirectCount int null -); --- displaying results selects everything based on the main MiniProfilers.Id column -CREATE UNIQUE NONCLUSTERED INDEX IX_{MiniProfilersTable}_Id ON {MiniProfilersTable} (Id); - --- speeds up a query that is called on every .Stop() -CREATE NONCLUSTERED INDEX IX_{MiniProfilersTable}_User_HasUserViewed_Includes ON {MiniProfilersTable} ([User], HasUserViewed) INCLUDE (Id, [Started]); - -CREATE TABLE {MiniProfilerTimingsTable} -( - RowId integer not null identity constraint PK_{MiniProfilerTimingsTable} primary key clustered, - Id uniqueidentifier not null, - MiniProfilerId uniqueidentifier not null, - ParentTimingId uniqueidentifier null, - Name nvarchar(200) not null, - DurationMilliseconds decimal(15,3) not null, - StartMilliseconds decimal(15,3) not null, - IsRoot bit not null, - Depth smallint not null, - CustomTimingsJson nvarchar(max) null -); - -CREATE UNIQUE NONCLUSTERED INDEX IX_{MiniProfilerTimingsTable}_Id ON {MiniProfilerTimingsTable} (Id); -CREATE NONCLUSTERED INDEX IX_{MiniProfilerTimingsTable}_MiniProfilerId ON {MiniProfilerTimingsTable} (MiniProfilerId); - -CREATE TABLE {MiniProfilerClientTimingsTable} -( - RowId integer not null identity constraint PK_{MiniProfilerClientTimingsTable} primary key clustered, - Id uniqueidentifier not null, - MiniProfilerId uniqueidentifier not null, - Name nvarchar(200) not null, - Start decimal(9, 3) not null, - Duration decimal(9, 3) not null -); - -CREATE UNIQUE NONCLUSTERED INDEX IX_{MiniProfilerClientTimingsTable}_Id on {MiniProfilerClientTimingsTable} (Id); -CREATE NONCLUSTERED INDEX IX_{MiniProfilerClientTimingsTable}_MiniProfilerId on {MiniProfilerClientTimingsTable} (MiniProfilerId); +IF OBJECT_ID(N'{MiniProfilersTable}', N'U') IS NULL +BEGIN + CREATE TABLE {MiniProfilersTable} + ( + RowId integer not null identity constraint PK_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")} primary key clustered, -- Need a clustered primary key for SQL Azure + Id uniqueidentifier not null, -- don't cluster on a guid + RootTimingId uniqueidentifier null, + Name nvarchar(200) null, + Started datetime not null, + DurationMilliseconds decimal(15,1) not null, + [User] nvarchar(100) null, + HasUserViewed bit not null, + MachineName nvarchar(100) null, + CustomLinksJson nvarchar(max), + ClientTimingsRedirectCount int null + ); + -- displaying results selects everything based on the main MiniProfilers.Id column + CREATE UNIQUE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id ON {MiniProfilersTable} (Id); + + -- speeds up a query that is called on every .Stop() + CREATE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_User_HasUserViewed_Includes ON {MiniProfilersTable} ([User], HasUserViewed) INCLUDE (Id, [Started]); +END; + +IF OBJECT_ID(N'{MiniProfilerTimingsTable}', N'U') IS NULL +BEGIN + CREATE TABLE {MiniProfilerTimingsTable} + ( + RowId integer not null identity constraint PK_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")} primary key clustered, + Id uniqueidentifier not null, + MiniProfilerId uniqueidentifier not null, + ParentTimingId uniqueidentifier null, + Name nvarchar(200) not null, + DurationMilliseconds decimal(15,3) not null, + StartMilliseconds decimal(15,3) not null, + IsRoot bit not null, + Depth smallint not null, + CustomTimingsJson nvarchar(max) null + ); + + CREATE UNIQUE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id ON {MiniProfilerTimingsTable} (Id); + CREATE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId ON {MiniProfilerTimingsTable} (MiniProfilerId); +END; + +IF OBJECT_ID(N'{MiniProfilerClientTimingsTable}', N'U') IS NULL +BEGIN + CREATE TABLE {MiniProfilerClientTimingsTable} + ( + RowId integer not null identity constraint PK_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")} primary key clustered, + Id uniqueidentifier not null, + MiniProfilerId uniqueidentifier not null, + Name nvarchar(200) not null, + Start decimal(9, 3) not null, + Duration decimal(9, 3) not null + ); + + CREATE UNIQUE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id on {MiniProfilerClientTimingsTable} (Id); + CREATE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId on {MiniProfilerClientTimingsTable} (MiniProfilerId); +END; "; } } diff --git a/src/MiniProfiler.Providers.SqlServerCe/SqlServerCeStorage.cs b/src/MiniProfiler.Providers.SqlServerCe/SqlServerCeStorage.cs index 29015d5c4..29e9d8d6d 100644 --- a/src/MiniProfiler.Providers.SqlServerCe/SqlServerCeStorage.cs +++ b/src/MiniProfiler.Providers.SqlServerCe/SqlServerCeStorage.cs @@ -4,6 +4,7 @@ using System.Data.SqlServerCe; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Dapper; @@ -237,14 +238,14 @@ private MiniProfiler SetUTC(MiniProfiler result) } /// - /// Sets a particular profiler session so it is considered "unviewed" + /// Sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. public override void SetUnviewed(string user, Guid id) => ToggleViewed(user, id, false); /// - /// Asynchronously sets a particular profiler session so it is considered "unviewed" + /// Asynchronously sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. @@ -379,10 +380,21 @@ private string BuildListQuery(DateTime? start = null, DateTime? finish = null, L } /// - /// Returns a connection to Sql Server. + /// Returns a connection to SQL Server CE. /// protected override DbConnection GetConnection() => new SqlCeConnection(ConnectionString); + /// + /// SQL statements to create the SQL Server CE schema names. + /// + protected override IEnumerable GetSchemaNameCreationScripts(IEnumerable schemaNames) + { + foreach (var schemaName in schemaNames) + { + yield return $@"IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = N'{schemaName}') EXEC('CREATE SCHEMA [{schemaName}]');"; + } + } + /// /// SQL statements to create the SQL Server CE tables. /// @@ -402,8 +414,8 @@ MachineName nvarchar(100) null, CustomLinksJson ntext, ClientTimingsRedirectCount int null );"; - yield return $"CREATE UNIQUE NONCLUSTERED INDEX IX_{MiniProfilersTable}_Id on {MiniProfilersTable} (Id);"; - yield return $"CREATE NONCLUSTERED INDEX IX_{MiniProfilersTable}_User_HasUserViewed on {MiniProfilersTable} ([User], HasUserViewed);"; + yield return $"CREATE UNIQUE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id on {MiniProfilersTable} (Id);"; + yield return $"CREATE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_User_HasUserViewed on {MiniProfilersTable} ([User], HasUserViewed);"; yield return $@"CREATE TABLE {MiniProfilerTimingsTable} ( RowId integer not null identity, @@ -417,8 +429,8 @@ StartMilliseconds decimal(15,3) not null, Depth smallint not null, CustomTimingsJson ntext null );"; - yield return $"CREATE UNIQUE NONCLUSTERED INDEX IX_{MiniProfilerTimingsTable}_Id on {MiniProfilerTimingsTable} (Id);"; - yield return $"CREATE NONCLUSTERED INDEX IX_{MiniProfilerTimingsTable}_MiniProfilerId on {MiniProfilerTimingsTable} (MiniProfilerId);"; + yield return $"CREATE UNIQUE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id on {MiniProfilerTimingsTable} (Id);"; + yield return $"CREATE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId on {MiniProfilerTimingsTable} (MiniProfilerId);"; yield return $@"CREATE TABLE {MiniProfilerClientTimingsTable} ( RowId integer not null identity, @@ -428,8 +440,8 @@ Name nvarchar(200) not null, Start decimal(9, 3) not null, Duration decimal(9, 3) not null );"; - yield return $"CREATE UNIQUE NONCLUSTERED INDEX IX_{MiniProfilerClientTimingsTable}_Id on {MiniProfilerClientTimingsTable} (Id);"; - yield return $"CREATE NONCLUSTERED INDEX IX_{MiniProfilerClientTimingsTable}_MiniProfilerId on {MiniProfilerClientTimingsTable} (MiniProfilerId);"; + yield return $"CREATE UNIQUE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_Id on {MiniProfilerClientTimingsTable} (Id);"; + yield return $"CREATE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilerClientTimingsTable, IndexNameWrongSymbolsReplacePattern, "_")}_MiniProfilerId on {MiniProfilerClientTimingsTable} (MiniProfilerId);"; } } } diff --git a/src/MiniProfiler.Providers.Sqlite/SqliteStorage.cs b/src/MiniProfiler.Providers.Sqlite/SqliteStorage.cs index 60e5bf2b3..7752d1a1c 100644 --- a/src/MiniProfiler.Providers.Sqlite/SqliteStorage.cs +++ b/src/MiniProfiler.Providers.Sqlite/SqliteStorage.cs @@ -295,14 +295,14 @@ public override async Task LoadAsync(Guid id) } /// - /// Sets a particular profiler session so it is considered "unviewed" + /// Sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. public override void SetUnviewed(string user, Guid id) => ToggleViewed(user, id, false); /// - /// Asynchronously sets a particular profiler session so it is considered "unviewed" + /// Asynchronously sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. @@ -439,7 +439,7 @@ Select Cast(Id as text) Id } /// - /// Returns a connection to Sql Server. + /// Returns a connection to SQLite. /// protected override DbConnection GetConnection() => new SqliteConnection(ConnectionString); @@ -462,7 +462,7 @@ public void CreateSchema(string connectionString, params string[] additionalSqlS { using (var cnn = new SqliteConnection(connectionString)) { - // We need some tiny mods to allow SQLite support + // We need some tiny mods to allow SQLite support foreach (var sql in TableCreationScripts.Union(additionalSqlStatements)) { cnn.Execute(sql); @@ -470,6 +470,17 @@ public void CreateSchema(string connectionString, params string[] additionalSqlS } } + /// + /// SQL statements to create the SQLite schema names. + /// + /// + /// Not implemented yet. + /// + protected override IEnumerable GetSchemaNameCreationScripts(IEnumerable _) + { + yield return string.Empty; + } + /// /// SQL statements to create the SQLite tables. /// diff --git a/src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs b/src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs index 2682d489d..0b6ae3a55 100644 --- a/src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs +++ b/src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs @@ -26,13 +26,18 @@ public abstract class DatabaseStorageBase : IAsyncStorage, IDatabaseStorageConne /// public readonly string MiniProfilerClientTimingsTable = "MiniProfilerClientTimings"; + /// + /// Replace pattern fox index and constraint identifiers. + /// + protected readonly string IndexNameWrongSymbolsReplacePattern = @"([+*\-\@#!$~%^&().\[\]]){1,}"; + /// /// Gets or sets how we connect to the database used to save/load MiniProfiler results. /// protected string ConnectionString { get; set; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// Returns a new SqlServerDatabaseStorage object that will insert into the database identified by connectionString. /// /// The connection String @@ -42,7 +47,7 @@ protected DatabaseStorageBase(string connectionString) } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// Returns a new SqlServerDatabaseStorage object that will insert into the database identified by connectionString. /// /// The connection String @@ -52,9 +57,9 @@ protected DatabaseStorageBase(string connectionString) protected DatabaseStorageBase(string connectionString, string profilersTable, string timingsTable, string clientTimingsTable) { ConnectionString = connectionString; - MiniProfilersTable = profilersTable; - MiniProfilerTimingsTable = timingsTable; - MiniProfilerClientTimingsTable = clientTimingsTable; + MiniProfilersTable = profilersTable ?? MiniProfilersTable; + MiniProfilerTimingsTable = timingsTable ?? MiniProfilerTimingsTable; + MiniProfilerClientTimingsTable = clientTimingsTable ?? MiniProfilerClientTimingsTable; } /// @@ -94,14 +99,14 @@ protected DatabaseStorageBase(string connectionString, string profilersTable, st public virtual bool SetUnviewedAfterSave => false; /// - /// Sets a particular profiler session so it is considered "unviewed" + /// Sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. public abstract void SetUnviewed(string user, Guid id); /// - /// Asynchronously sets a particular profiler session so it is considered "unviewed" + /// Asynchronously sets a particular profiler session so it is considered "unviewed" /// /// The user to set this profiler ID as unviewed for. /// The profiler ID to set unviewed. @@ -223,6 +228,14 @@ protected void FlattenTimings(Timing timing, List timingsCollection) } } + private List _schemaNameCreationScripts; + + /// + /// The schema name creation scripts for this database storage. + /// Generated by the implemented by the provider. + /// + public List SchemaNameCreationScripts(IEnumerable schemaNames) => _schemaNameCreationScripts ??= GetSchemaNameCreationScripts(schemaNames).ToList(); + private List _tableCreationScripts; /// @@ -231,6 +244,15 @@ protected void FlattenTimings(Timing timing, List timingsCollection) /// public List TableCreationScripts => _tableCreationScripts ??= GetTableCreationScripts().ToList(); + /// + /// Creates needed schema names. Run this once on your database before table creation scripts if needed. + /// + /// + /// Works in SQL server and sqlite (with documented removals). + /// + /// Schema names to create. + protected abstract IEnumerable GetSchemaNameCreationScripts(IEnumerable schemaNames); + /// /// Creates needed tables. Run this once on your database. /// diff --git a/tests/MiniProfiler.Tests/Storage/MySqlStorageTests.cs b/tests/MiniProfiler.Tests/Storage/MySqlStorageTests.cs index 063ca2642..8fb98135a 100644 --- a/tests/MiniProfiler.Tests/Storage/MySqlStorageTests.cs +++ b/tests/MiniProfiler.Tests/Storage/MySqlStorageTests.cs @@ -20,9 +20,9 @@ public MySqlStorageFixture() Storage = new MySqlStorage( TestConfig.Current.MySQLConnectionString, - "MPTest" + TestId, - "MPTimingsTest" + TestId, - "MPClientTimingsTest" + TestId); + $"[{TestSchemaName}].[MPTest{TestId}]", + $"[{TestSchemaName}].[MPTimingsTest{TestId}]", + $"[{TestSchemaName}].[MPClientTimingsTest{TestId}]"); try { Storage.CreateSchema(); @@ -39,7 +39,8 @@ public void Dispose() { if (!ShouldSkip) { - Storage.DropSchema(); + Storage?.DropSchema(); + Storage?.DropSchemaNames(new[] { TestSchemaName }); } } } diff --git a/tests/MiniProfiler.Tests/Storage/PostgreSqlStorageTests.cs b/tests/MiniProfiler.Tests/Storage/PostgreSqlStorageTests.cs index 3f3bc4bf9..b3701df40 100644 --- a/tests/MiniProfiler.Tests/Storage/PostgreSqlStorageTests.cs +++ b/tests/MiniProfiler.Tests/Storage/PostgreSqlStorageTests.cs @@ -21,9 +21,9 @@ public PostgreSqlStorageFixture() Storage = new PostgreSqlStorage( TestConfig.Current.PostgreSqlConnectionString, - "MPTest" + TestId, - "MPTimingsTest" + TestId, - "MPClientTimingsTest" + TestId); + $"[{TestSchemaName}].[MPTest{TestId}]", + $"[{TestSchemaName}].[MPTimingsTest{TestId}]", + $"[{TestSchemaName}].[MPClientTimingsTest{TestId}]"); try { Storage.CreateSchema(); @@ -40,7 +40,8 @@ public void Dispose() { if (!ShouldSkip) { - Storage.DropSchema(); + Storage?.DropSchema(); + Storage?.DropSchemaNames(new[] { TestSchemaName }); } } } diff --git a/tests/MiniProfiler.Tests/Storage/SqlServerCeStorageTests.cs b/tests/MiniProfiler.Tests/Storage/SqlServerCeStorageTests.cs index b1754de69..f66601217 100644 --- a/tests/MiniProfiler.Tests/Storage/SqlServerCeStorageTests.cs +++ b/tests/MiniProfiler.Tests/Storage/SqlServerCeStorageTests.cs @@ -27,9 +27,9 @@ public SqlServerCeStorageFixture() Storage = new SqlServerCeStorage( connString, - "MPTest" + TestId, - "MPTimingsTest" + TestId, - "MPClientTimingsTest" + TestId); + $"[{TestSchemaName}].[MPTest{TestId}]", + $"[{TestSchemaName}].[MPTimingsTest{TestId}]", + $"[{TestSchemaName}].[MPClientTimingsTest{TestId}]"); try { try @@ -55,6 +55,7 @@ public void Dispose() try { Storage?.DropSchema(); + Storage?.DropSchemaNames(new[] { TestSchemaName }); } catch { diff --git a/tests/MiniProfiler.Tests/Storage/SqlServerStorageTests.cs b/tests/MiniProfiler.Tests/Storage/SqlServerStorageTests.cs index 397398a10..02b441f95 100644 --- a/tests/MiniProfiler.Tests/Storage/SqlServerStorageTests.cs +++ b/tests/MiniProfiler.Tests/Storage/SqlServerStorageTests.cs @@ -20,11 +20,12 @@ public SqlServerStorageFixture() Storage = new SqlServerStorage( TestConfig.Current.SQLServerConnectionString, - "MPTest" + TestId, - "MPTimingsTest" + TestId, - "MPClientTimingsTest" + TestId); + $"[{TestSchemaName}].[MPTest{TestId}]", + $"[{TestSchemaName}].[MPTimingsTest{TestId}]", + $"[{TestSchemaName}].[MPClientTimingsTest{TestId}]"); try { + Storage.CreateSchemaName(new[] { TestSchemaName }); Storage.CreateSchema(); } catch (Exception e) @@ -39,7 +40,8 @@ public void Dispose() { if (!ShouldSkip) { - Storage.DropSchema(); + Storage?.DropSchema(); + Storage?.DropSchemaNames(new [] { TestSchemaName }); } } } diff --git a/tests/MiniProfiler.Tests/Storage/SqliteStorageTests.cs b/tests/MiniProfiler.Tests/Storage/SqliteStorageTests.cs index 5736dff36..133eb4791 100644 --- a/tests/MiniProfiler.Tests/Storage/SqliteStorageTests.cs +++ b/tests/MiniProfiler.Tests/Storage/SqliteStorageTests.cs @@ -42,7 +42,7 @@ public void Dispose() { if (!ShouldSkip) { - Storage.DropSchema(); + Storage?.DropSchema(); } if (File.Exists(fileName)) { diff --git a/tests/MiniProfiler.Tests/Storage/StorageBaseTest.cs b/tests/MiniProfiler.Tests/Storage/StorageBaseTest.cs index 350932304..70337e024 100644 --- a/tests/MiniProfiler.Tests/Storage/StorageBaseTest.cs +++ b/tests/MiniProfiler.Tests/Storage/StorageBaseTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -203,6 +204,28 @@ protected MiniProfiler GetMiniProfiler(string name = "Test", [CallerMemberName]s public static class DatabaseStorageExtensions { + /// + /// Creates the database schema names for this storage provider to use. + /// + /// The storage to create schema names for. + /// The database schema names to use for MiniProfiler tables. + public static void CreateSchemaName(this IAsyncStorage storage, IEnumerable schemaNames) + { + if (storage is DatabaseStorageBase dbs && storage is IDatabaseStorageConnectable dbsc) + { + using (var conn = dbsc.GetConnection()) + { + foreach (var script in dbs.SchemaNameCreationScripts(schemaNames)) + { + if (!string.IsNullOrWhiteSpace(script)) + { + conn.Execute(script); + } + } + } + } + } + /// /// Creates the tables for this storage provider to use. /// @@ -221,6 +244,28 @@ public static void CreateSchema(this IAsyncStorage storage) } } + /// + /// Drops the schema names for this storage provider. + /// + /// The database schema names to use for MiniProfiler tables. + /// The storage to drop schema names for. + public static void DropSchemaNames(this IAsyncStorage storage, IEnumerable schemaNames) + { + if (storage is DatabaseStorageBase dbs && storage is IDatabaseStorageConnectable dbsc) + { + using (var conn = dbsc.GetConnection()) + { + foreach (var schemaName in schemaNames) + { + if (!string.IsNullOrWhiteSpace(schemaName)) + { + conn.Execute($"Drop Schema IF EXISTS {schemaName}"); + } + } + } + } + } + /// /// Drops the tables for this storage provider. /// @@ -241,6 +286,7 @@ public static void DropSchema(this IAsyncStorage storage) public abstract class StorageFixtureBase { + public string TestSchemaName { get; } = "MPSchemaTest"; public string TestId { get; } = Guid.NewGuid().ToString("N").Substring(20); public bool ShouldSkip { get; protected set; } public string SkipReason { get; protected set; }