From 758f901ee94f164719a70ccd8371b2183e47f7a4 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 2 Oct 2024 14:54:04 +0200 Subject: [PATCH] Add an extension method ExecuteScript() to execute a SQL script. --- src/Directory.Build.props | 24 ++++-- .../SqlServerDatabaseExtensions.cs | 34 +++++++++ .../SqlServerScriptBlock.cs | 21 ++++++ .../SqlServerScriptParser.cs | 73 +++++++++++++++++++ .../SqlServerDatabaseExtensionsTest.cs | 66 +++++++++++++++++ 5 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 src/Testing.Databases.SqlServer/SqlServerScriptBlock.cs create mode 100644 src/Testing.Databases.SqlServer/SqlServerScriptParser.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 688bf32..0d8078c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,11 +11,25 @@ README.md MIT - 1.0.1 - - Fix the documentation - - 1.0.0 - - Initial version + 2.1.0 + - PosInformatique.Testing.Databases.SqlServer target the .NET Standard 2.0 platform. + - PosInformatique.Testing.Databases.SqlServer.Dac target the .NET Core 6.0 and .NET Framework 4.6.2 + - PosInformatique.Testing.Databases.SqlServer.EntityFramework target the .NET Core 6.0 + - Reduce the dependencies to Entity Framework 6.0 + - Reduce the dependencies of DACfx to a more earlier version. + - Add new method SqlServerDatabase.ExecuteScript() to execute T-SQL script. + + 2.0.0 + - Add SqlServerDatabaseComparer class to perform comparison between two databases. + - Add new PosInformatique.Testing.Databases.SqlServer.Dac NuGet package which contains DAC package tools. + - Add new SqlServer.CreateDatabaseAsync() extension method to create a database from a DbContext. + - Reduce dependencies version of the Entity Framework Core and SQL Server Client NuGet packages. + + 1.0.1 + - Fix the documentation + + 1.0.0 + - Initial version diff --git a/src/Testing.Databases.SqlServer/SqlServerDatabaseExtensions.cs b/src/Testing.Databases.SqlServer/SqlServerDatabaseExtensions.cs index dd978f6..2026d2a 100644 --- a/src/Testing.Databases.SqlServer/SqlServerDatabaseExtensions.cs +++ b/src/Testing.Databases.SqlServer/SqlServerDatabaseExtensions.cs @@ -130,6 +130,40 @@ [sys].[identity_columns] AS [ic] database.ExecuteNonQuery("EXEC sp_msforeachtable 'ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all'"); } + /// + /// Execute an T-SQL script on the . + /// + /// where the will be executed. + /// T-SQL script to execute. + public static void ExecuteScript(this SqlServerDatabase database, string script) + { + using var stringReader = new StringReader(script); + + ExecuteScript(database, stringReader); + } + + /// + /// Execute an T-SQL script on the . + /// + /// where the will be executed. + /// which contains the T-SQL script to execute. + public static void ExecuteScript(this SqlServerDatabase database, StringReader script) + { + var parser = new SqlServerScriptParser(script); + + var block = parser.ReadNextBlock(); + + while (block is not null) + { + for (var i = 0; i < block.Count; i++) + { + database.ExecuteNonQuery(block.Code); + } + + block = parser.ReadNextBlock(); + } + } + private sealed class SqlInsertStatementBuilder { private readonly string tableName; diff --git a/src/Testing.Databases.SqlServer/SqlServerScriptBlock.cs b/src/Testing.Databases.SqlServer/SqlServerScriptBlock.cs new file mode 100644 index 0000000..b0aeec9 --- /dev/null +++ b/src/Testing.Databases.SqlServer/SqlServerScriptBlock.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique.Testing.Databases.SqlServer +{ + internal class SqlServerScriptBlock + { + public SqlServerScriptBlock(string code, int count) + { + this.Code = code; + this.Count = count; + } + + public string Code { get; } + + public int Count { get; } + } +} diff --git a/src/Testing.Databases.SqlServer/SqlServerScriptParser.cs b/src/Testing.Databases.SqlServer/SqlServerScriptParser.cs new file mode 100644 index 0000000..0ee91d0 --- /dev/null +++ b/src/Testing.Databases.SqlServer/SqlServerScriptParser.cs @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique.Testing.Databases.SqlServer +{ + using System.Globalization; + using System.Text; + + internal sealed class SqlServerScriptParser + { + private readonly TextReader script; + + private bool isEndOfScript; + + public SqlServerScriptParser(TextReader script) + { + this.script = script; + } + + public SqlServerScriptBlock? ReadNextBlock() + { + if (this.isEndOfScript) + { + return null; + } + + var codeBuilder = new StringBuilder(); + + var count = 1; + + while (true) + { + var line = this.script.ReadLine(); + + if (line is null) + { + // End of the script reach. + this.isEndOfScript = true; + break; + } + + line = line.Trim(); + + if (line.StartsWith("GO")) + { + // Parse the number after the "GO". + var textAfterGo = line.Substring(2).Trim(); + + if (textAfterGo != string.Empty) + { + count = Convert.ToInt32(textAfterGo, CultureInfo.InvariantCulture); + } + + // If no code parsed, we continue to parse the block. + if (codeBuilder.Length == 0) + { + continue; + } + + // Else, we stop the read of the script. + break; + } + + codeBuilder.AppendLine(line); + } + + return new SqlServerScriptBlock(codeBuilder.ToString(), count); + } + } +} diff --git a/tests/Testing.Databases.SqlServer.Tests/SqlServerDatabaseExtensionsTest.cs b/tests/Testing.Databases.SqlServer.Tests/SqlServerDatabaseExtensionsTest.cs index d4379a4..538041a 100644 --- a/tests/Testing.Databases.SqlServer.Tests/SqlServerDatabaseExtensionsTest.cs +++ b/tests/Testing.Databases.SqlServer.Tests/SqlServerDatabaseExtensionsTest.cs @@ -90,5 +90,71 @@ Binary VARBINARY(MAX) NULL, table.Rows[1]["Boolean"].As().Should().Be(false); table.Rows[1]["BooleanNull"].Should().Be(DBNull.Value); } + + [Fact] + public void ExecuteScript_String() + { + var server = new SqlServer(ConnectionString); + + var database = server.CreateEmptyDatabase("SqlServerDatabaseExtensionsTest"); + + database.ExecuteScript(@" + CREATE TABLE TableTest + ( + Id INT NOT NULL + ) + + GO + GO + + INSERT INTO [TableTest] ([Id]) VALUES (0) + + GO + UPDATE [TableTest] + SET [Id] = [Id] + 1 + + GO 10 + + "); + + var table = database.ExecuteQuery("SELECT * FROM [TableTest]"); + + table.Rows.Should().HaveCount(1); + + table.Rows[0]["Id"].Should().Be(10); + } + + [Fact] + public void ExecuteScript_StringReader() + { + var server = new SqlServer(ConnectionString); + + var database = server.CreateEmptyDatabase("SqlServerDatabaseExtensionsTest"); + + database.ExecuteScript(new StringReader(@" + CREATE TABLE TableTest + ( + Id INT NOT NULL + ) + + GO + GO + + INSERT INTO [TableTest] ([Id]) VALUES (0) + + GO + UPDATE [TableTest] + SET [Id] = [Id] + 1 + + GO 10 + + ")); + + var table = database.ExecuteQuery("SELECT * FROM [TableTest]"); + + table.Rows.Should().HaveCount(1); + + table.Rows[0]["Id"].Should().Be(10); + } } } \ No newline at end of file