This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace DatabaseUnitTesting.Utilities
{
internal class DatabaseAdapter
{
private readonly SqlConnection _connection;
internal DatabaseAdapter(SqlConnection connection)
{
_connection = connection;
if (_connection.State == ConnectionState.Closed)
_connection.Open();
}
internal bool IsSnapshot(string name)
{
SqlCommand command = _connection.CreateCommand();
command.CommandText =
"--Testing Existence Type\nSELECT source_database_id FROM sys.databases WHERE name = @name";
command.Parameters.AddWithValue("@name", name);
object objResult = command.ExecuteScalar();
if (objResult != null && !(objResult is DBNull))
return true;
return false;
}
internal void UseDatabase(string databaseName)
{
SqlCommand command = _connection.CreateCommand();
command.CommandText = "USE " + databaseName;
command.ExecuteNonQuery();
}
internal void CreateSnapshot(string databaseName, string snapshotName)
{
SqlCommand command = _connection.CreateCommand();
StringBuilder stringBuilder = new StringBuilder("-- Creating Snapshot \nCREATE DATABASE ");
stringBuilder.Append(snapshotName);
stringBuilder.Append(" ON ( NAME = ");
stringBuilder.Append(databaseName);
stringBuilder.Append(", FILENAME = 'C:\\Temp\\");
stringBuilder.Append(snapshotName);
stringBuilder.Append("') AS SNAPSHOT OF ");
stringBuilder.Append(databaseName);
command.CommandText = stringBuilder.ToString();
command.ExecuteNonQuery();
}
internal void DropSnapshot(string snapshotName)
{
if (!IsSnapshot(snapshotName))
throw new ArgumentException("A snapshot of name " + snapshotName + " does not exist.");
SqlCommand command = _connection.CreateCommand();
command.CommandText = "DROP DATABASE " + snapshotName;
command.ExecuteNonQuery();
}
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
using DatabaseUnitTesting.Utilities.Results;
namespace DatabaseUnitTesting.Utilities
{
internal class DatabaseComparer
{
private readonly SqlConnection _connection;
private readonly DatabaseAdapter _databaseAdapter;
private readonly string _databaseOne;
private readonly string _databaseTwo;
private readonly List<ObjectsToCompare> _objectsToCompare = new List<ObjectsToCompare>();
internal DatabaseComparer(SqlConnection connection, string databaseOneName, string databaseTwoName)
{
_connection = connection;
_databaseAdapter = new DatabaseAdapter(connection);
_databaseAdapter.UseDatabase(databaseOneName);
_databaseOne = databaseOneName;
_databaseTwo = databaseTwoName;
}
internal void CleanUp()
{
_objectsToCompare.Clear();
}
internal Database GenerateDifferences(SqlTransaction transaction)
{
Database tables = new Database();
foreach (ObjectsToCompare objects in _objectsToCompare)
{
Table table = RunCompare(transaction, objects);
if (table.RowCount > 0)
tables.AddTable(table);
}
return tables;
}
internal string GetAllColumns(SqlTransaction transaction, ObjectsToCompare objects)
{
SqlCommand command = _connection.CreateCommand();
command.CommandText =
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = @table AND" +
" table_schema = @schema AND table_catalog = @database";
command.Transaction = transaction;
command.Parameters.AddWithValue("@table", objects.Object1Name);
command.Parameters.AddWithValue("@schema", objects.Schema1Name);
command.Parameters.AddWithValue("@database", _databaseOne);
List<string> allColumns = new List<string>();
using (SqlDataReader result = command.ExecuteReader())
{
while (result.Read())
allColumns.Add(result.GetString(0));
}
if (allColumns.Count == 0)
throw new ArgumentException("Object " + objects.Qualified1 + " does not exist");
foreach (string victim in objects.ColumnsToIgnore)
{
if (!allColumns.Remove(victim))
throw new ArgumentException("Specified column " + victim + " was not in table " +
objects.Qualified1);
if (allColumns.Count == 0)
throw new ArgumentException("User cannot ignore all columns in a table.");
}
return String.Join(",", allColumns.ToArray());
}
internal Table RunCompare(SqlTransaction transaction, ObjectsToCompare objectComparison)
{
Table table =
new Table(objectComparison.Qualified1, objectComparison.Qualified2);
string columns = GetAllColumns(transaction, objectComparison);
StringBuilder select = new StringBuilder("SELECT ", 1000);
select.Append(columns);
select.Append(", ROW_NUMBER() OVER(PARTITION BY ");
select.Append(columns);
select.Append(" ORDER BY @@SPID) AS 'TempRowNumber' FROM ");
string select1 = select + _databaseOne + "." + objectComparison.Qualified1;
string select2 = select + _databaseTwo + "." + objectComparison.Qualified2;
string commandText = "--Data Comparison\n" + select1 + "\nEXCEPT\n" + select2 + "\n" +
select2 + "\nEXCEPT\n" + select1;
SqlCommand command = _connection.CreateCommand();
command.CommandText = commandText;
command.Transaction = transaction;
using (SqlDataReader reader = command.ExecuteReader())
{
string type = "In First";
string[] columnNames = columns.Split(',');
do
{
while (reader.Read())
{
Row row = new Row(type);
for (int i = 0; i < (reader.FieldCount - 1); i++)
{
object value = reader.GetValue(i);
if (!(value is DBNull))
row.AddColumn(new Column(columnNames[i].ToLower(), value));
}
table.AddRow(row);
}
type = "In Second";
} while (reader.NextResult());
}
return table;
}
internal void AddObjectComparison(string schema1, string object1, string schema2, string object2)
{
_objectsToCompare.Add(new ObjectsToCompare(schema1, object1, schema2, object2));
}
internal void AddColumnToIgnore(string schemaName, string objectName, string columnName)
{
_objectsToCompare.Find(
delegate (ObjectsToCompare item) { return item.Schema1Name == schemaName && item.Object1Name == objectName; }
).AddColumnToIgnore(columnName);
}
internal void AddColumnsToIgnore(string schemaName, string tableName, List<string> columnNames)
{
foreach (string columnName in columnNames)
AddColumnToIgnore(schemaName, tableName, columnName);
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
namespace DatabaseUnitTesting.Utilities
{
internal class ObjectsToCompare
{
private readonly string _schema1Name;
private readonly string _object1Name;
private readonly string _schema2Name;
private readonly string _object2Name;
private readonly List<string> _columnsToIgnore = new List<string>();
public ObjectsToCompare(string schema1Name, string object1Name, string schema2Name,
string object2Name)
{
_schema1Name = schema1Name;
_object1Name = object1Name;
_schema2Name = schema2Name;
_object2Name = object2Name;
}
public IEnumerable<string> ColumnsToIgnore
{
get { return _columnsToIgnore; }
}
public string Object1Name
{
get { return _object1Name; }
}
public string Schema1Name
{
get { return _schema1Name; }
}
public string Object2Name
{
get { return _object2Name; }
}
public string Schema2Name
{
get { return _schema2Name; }
}
public string Qualified1
{
get { return _schema1Name + "." + _object1Name; }
}
public string Qualified2
{
get { return _schema2Name + "." + _object2Name; }
}
public void AddColumnToIgnore(string columnName)
{
_columnsToIgnore.Add(columnName);
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
namespace DatabaseUnitTesting.Utilities.Results
{
internal class Column
{
private readonly string _name;
private readonly string _value;
private readonly string _sortString;
public const string DELIMITER = "\x1f;;";
public Column(string name, object value)
{
_name = name;
_value = Convert(value);
_sortString = String.Concat(_name.ToLower(), DELIMITER, _value);
}
public static string Convert(object value)
{
if (value is byte[])
{
string[] binary = new string[((byte[])value).Length + 1];
binary[0] = "0x";
for (int i = 1; i < binary.Length; i++)
binary[i] = ((byte[])value)[i - 1].ToString("X1");
return String.Join("", binary);
}
if (value is DateTime)
{
string time = ((DateTime)value).ToShortDateString() + " ";
time += ((DateTime)value).TimeOfDay;
return time.TrimEnd('0').TrimEnd(':');
}
return value.ToString();
}
public string Name
{
get { return _name; }
}
public string Value
{
get { return _value; }
}
public string SortString
{
get { return _sortString; }
}
}
}

View File

@@ -0,0 +1,66 @@
using System.Collections.Generic;
namespace DatabaseUnitTesting.Utilities.Results
{
internal class Database
{
private int _tableCount = 0;
private int _hashCode = 0;
private readonly Dictionary<Table, int> _tables = new Dictionary<Table, int>();
public int TableCount
{
get { return _tableCount; }
}
public IEnumerable<KeyValuePair<Table, int>> Tables
{
get { return _tables; }
}
public void AddTable(Table table)
{
if (_tables.ContainsKey(table))
_tables[table]++;
else
_tables.Add(table, 1);
_tableCount++;
_hashCode = _hashCode + table.GetHashCode();
}
public bool ContainsTable(Table table)
{
return _tables.ContainsKey(table);
}
public int GetCount(Table table)
{
return _tables[table];
}
public override int GetHashCode()
{
return _hashCode;
}
public override bool Equals(object otherObject)
{
if (!(otherObject is Database))
return false;
Database other = (Database)otherObject;
if (TableCount != other.TableCount ||
GetHashCode() != other.GetHashCode())
return false;
foreach (KeyValuePair<Table, int> pair in _tables)
if (!other.ContainsTable(pair.Key) ||
other.GetCount(pair.Key) != pair.Value)
return false;
return true;
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace DatabaseUnitTesting.Utilities.Results
{
internal static class ResultSetParser
{
private const int NAME = 0;
private const int WIDTH = 2;
private const int PRECISION = 3;
private const int SCALE = 4;
private const int TYPE = 24;
internal static Database Parse(SqlCommand command)
{
using (SqlDataReader sqlReader = command.ExecuteReader())
{
Database results = new Database();
if (!sqlReader.HasRows)
return results;
do
{
Table table = new Table("Result Set");
List<string> fieldNames = SetFields(table, sqlReader.GetSchemaTable());
while (sqlReader.Read())
{
Row row = new Row("datarow");
for (int i = 0; i < sqlReader.FieldCount; i++)
{
object value = sqlReader.GetValue(i);
if (!(value is DBNull))
row.AddColumn(new Column(fieldNames[i], sqlReader.GetValue(i)));
}
table.AddRow(row);
}
results.AddTable(table);
} while (sqlReader.NextResult());
return results;
}
}
internal static List<string> SetFields(Table table, DataTable schema)
{
List<string> fieldNames = new List<string>();
foreach (DataRow dataRow in schema.Rows)
{
string name = dataRow[NAME].ToString();
string type = dataRow[TYPE].ToString().ToLower();
string precision = dataRow[PRECISION].ToString();
string scale = dataRow[SCALE].ToString();
string width = dataRow[WIDTH].ToString();
StringBuilder typeString = new StringBuilder(type);
if (type.Equals("decimal") || type.Equals("numeric"))
{
typeString.Append("(");
typeString.Append(precision);
typeString.Append(",");
typeString.Append(scale);
typeString.Append(")");
}
else if (type.Contains("char") || type.Contains("binary"))
{
if (int.Parse(width) > 8000)
width = "max";
typeString.Append("(");
typeString.Append(width);
typeString.Append(")");
}
fieldNames.Add(name);
table.AddField(name, typeString.ToString());
}
return fieldNames;
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
namespace DatabaseUnitTesting.Utilities.Results
{
internal class Row
{
private readonly string _type;
private readonly List<Column> _columns = new List<Column>();
private string _keyString;
private bool _keyValid = false;
public const string DELIMITER = "\x1e;;";
public Row(string type)
{
_type = type.ToLower();
}
public void AddColumn(Column column)
{
_columns.Add(column);
_keyValid = false;
}
public string KeyString
{
get
{
if (!_keyValid)
{
string[] keyString = new string[_columns.Count + 1];
keyString[0] = _type;
for (int i = 1; i < keyString.Length; i++)
keyString[i] = _columns[i - 1].SortString;
_keyString = String.Join(DELIMITER, keyString);
_keyValid = true;
}
return _keyString;
}
}
public int ColumnCount
{
get { return _columns.Count; }
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
namespace DatabaseUnitTesting.Utilities.Results
{
internal class Table
{
private readonly string _name1;
private readonly string _name2;
private Row _schema = new Row("schema");
private readonly Dictionary<string, int> rows = new Dictionary<string, int>();
private int _hashCode;
private int _rowCount;
private int _fieldCount;
public Table(string name1) : this(name1, String.Empty)
{ }
public Table(string name1, string name2)
{
_name1 = name1.ToLower();
_name2 = name2.ToLower();
}
public override bool Equals(object otherObject)
{
if (!(otherObject is Table))
return false;
Table other = (Table)otherObject;
if (GetHashCode() != other.GetHashCode())
return false;
if (RowCount != other.RowCount ||
FieldCount != other.FieldCount ||
!Schema.KeyString.Equals(other.Schema.KeyString))
return false;
int otherCount;
foreach (string row in rows.Keys)
if (!other.LookupRow(row, out otherCount) ||
otherCount != rows[row])
return false;
return true;
}
public override int GetHashCode()
{
return _hashCode;
}
public string Name1
{
get { return _name1; }
}
public string Name2
{
get { return _name2; }
}
public void AddRow(Row row)
{
string key = row.KeyString;
if (rows.ContainsKey(key))
rows[key]++;
else
rows.Add(key, 1);
_rowCount = _rowCount + 1;
_hashCode = _hashCode + key.GetHashCode();
}
public IEnumerable<KeyValuePair<string, int>> Rows
{
get { return rows; }
}
public int RowCount
{
get { return _rowCount; }
}
public int FieldCount
{
get { return _fieldCount; }
}
public Row Schema
{
get { return _schema; }
set
{
_schema = value;
_fieldCount = _schema.ColumnCount;
}
}
public void AddField(string name, string type)
{
Column c = new Column(name, type.ToLower());
_schema.AddColumn(c);
_fieldCount++;
// _hashCode = _hashCode + c.SortString.GetHashCode();
}
public bool LookupRow(string key, out int other)
{
return rows.TryGetValue(key, out other);
}
}
}

View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Security;
using System.Text;
using System.Xml;
namespace DatabaseUnitTesting.Utilities.Results
{
internal static class XmlFileAdapter
{
private const string LEFT = "\0L;;";
private const string RIGHT = "\0R;;";
internal static Database Read(string filename)
{
XmlDocument document = new XmlDocument();
Database database = new Database();
document.Load(filename);
XmlNode xmlRoot = document.LastChild;
if (xmlRoot == null)
return database;
foreach (Table diff in ReadTables(xmlRoot))
database.AddTable(diff);
return database;
}
internal static List<Table> ReadTables(XmlNode xmlRoot)
{
List<Table> tableDiffs = new List<Table>();
for (XmlNode xmlObject = xmlRoot.FirstChild; xmlObject != null; xmlObject = xmlObject.NextSibling)
{
XmlAttribute name1Attribute = xmlObject.Attributes["name1"];
XmlAttribute name2Attribute = xmlObject.Attributes["name2"];
if (name1Attribute == null || name2Attribute == null)
throw new XmlSyntaxException("Tables must have name1 and name2 attributes");
Table tableDiff = new Table(name1Attribute.Value, name2Attribute.Value);
Row schema = new Row("schema");
ReadColumns(xmlObject.FirstChild, schema);
tableDiff.Schema = schema;
foreach (Row row in ReadRows(xmlObject))
{
tableDiff.AddRow(row);
}
tableDiffs.Add(tableDiff);
}
return tableDiffs;
}
internal static List<Row> ReadRows(XmlNode xmlTable)
{
List<Row> rowDiffs = new List<Row>();
for (XmlNode xmlRow = xmlTable.FirstChild.NextSibling; xmlRow != null; xmlRow = xmlRow.NextSibling)
{
XmlAttribute typeAttribute = xmlRow.Attributes["type"];
if (typeAttribute == null)
throw new XmlSyntaxException("Row does not have a 'type' attribute");
Row row = new Row(typeAttribute.Value);
ReadColumns(xmlRow, row);
rowDiffs.Add(row);
}
return rowDiffs;
}
internal static void ReadColumns(XmlNode xmlRow, Row row)
{
for (XmlNode column = xmlRow.FirstChild; column != null; column = column.NextSibling)
{
XmlAttribute nameAttribute = column.Attributes["name"];
if (nameAttribute == null)
throw new XmlSyntaxException("Fields and Keys must have 'name' attributes");
string name = nameAttribute.Value.ToLower();
if (column.Name.ToLower().Equals("column"))
{
XmlAttribute valueAttribute = column.Attributes["value"];
if (valueAttribute == null)
throw new XmlSyntaxException("Columns must have 'value' attribute");
row.AddColumn(new Column(name, valueAttribute.Value.Replace(LEFT, "<").Replace(RIGHT, ">")));
}
else
throw new XmlSyntaxException("Rows may only contain 'column' children");
}
}
internal static void Write(string filename, Database diffs)
{
using (XmlTextWriter writer = new XmlTextWriter(filename, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
writer.WriteStartDocument();
writer.WriteStartElement("results");
foreach (KeyValuePair<Table, int> table in diffs.Tables)
for (int i = 0; i < table.Value; i++)
WriteTable(writer, table.Key);
writer.WriteEndDocument();
}
}
internal static void WriteTable(XmlTextWriter writer, Table tableDiff)
{
writer.WriteStartElement("object");
writer.WriteAttributeString("name1", tableDiff.Name1);
writer.WriteAttributeString("name2", tableDiff.Name2);
WriteRow(writer, tableDiff.Schema.KeyString);
foreach (KeyValuePair<string, int> row in tableDiff.Rows)
{
for (int i = 0; i < row.Value; i++)
WriteRow(writer, row.Key);
}
writer.WriteEndElement();
}
internal static void WriteRow(XmlTextWriter writer, string rowDiff)
{
writer.WriteStartElement("row");
string[] columns = rowDiff.Split(new string[] { Row.DELIMITER }, StringSplitOptions.None);
writer.WriteAttributeString("type", columns[0]);
for (int i = 1; i < columns.Length; i++)
WriteColumn(writer, columns[i]);
writer.WriteEndElement();
}
internal static void WriteColumn(XmlTextWriter writer, string column)
{
string[] definition = column.Split(new string[] { Column.DELIMITER }, StringSplitOptions.None);
writer.WriteStartElement("column");
writer.WriteAttributeString("name", definition[0]);
writer.WriteAttributeString("value", definition[1].Replace("<", LEFT).Replace(">", RIGHT));
writer.WriteEndElement();
}
}
}