diff --git a/src/MiniProfiler.Providers.MySql/MySqlStorage.cs b/src/MiniProfiler.Providers.MySql/MySqlStorage.cs index 5547d77fb..0070d1241 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. @@ -374,6 +375,17 @@ SELECT Id /// protected override DbConnection GetConnection() => new MySqlConnection(ConnectionString); + /// + /// SQL statements to create the SQL Server CE 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..29c7e5265 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. @@ -390,6 +391,17 @@ Select Id /// protected override DbConnection GetConnection() => new NpgsqlConnection(ConnectionString); + /// + /// SQL statements to create the SQL Server CE 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 SQL Server tables. /// @@ -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 486de2af5..519739230 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 @@ -28,29 +29,28 @@ public SqlServerStorage(string connectionString) : base(connectionString) { /* b /// The table name to use for MiniProfilers. /// The table name to use for MiniProfiler Timings. /// The table name to use for MiniProfiler Client Timings. - /// The database schema to use for MiniProfiler tables. - public SqlServerStorage(string connectionString, string profilersTable = null, string timingsTable = null, string clientTimingsTable = null, string schemaName = null) - : base(connectionString, profilersTable, timingsTable, clientTimingsTable, schemaName) { } + public SqlServerStorage(string connectionString, string profilersTable, string timingsTable, string clientTimingsTable) + : base(connectionString, profilersTable, timingsTable, clientTimingsTable) { } private string _saveSql, _saveTimingsSql, _saveClientTimingsSql; private string SaveSql => _saveSql ??= $@" -INSERT INTO {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} +INSERT INTO {MiniProfilersTable} (Id, RootTimingId, Name, Started, DurationMilliseconds, [User], HasUserViewed, MachineName, CustomLinksJson, ClientTimingsRedirectCount) SELECT @Id, @RootTimingId, @Name, @Started, @DurationMilliseconds, @User, @HasUserViewed, @MachineName, @CustomLinksJson, @ClientTimingsRedirectCount -WHERE NOT EXISTS (SELECT 1 FROM {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} WHERE Id = @Id)"; +WHERE NOT EXISTS (SELECT 1 FROM {MiniProfilersTable} WHERE Id = @Id)"; private string SaveTimingsSql => _saveTimingsSql ??= $@" -INSERT INTO {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerTimingsTable} +INSERT INTO {MiniProfilerTimingsTable} (Id, MiniProfilerId, ParentTimingId, Name, DurationMilliseconds, StartMilliseconds, IsRoot, Depth, CustomTimingsJson) SELECT @Id, @MiniProfilerId, @ParentTimingId, @Name, @DurationMilliseconds, @StartMilliseconds, @IsRoot, @Depth, @CustomTimingsJson -WHERE NOT EXISTS (SELECT 1 FROM {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerTimingsTable} WHERE Id = @Id)"; +WHERE NOT EXISTS (SELECT 1 FROM {MiniProfilerTimingsTable} WHERE Id = @Id)"; private string SaveClientTimingsSql => _saveClientTimingsSql ??= $@" -INSERT INTO {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerClientTimingsTable} +INSERT INTO {MiniProfilerClientTimingsTable} (Id, MiniProfilerId, Name, Start, Duration) SELECT @Id, @MiniProfilerId, @Name, @Start, @Duration -WHERE NOT EXISTS (SELECT 1 FROM {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerClientTimingsTable} WHERE Id = @Id)"; +WHERE NOT EXISTS (SELECT 1 FROM {MiniProfilerClientTimingsTable} WHERE Id = @Id)"; /// /// Stores to dbo.MiniProfilers under its ; @@ -182,9 +182,9 @@ public override async Task SaveAsync(MiniProfiler profiler) private string _loadSql; private string LoadSql => _loadSql ??= $@" -SELECT * FROM {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} WHERE Id = @id; -SELECT * FROM {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerTimingsTable} WHERE MiniProfilerId = @id ORDER BY StartMilliseconds; -SELECT * FROM {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerClientTimingsTable} WHERE MiniProfilerId = @id ORDER BY Start;"; +SELECT * FROM {MiniProfilersTable} WHERE Id = @id; +SELECT * FROM {MiniProfilerTimingsTable} WHERE MiniProfilerId = @id ORDER BY StartMilliseconds; +SELECT * FROM {MiniProfilerClientTimingsTable} WHERE MiniProfilerId = @id ORDER BY Start;"; /// /// Loads the MiniProfiler identified by 'id' from the database. @@ -273,7 +273,7 @@ public override async Task LoadAsync(Guid id) private string _toggleViewedSql; private string ToggleViewedSql => _toggleViewedSql ??= $@" -Update {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} +Update {MiniProfilersTable} Set HasUserViewed = @hasUserVeiwed Where Id = @id And [User] = @user"; @@ -298,7 +298,7 @@ private async Task ToggleViewedAsync(string user, Guid id, bool hasUserVeiwed) private string GetUnviewedIdsSql => _getUnviewedIdsSql ??= $@" Select Id - From {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} + From {MiniProfilersTable} Where [User] = @user And HasUserViewed = 0 Order By Started"; @@ -368,7 +368,7 @@ private string BuildListQuery(DateTime? start = null, DateTime? finish = null, L var sb = StringBuilderCache.Get(); sb.Append(@" Select Top {=maxResults} Id - From ").Append(SchemaName == null ? string.Empty : $"{SchemaName}.").Append(MiniProfilersTable).Append(@" + From ").Append(MiniProfilersTable).Append(@" "); if (finish != null) { @@ -390,20 +390,28 @@ private string BuildListQuery(DateTime? start = null, DateTime? finish = null, L /// 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 $@" --- creating schema name if not exists -IF NOT EXISTS ( SELECT * FROM sys.schemas WHERE name = N'{SchemaName}') EXEC('CREATE SCHEMA [{SchemaName}]'); - -IF OBJECT_ID(N'{(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable}', N'U') IS NULL +IF OBJECT_ID(N'{MiniProfilersTable}', N'U') IS NULL BEGIN - CREATE TABLE {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} + CREATE TABLE {MiniProfilersTable} ( - RowId integer not null identity constraint PK_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilersTable} primary key clustered, -- Need a clustered primary key for SQL Azure + 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, @@ -416,17 +424,17 @@ CustomLinksJson nvarchar(max), ClientTimingsRedirectCount int null ); -- displaying results selects everything based on the main MiniProfilers.Id column - CREATE UNIQUE NONCLUSTERED INDEX IX_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilersTable}_Id ON {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} (Id); + 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_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilersTable}_User_HasUserViewed_Includes ON {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilersTable} ([User], HasUserViewed) INCLUDE (Id, [Started]); + CREATE NONCLUSTERED INDEX IX_{Regex.Replace(MiniProfilersTable, IndexNameWrongSymbolsReplacePattern, "_")}_User_HasUserViewed_Includes ON {MiniProfilersTable} ([User], HasUserViewed) INCLUDE (Id, [Started]); END; -IF OBJECT_ID(N'{(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerTimingsTable}', N'U') IS NULL +IF OBJECT_ID(N'{MiniProfilerTimingsTable}', N'U') IS NULL BEGIN - CREATE TABLE {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerTimingsTable} + CREATE TABLE {MiniProfilerTimingsTable} ( - RowId integer not null identity constraint PK_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilerTimingsTable} primary key clustered, + 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, @@ -438,15 +446,15 @@ StartMilliseconds decimal(15,3) not null, CustomTimingsJson nvarchar(max) null ); - CREATE UNIQUE NONCLUSTERED INDEX IX_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilerTimingsTable}_Id ON {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerTimingsTable} (Id); - CREATE NONCLUSTERED INDEX IX_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilerTimingsTable}_MiniProfilerId ON {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerTimingsTable} (MiniProfilerId); + 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'{(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerClientTimingsTable}', N'U') IS NULL +IF OBJECT_ID(N'{MiniProfilerClientTimingsTable}', N'U') IS NULL BEGIN - CREATE TABLE {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerClientTimingsTable} + CREATE TABLE {MiniProfilerClientTimingsTable} ( - RowId integer not null identity constraint PK_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilerClientTimingsTable} primary key clustered, + 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, @@ -454,8 +462,8 @@ Start decimal(9, 3) not null, Duration decimal(9, 3) not null ); - CREATE UNIQUE NONCLUSTERED INDEX IX_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilerClientTimingsTable}_Id on {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerClientTimingsTable} (Id); - CREATE NONCLUSTERED INDEX IX_{(SchemaName == null ? string.Empty : $"{SchemaName}_")}{MiniProfilerClientTimingsTable}_MiniProfilerId on {(SchemaName == null ? string.Empty : $"{SchemaName}.")}{MiniProfilerClientTimingsTable} (MiniProfilerId); + 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..36283b617 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. @@ -383,6 +384,17 @@ private string BuildListQuery(DateTime? start = null, DateTime? finish = null, L /// 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..86f823645 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. @@ -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,14 @@ public void CreateSchema(string connectionString, params string[] additionalSqlS } } + /// + /// SQL statements to create the SQL Server schema names. + /// + 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 847fe3a0d..0b6ae3a55 100644 --- a/src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs +++ b/src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs @@ -27,9 +27,9 @@ public abstract class DatabaseStorageBase : IAsyncStorage, IDatabaseStorageConne public readonly string MiniProfilerClientTimingsTable = "MiniProfilerClientTimings"; /// - /// The database schema to use for MiniProfiler tables. + /// Replace pattern fox index and constraint identifiers. /// - public readonly string SchemaName = null; + protected readonly string IndexNameWrongSymbolsReplacePattern = @"([+*\-\@#!$~%^&().\[\]]){1,}"; /// /// Gets or sets how we connect to the database used to save/load MiniProfiler results. @@ -37,7 +37,7 @@ public abstract class DatabaseStorageBase : IAsyncStorage, IDatabaseStorageConne 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 @@ -47,21 +47,19 @@ 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 /// The table name to use for MiniProfilers. /// The table name to use for MiniProfiler Timings. /// The table name to use for MiniProfiler Client Timings. - /// The database schema to use for MiniProfiler tables. - protected DatabaseStorageBase(string connectionString, string profilersTable = null, string timingsTable = null, string clientTimingsTable = null, string schemaName = null) + protected DatabaseStorageBase(string connectionString, string profilersTable, string timingsTable, string clientTimingsTable) { ConnectionString = connectionString; MiniProfilersTable = profilersTable ?? MiniProfilersTable; MiniProfilerTimingsTable = timingsTable ?? MiniProfilerTimingsTable; MiniProfilerClientTimingsTable = clientTimingsTable ?? MiniProfilerClientTimingsTable; - SchemaName = schemaName; } /// @@ -101,14 +99,14 @@ protected DatabaseStorageBase(string connectionString, string profilersTable = n 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. @@ -230,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; /// @@ -238,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 42632d567..02b441f95 100644 --- a/tests/MiniProfiler.Tests/Storage/SqlServerStorageTests.cs +++ b/tests/MiniProfiler.Tests/Storage/SqlServerStorageTests.cs @@ -20,12 +20,12 @@ public SqlServerStorageFixture() Storage = new SqlServerStorage( TestConfig.Current.SQLServerConnectionString, - "MPTest" + TestId, - "MPTimingsTest" + TestId, - "MPClientTimingsTest" + TestId, - "MPSchemaNameTest" + TestId); + $"[{TestSchemaName}].[MPTest{TestId}]", + $"[{TestSchemaName}].[MPTimingsTest{TestId}]", + $"[{TestSchemaName}].[MPClientTimingsTest{TestId}]"); try { + Storage.CreateSchemaName(new[] { TestSchemaName }); Storage.CreateSchema(); } catch (Exception e) @@ -40,7 +40,8 @@ public void Dispose() { if (!ShouldSkip) { - Storage?.DropSchema(true); + 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 583b7d667..3b523f4e8 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. /// @@ -225,16 +248,36 @@ public static void CreateSchema(this IAsyncStorage storage) /// Drops the tables for this storage provider. /// /// The storage to drop schema for. - /// Drop tables with schema name. - public static void DropSchema(this IAsyncStorage storage, bool withSchema = false) + 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. + /// + /// The storage to drop schema for. + public static void DropSchema(this IAsyncStorage storage) { if (storage is DatabaseStorageBase dbs && storage is IDatabaseStorageConnectable dbsc) { using (var conn = dbsc.GetConnection()) { - conn.Execute($"Drop Table {(withSchema ? $"{dbs.SchemaName}." : string.Empty)}{dbs.MiniProfilerClientTimingsTable}"); - conn.Execute($"Drop Table {(withSchema ? $"{dbs.SchemaName}." : string.Empty)}{dbs.MiniProfilerTimingsTable}"); - conn.Execute($"Drop Table {(withSchema ? $"{dbs.SchemaName}." : string.Empty)}{dbs.MiniProfilersTable}"); + conn.Execute("Drop Table " + dbs.MiniProfilerClientTimingsTable); + conn.Execute("Drop Table " + dbs.MiniProfilerTimingsTable); + conn.Execute("Drop Table " + dbs.MiniProfilersTable); } } } @@ -242,6 +285,7 @@ public static void DropSchema(this IAsyncStorage storage, bool withSchema = fals 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; }