Skip to content

Commit c075a61

Browse files
Add SqlCmd library.
1 parent 8818285 commit c075a61

18 files changed

+1047
-3
lines changed

.github/workflows/github-actions-release.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
type: string
88
description: The version of the library
99
required: true
10-
default: 2.4.0
10+
default: 3.0.0
1111
VersionSuffix:
1212
type: string
1313
description: The version suffix of the library (for example rc.1)
@@ -32,19 +32,26 @@ jobs:
3232
--property:VersionSuffix=${{ github.event.inputs.VersionSuffix }}
3333
"src/Testing.Databases.SqlServer/Testing.Databases.SqlServer.csproj"
3434

35+
- name: Build Testing.Databases.SqlServer.Dac
36+
run: dotnet pack
37+
--property:Configuration=Release
38+
--property:VersionPrefix=${{ github.event.inputs.VersionPrefix }}
39+
--property:VersionSuffix=${{ github.event.inputs.VersionSuffix }}
40+
"src/Testing.Databases.SqlServer.Dac/Testing.Databases.SqlServer.Dac.csproj"
41+
3542
- name: Build Testing.Databases.SqlServer.EntityFramework
3643
run: dotnet pack
3744
--property:Configuration=Release
3845
--property:VersionPrefix=${{ github.event.inputs.VersionPrefix }}
3946
--property:VersionSuffix=${{ github.event.inputs.VersionSuffix }}
4047
"src/Testing.Databases.SqlServer.EntityFramework/Testing.Databases.SqlServer.EntityFramework.csproj"
4148

42-
- name: Build Testing.Databases.SqlServer.Dac
49+
- name: Build Testing.Databases.SqlServer.SqlCmd
4350
run: dotnet pack
4451
--property:Configuration=Release
4552
--property:VersionPrefix=${{ github.event.inputs.VersionPrefix }}
4653
--property:VersionSuffix=${{ github.event.inputs.VersionSuffix }}
47-
"src/Testing.Databases.SqlServer.Dac/Testing.Databases.SqlServer.Dac.csproj"
54+
"src/Testing.Databases.SqlServer.SqlCmd/Testing.Databases.SqlServer.SqlCmd.csproj"
4855

4956
- name: Publish the package to nuget.org
5057
run: dotnet nuget push "src/**/bin/Release/*.nupkg" --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json

