Skip to content

Commit 0f2c855

Browse files
committed
Refactored for testablility: split SqlTableCreator into two separate testable and mockable classes with interfaces and added an internal constructor to MSSqlServerSinkTraits which allows injection of an ISqlTableCreator.
1 parent c6f1185 commit 0f2c855

File tree

5 files changed

+126
-97
lines changed

5 files changed

+126
-97
lines changed

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkTraits.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,21 @@ public MSSqlServerSinkTraits(
4646
IFormatProvider formatProvider,
4747
bool autoCreateSqlTable,
4848
ITextFormatter logEventFormatter)
49+
: this(connectionString, tableName, schemaName, columnOptions, formatProvider, autoCreateSqlTable,
50+
logEventFormatter, new SqlTableCreator(new SqlCreateTableWriter()))
51+
{
52+
}
53+
54+
// Internal constructor with injectable dependencies for better testability
55+
internal MSSqlServerSinkTraits(
56+
string connectionString,
57+
string tableName,
58+
string schemaName,
59+
ColumnOptions columnOptions,
60+
IFormatProvider formatProvider,
61+
bool autoCreateSqlTable,
62+
ITextFormatter logEventFormatter,
63+
ISqlTableCreator sqlTableCreator)
4964
{
5065
if (string.IsNullOrWhiteSpace(connectionString))
5166
throw new ArgumentNullException(nameof(connectionString));
@@ -80,8 +95,7 @@ public MSSqlServerSinkTraits(
8095
{
8196
try
8297
{
83-
SqlTableCreator tableCreator = new SqlTableCreator(ConnectionString, SchemaName, TableName, EventTable, ColumnOptions);
84-
tableCreator.CreateTable(); // return code ignored, 0 = failure?
98+
sqlTableCreator.CreateTable(ConnectionString, SchemaName, TableName, EventTable, ColumnOptions); // return code ignored, 0 = failure?
8599
}
86100
catch (Exception ex)
87101
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Data;
2+
3+
namespace Serilog.Sinks.MSSqlServer.Platform
4+
{
5+
internal interface ISqlCreateTableWriter
6+
{
7+
string GetSqlFromDataTable(string schemaName, string tableName, DataTable dataTable, ColumnOptions columnOptions);
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Data;
2+
3+
namespace Serilog.Sinks.MSSqlServer.Platform
4+
{
5+
internal interface ISqlTableCreator
6+
{
7+
int CreateTable(string connectionString, string schemaName, string tableName, DataTable dataTable, ColumnOptions columnOptions);
8+
}
9+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System.Data;
2+
using System.Text;
3+
4+
namespace Serilog.Sinks.MSSqlServer.Platform
5+
{
6+
internal class SqlCreateTableWriter : ISqlCreateTableWriter
7+
{
8+
public string GetSqlFromDataTable(string schemaName, string tableName, DataTable dataTable, ColumnOptions columnOptions)
9+
{
10+
var sql = new StringBuilder();
11+
var ix = new StringBuilder();
12+
int indexCount = 1;
13+
14+
// start schema check and DDL (wrap in EXEC to make a separate batch)
15+
sql.AppendLine($"IF(NOT EXISTS(SELECT * FROM sys.schemas WHERE name = '{schemaName}'))");
16+
sql.AppendLine("BEGIN");
17+
sql.AppendLine($"EXEC('CREATE SCHEMA [{schemaName}] AUTHORIZATION [dbo]')");
18+
sql.AppendLine("END");
19+
20+
// start table-creatin batch and DDL
21+
sql.AppendLine($"IF NOT EXISTS (SELECT s.name, t.name FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE s.name = '{schemaName}' AND t.name = '{tableName}')");
22+
sql.AppendLine("BEGIN");
23+
sql.AppendLine($"CREATE TABLE [{schemaName}].[{tableName}] ( ");
24+
25+
// build column list
26+
int i = 1;
27+
foreach (DataColumn column in dataTable.Columns)
28+
{
29+
var common = (SqlColumn)column.ExtendedProperties["SqlColumn"];
30+
31+
sql.Append(GetColumnDDL(common));
32+
if (dataTable.Columns.Count > i++) sql.Append(",");
33+
sql.AppendLine();
34+
35+
// collect non-PK indexes for separate output after the table DDL
36+
if (common != null && common.NonClusteredIndex && common != columnOptions.PrimaryKey)
37+
ix.AppendLine($"CREATE NONCLUSTERED INDEX [IX{indexCount++}_{tableName}] ON [{schemaName}].[{tableName}] ([{common.ColumnName}]);");
38+
}
39+
40+
// primary key constraint at the end of the table DDL
41+
if (columnOptions.PrimaryKey != null)
42+
{
43+
var clustering = (columnOptions.PrimaryKey.NonClusteredIndex ? "NON" : string.Empty);
44+
sql.AppendLine($" CONSTRAINT [PK_{tableName}] PRIMARY KEY {clustering}CLUSTERED ([{columnOptions.PrimaryKey.ColumnName}])");
45+
}
46+
47+
// end of CREATE TABLE
48+
sql.AppendLine(");");
49+
50+
// CCI is output separately after table DDL
51+
if (columnOptions.ClusteredColumnstoreIndex)
52+
sql.AppendLine($"CREATE CLUSTERED COLUMNSTORE INDEX [CCI_{tableName}] ON [{schemaName}].[{tableName}]");
53+
54+
// output any extra non-clustered indexes
55+
sql.Append(ix);
56+
57+
// end of batch
58+
sql.AppendLine("END");
59+
60+
return sql.ToString();
61+
}
62+
63+
// Examples of possible output:
64+
// [Id] BIGINT IDENTITY(1,1) NOT NULL
65+
// [Message] VARCHAR(1024) NULL
66+
private string GetColumnDDL(SqlColumn column)
67+
{
68+
var sb = new StringBuilder();
69+
70+
sb.Append($"[{column.ColumnName}] ");
71+
72+
sb.Append(column.DataType.ToString().ToUpperInvariant());
73+
74+
if (SqlDataTypes.DataLengthRequired.Contains(column.DataType))
75+
sb.Append("(").Append(column.DataLength == -1 ? "MAX" : column.DataLength.ToString()).Append(")");
76+
77+
if (column.StandardColumnIdentifier == StandardColumn.Id)
78+
sb.Append(" IDENTITY(1,1)");
79+
80+
sb.Append(column.AllowNull ? " NULL" : " NOT NULL");
81+
82+
return sb.ToString();
83+
}
84+
}
85+
}
Lines changed: 7 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,22 @@
11
using System.Data;
22
using System.Data.SqlClient;
3-
using System.Text;
43

54
namespace Serilog.Sinks.MSSqlServer.Platform
65
{
7-
internal class SqlTableCreator
6+
internal class SqlTableCreator : ISqlTableCreator
87
{
9-
private readonly string _connectionString;
10-
private readonly string _schemaName;
11-
private readonly string _tableName;
12-
private readonly DataTable _dataTable;
13-
private readonly ColumnOptions _columnOptions;
8+
private readonly ISqlCreateTableWriter _sqlCreateTableWriter;
149

15-
public SqlTableCreator(string connectionString, string schemaName, string tableName, DataTable dataTable, ColumnOptions columnOptions)
10+
public SqlTableCreator(ISqlCreateTableWriter sqlCreateTableWriter)
1611
{
17-
_connectionString = connectionString;
18-
_schemaName = schemaName;
19-
_tableName = tableName;
20-
_dataTable = dataTable;
21-
_columnOptions = columnOptions;
12+
_sqlCreateTableWriter = sqlCreateTableWriter;
2213
}
2314

24-
public int CreateTable()
15+
public int CreateTable(string connectionString, string schemaName, string tableName, DataTable dataTable, ColumnOptions columnOptions)
2516
{
26-
using (var conn = new SqlConnection(_connectionString))
17+
using (var conn = new SqlConnection(connectionString))
2718
{
28-
string sql = GetSqlFromDataTable();
19+
string sql = _sqlCreateTableWriter.GetSqlFromDataTable(schemaName, tableName, dataTable, columnOptions);
2920
using (SqlCommand cmd = new SqlCommand(sql, conn))
3021
{
3122
conn.Open();
@@ -34,84 +25,5 @@ public int CreateTable()
3425

3526
}
3627
}
37-
38-
private string GetSqlFromDataTable()
39-
{
40-
var sql = new StringBuilder();
41-
var ix = new StringBuilder();
42-
int indexCount = 1;
43-
44-
// start schema check and DDL (wrap in EXEC to make a separate batch)
45-
sql.AppendLine($"IF(NOT EXISTS(SELECT * FROM sys.schemas WHERE name = '{_schemaName}'))");
46-
sql.AppendLine("BEGIN");
47-
sql.AppendLine($"EXEC('CREATE SCHEMA [{_schemaName}] AUTHORIZATION [dbo]')");
48-
sql.AppendLine("END");
49-
50-
// start table-creatin batch and DDL
51-
sql.AppendLine($"IF NOT EXISTS (SELECT s.name, t.name FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE s.name = '{_schemaName}' AND t.name = '{_tableName}')");
52-
sql.AppendLine("BEGIN");
53-
sql.AppendLine($"CREATE TABLE [{_schemaName}].[{_tableName}] ( ");
54-
55-
// build column list
56-
int i = 1;
57-
foreach (DataColumn column in _dataTable.Columns)
58-
{
59-
var common = (SqlColumn)column.ExtendedProperties["SqlColumn"];
60-
61-
sql.Append(GetColumnDDL(common));
62-
if (_dataTable.Columns.Count > i++) sql.Append(",");
63-
sql.AppendLine();
64-
65-
// collect non-PK indexes for separate output after the table DDL
66-
if(common != null && common.NonClusteredIndex && common != _columnOptions.PrimaryKey)
67-
ix.AppendLine($"CREATE NONCLUSTERED INDEX [IX{indexCount++}_{_tableName}] ON [{_schemaName}].[{_tableName}] ([{common.ColumnName}]);");
68-
}
69-
70-
// primary key constraint at the end of the table DDL
71-
if (_columnOptions.PrimaryKey != null)
72-
{
73-
var clustering = (_columnOptions.PrimaryKey.NonClusteredIndex ? "NON" : string.Empty);
74-
sql.AppendLine($" CONSTRAINT [PK_{_tableName}] PRIMARY KEY {clustering}CLUSTERED ([{_columnOptions.PrimaryKey.ColumnName}])");
75-
}
76-
77-
// end of CREATE TABLE
78-
sql.AppendLine(");");
79-
80-
// CCI is output separately after table DDL
81-
if (_columnOptions.ClusteredColumnstoreIndex)
82-
sql.AppendLine($"CREATE CLUSTERED COLUMNSTORE INDEX [CCI_{_tableName}] ON [{_schemaName}].[{_tableName}]");
83-
84-
// output any extra non-clustered indexes
85-
sql.Append(ix);
86-
87-
// end of batch
88-
sql.AppendLine("END");
89-
90-
return sql.ToString();
91-
}
92-
93-
// Examples of possible output:
94-
// [Id] BIGINT IDENTITY(1,1) NOT NULL
95-
// [Message] VARCHAR(1024) NULL
96-
private string GetColumnDDL(SqlColumn column)
97-
{
98-
var sb = new StringBuilder();
99-
100-
sb.Append($"[{column.ColumnName}] ");
101-
102-
sb.Append(column.DataType.ToString().ToUpperInvariant());
103-
104-
if (SqlDataTypes.DataLengthRequired.Contains(column.DataType))
105-
sb.Append("(").Append(column.DataLength == -1 ? "MAX" : column.DataLength.ToString()).Append(")");
106-
107-
if (column.StandardColumnIdentifier == StandardColumn.Id)
108-
sb.Append(" IDENTITY(1,1)");
109-
110-
sb.Append(column.AllowNull ? " NULL" : " NOT NULL");
111-
112-
return sb.ToString();
113-
}
114-
11528
}
116-
11729
}

0 commit comments

Comments
 (0)