PosInformatique.Testing.Databases.sln

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testing.Databases.SqlServer
5959
EndProject
6060
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testing.Databases.SqlServer.Dac.Tests", "tests\Testing.Databases.SqlServer.Dac.Tests\Testing.Databases.SqlServer.Dac.Tests.csproj", "{6751A585-1BB0-49A1-BF68-D7FBD3392DF6}"
6161
EndProject
62+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testing.Databases.SqlServer.SqlCmd", "src\Testing.Databases.SqlServer.SqlCmd\Testing.Databases.SqlServer.SqlCmd.csproj", "{D3004122-CCDD-4EAD-BD9E-DA6DFF470943}"
63+
EndProject
64+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testing.Databases.SqlServer.SqlCmd.Tests", "tests\Testing.Databases.SqlServer.SqlCmd.Tests\Testing.Databases.SqlServer.SqlCmd.Tests.csproj", "{F8E025D7-4E2F-437A-ABFA-C43A1368E15A}"
65+
EndProject
6266
Global
6367
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6468
Debug|Any CPU = Debug|Any CPU
@@ -107,6 +111,14 @@ Global
107111
{6751A585-1BB0-49A1-BF68-D7FBD3392DF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
108112
{6751A585-1BB0-49A1-BF68-D7FBD3392DF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
109113
{6751A585-1BB0-49A1-BF68-D7FBD3392DF6}.Release|Any CPU.Build.0 = Release|Any CPU
114+
{D3004122-CCDD-4EAD-BD9E-DA6DFF470943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
115+
{D3004122-CCDD-4EAD-BD9E-DA6DFF470943}.Debug|Any CPU.Build.0 = Debug|Any CPU
116+
{D3004122-CCDD-4EAD-BD9E-DA6DFF470943}.Release|Any CPU.ActiveCfg = Release|Any CPU
117+
{D3004122-CCDD-4EAD-BD9E-DA6DFF470943}.Release|Any CPU.Build.0 = Release|Any CPU
118+
{F8E025D7-4E2F-437A-ABFA-C43A1368E15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
119+
{F8E025D7-4E2F-437A-ABFA-C43A1368E15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
120+
{F8E025D7-4E2F-437A-ABFA-C43A1368E15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
121+
{F8E025D7-4E2F-437A-ABFA-C43A1368E15A}.Release|Any CPU.Build.0 = Release|Any CPU
110122
EndGlobalSection
111123
GlobalSection(SolutionProperties) = preSolution
112124
HideSolutionNode = FALSE
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="SqlCmdCommandLineArgumentsBuilder.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Testing.Databases.SqlServer
8+
{
9+
using System.Text;
10+
using Microsoft.Data.SqlClient;
11+
12+
internal sealed class SqlCmdCommandLineArgumentsBuilder
13+
{
14+
private readonly Dictionary<string, string> variables;
15+
16+
public SqlCmdCommandLineArgumentsBuilder(SqlConnectionStringBuilder connectionString)
17+
{
18+
this.Database = connectionString.InitialCatalog;
19+
this.LoginId = connectionString.UserID;
20+
this.Password = connectionString.Password;
21+
this.Server = connectionString.DataSource;
22+
this.TrustedConnection = connectionString.IntegratedSecurity;
23+
24+
this.variables = new Dictionary<string, string>();
25+
}
26+
27+
public string? Database { get; set; }
28+
29+
public string? InputFile { get; set; }
30+
31+
public string? LoginId { get; set; }
32+
33+
public string? Password { get; set; }
34+
35+
public string? Server { get; set; }
36+
37+
public bool TrustedConnection { get; set; }
38+
39+
public IReadOnlyDictionary<string, string> Variables => this.variables;
40+
41+
public void AddVariable(string name, string value)
42+
{
43+
this.variables.Add(name, value);
44+
}
45+
46+
public override string ToString()
47+
{
48+
var parameters = new StringBuilder();
49+
50+
if (!string.IsNullOrEmpty(this.Database))
51+
{
52+
parameters.Append($"-d \"{this.Database}\" ");
53+
}
54+
55+
if (!string.IsNullOrEmpty(this.InputFile))
56+
{
57+
parameters.Append($"-i \"{this.InputFile}\" ");
58+
}
59+
60+
if (!string.IsNullOrEmpty(this.LoginId))
61+
{
62+
parameters.Append($"-U \"{this.LoginId}\" ");
63+
}
64+
65+
if (!string.IsNullOrEmpty(this.Password))
66+
{
67+
parameters.Append($"-P \"{this.Password}\" ");
68+
}
69+
70+
if (this.TrustedConnection)
71+
{
72+
parameters.Append($"-E ");
73+
}
74+
75+
if (!string.IsNullOrEmpty(this.Server))
76+
{
77+
parameters.Append($"-S \"{this.Server}\" ");
78+
}
79+
80+
foreach (var variable in this.variables)
81+
{
82+
parameters.Append($"-v {variable.Key}=\"{variable.Value}\" ");
83+
}
84+
85+
// To have exit error code when the script contains errors.
86+
parameters.Append("-b");
87+
88+
return parameters.ToString();
89+
}
90+
}
91+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="SqlCmdDatabaseInitializer.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Testing.Databases.SqlServer
8+
{
9+
using Microsoft.Data.SqlClient;
10+
11+
/// <summary>
12+
/// Initializer used to initialize the database for the tests.
13+
/// Call the <see cref="Initialize(SqlServerDatabaseInitializer, string, string, SqlCmdRunScriptSettings?)"/> method to initialize a database from
14+
/// a T-SQL script file using <c>sqlcmd</c>.
15+
/// </summary>
16+
/// <remarks>The database will be created the call of the <see cref="Initialize(SqlServerDatabaseInitializer, string, string, SqlCmdRunScriptSettings?)"/> method. For the next calls
17+
/// the database is preserved but all the data are deleted.</remarks>
18+
public static class SqlCmdDatabaseInitializer
19+
{
20+
/// <summary>
21+
/// Initialize a SQL Server database by executing the T-SQL script specified in the <paramref name="fileName"/> argument.
22+
/// The script will be executed on the <c>master</c> database specified in the <paramref name="connectionString"/>, so
23+
/// you have to switch the connection if need in your script using the T-SQL <c>USE</c> directive.
24+
/// </summary>
25+
/// <param name="initializer"><see cref="SqlServerDatabaseInitializer"/> which the initialization will be perform on.</param>
26+
/// <param name="fileName">Full path of the T-SQL file to execute.</param>
27+
/// <param name="connectionString">Connection string to the SQL Server with administrator rights.</param>
28+
/// <param name="settings">Additionnal settings to run the <c>sqlcmd</c> tool.</param>
29+
/// <returns>An instance of the <see cref="SqlServerDatabase"/> which allows to perform query to initialize the data.</returns>
30+
public static SqlServerDatabase Initialize(this SqlServerDatabaseInitializer initializer, string fileName, string connectionString, SqlCmdRunScriptSettings? settings = null)
31+
{
32+
var connectionStringBuilder = new SqlConnectionStringBuilder(connectionString);
33+
34+
var server = new SqlServer(connectionString);
35+
36+
SqlServerDatabase database;
37+
38+
if (!initializer.IsInitialized)
39+
{
40+
server.Master.RunScript(fileName, settings);
41+
42+
initializer.IsInitialized = true;
43+
}
44+
45+
database = server.GetDatabase(connectionStringBuilder.InitialCatalog);
46+
database.ClearAllData();
47+
48+
return database;
49+
}
50+
}
51+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="SqlCmdException.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Testing.Databases.SqlServer
8+
{
9+
/// <summary>
10+
/// Occured when an error has been raised by the SQL Server <c>sqlcmd</c> tool.
11+
/// </summary>
12+
public class SqlCmdException : Exception
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="SqlCmdException"/> class.
16+
/// </summary>
17+
public SqlCmdException()
18+
: base()
19+
{
20+
}
21+
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="SqlCmdException"/> class
24+
/// with the specified <paramref name="message"/>.
25+
/// </summary>
26+
/// <param name="message">Exception message.</param>
27+
public SqlCmdException(string message)
28+
: base(message)
29+
{
30+
}
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="SqlCmdException"/> class
34+
/// with the specified <paramref name="message"/> and <paramref name="output"/>
35+
/// of the <c>sqlcmd</c> process.
36+
/// </summary>
37+
/// <param name="message">Exception message.</param>
38+
/// <param name="output">Output of the <c>sqlcmd</c> process.</param>
39+
public SqlCmdException(string message, string output)
40+
: base(message)
41+
{
42+
this.Output = output;
43+
}
44+
45+
/// <summary>
46+
/// Initializes a new instance of the <see cref="SqlCmdException"/> class
47+
/// with the specified <paramref name="message"/> raised by
48+
/// an other <paramref name="innerException"/>.
49+
/// </summary>
50+
/// <param name="message">Exception message.</param>
51+
/// <param name="innerException">Previous <see cref="Exception"/> which raised the <see cref="SqlCmdException"/>.</param>
52+
public SqlCmdException(string message, Exception innerException)
53+
: base(message, innerException)
54+
{
55+
}
56+
57+
/// <summary>
58+
/// Gets the output of the <c>sqlcmd</c> execution.
59+
/// </summary>
60+
public string? Output { get; }
61+
}
62+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="SqlCmdProcess.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Testing.Databases.SqlServer
8+
{
9+
using System.Diagnostics;
10+
using Microsoft.Data.SqlClient;
11+
12+
internal sealed class SqlCmdProcess : IDisposable
13+
{
14+
private Process? process;
15+
16+
private List<string> output;
17+
18+
private SqlCmdProcess(string arguments)
19+
{
20+
this.output = new List<string>();
21+
22+
this.process = new Process()
23+
{
24+
StartInfo =
25+
{
26+
Arguments = arguments,
27+
FileName = "sqlcmd",
28+
RedirectStandardError = true,
29+
RedirectStandardOutput = true,
30+
UseShellExecute = false,
31+
},
32+
};
33+
34+
this.process.ErrorDataReceived += this.OnOutputDataReceived;
35+
this.process.OutputDataReceived += this.OnOutputDataReceived;
36+
37+
this.process.Start();
38+
39+
this.process.BeginErrorReadLine();
40+
this.process.BeginOutputReadLine();
41+
}
42+
43+
public string Output => string.Join(Environment.NewLine, this.output);
44+
45+
public static SqlCmdProcess RunScript(SqlConnectionStringBuilder connectionString, string inputFile, SqlCmdRunScriptSettings settings)
46+
{
47+
var commandLineBuilder = new SqlCmdCommandLineArgumentsBuilder(connectionString)
48+
{
49+
InputFile = inputFile,
50+
};
51+
52+
foreach (var variable in settings.Variables)
53+
{
54+
commandLineBuilder.AddVariable(variable.Key, variable.Value);
55+
}
56+
57+
var process = new SqlCmdProcess(commandLineBuilder.ToString());
58+
59+
return process;
60+
}
61+
62+
public void Dispose()
63+
{
64+
if (this.process is not null)
65+
{
66+
this.process.Dispose();
67+
this.process = null;
68+
}
69+
}
70+
71+
public int WaitForExit()
72+
{
73+
if (this.process is null)
74+
{
75+
throw new ObjectDisposedException(this.GetType().FullName);
76+
}
77+
78+
this.process.WaitForExit();
79+
80+
return this.process.ExitCode;
81+
}
82+
83+
private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
84+
{
85+
if (e.Data is not null)
86+
{
87+
lock (this.output)
88+
{
89+
this.output.Add(e.Data);
90+
}
91+
}
92+
}
93+
}
94+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="SqlCmdRunScriptSettings.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Testing.Databases.SqlServer
8+
{
9+
/// <summary>
10+
/// Contains additional settings when runing the <c>sqlcmd</c>.
11+
/// </summary>
12+
public class SqlCmdRunScriptSettings
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="SqlCmdRunScriptSettings"/> class.
16+
/// </summary>
17+
public SqlCmdRunScriptSettings()
18+
{
19+
this.Variables = new Dictionary<string, string>();
20+
}
21+
22+
/// <summary>
23+
/// Gets a collection of variables and associated values which will be applied
24+
/// on the script to run. The variables can be referenced in the T-SQL script
25+
/// using the <c>$(Variable)</c> syntax.
26+
/// </summary>
27+
public IDictionary<string, string> Variables { get; }
28+
}
29+
}

0 commit comments

Comments
 (0)