Compare commits
4 Commits
645d41bdb3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| df79b32d45 | |||
| e25a6b166a | |||
| c0c8ac72f1 | |||
| c13cfc320b |
16
DbTools.nuspec
Normal file
16
DbTools.nuspec
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<package >
|
||||||
|
<metadata>
|
||||||
|
<id>DbTools</id>
|
||||||
|
<version>1.0.2.0</version>
|
||||||
|
<title>DbTools</title>
|
||||||
|
<authors>Russ Kollmansberger</authors>
|
||||||
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
|
<license type="expression">MIT</license>
|
||||||
|
<!-- <icon>icon.png</icon> -->
|
||||||
|
<description>A library to sync two database structures and apply migrations.</description>
|
||||||
|
<releaseNotes>Removed async methods, except when capturing data.</releaseNotes>
|
||||||
|
<copyright>$copyright$</copyright>
|
||||||
|
<tags>Tag1 Tag2</tags>
|
||||||
|
</metadata>
|
||||||
|
</package>
|
||||||
@@ -57,13 +57,22 @@ namespace DbToolsTester.Forms {
|
|||||||
|
|
||||||
private async void ToolbarItemClicked(object sender, DevExpress.XtraBars.ItemClickEventArgs e) {
|
private async void ToolbarItemClicked(object sender, DevExpress.XtraBars.ItemClickEventArgs e) {
|
||||||
if (e.Item.Equals(bbiDelta)) {
|
if (e.Item.Equals(bbiDelta)) {
|
||||||
|
deltaSql.Clear();
|
||||||
|
|
||||||
SQLiteConnection db1 = new SQLiteConnection("Data Source=" + Database1File + ";Version=3;");
|
SQLiteConnection db1 = new SQLiteConnection("Data Source=" + Database1File + ";Version=3;");
|
||||||
SQLiteConnection db2 = new SQLiteConnection("Data Source=" + Database2File + ";Version=3;");
|
SQLiteConnection db2 = new SQLiteConnection("Data Source=" + Database2File + ";Version=3;");
|
||||||
|
|
||||||
Delta delta = new Delta();
|
SqlBuilder builder = new SqlBuilder();
|
||||||
deltaSql.Text = delta.BuildDelta(db1, db2, btsRemoveUnusedTables.Checked, btsRemoveUnusedColumns.Checked);
|
string[] missingTables = builder.GetMissingTables(db1, db2);
|
||||||
db1Sql.Text = delta.Db1Sql;
|
foreach (string table in missingTables) {
|
||||||
db2Sql.Text = delta.Db2Sql;
|
string sql = await builder.GetTableCreateSqlAsync(db2, table);
|
||||||
|
deltaSql.AppendText(sql + "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delta delta = new Delta();
|
||||||
|
//deltaSql.Text = delta.BuildDelta(db1, db2, btsRemoveUnusedTables.Checked, btsRemoveUnusedColumns.Checked);
|
||||||
|
//db1Sql.Text = delta.Db1Sql;
|
||||||
|
//db2Sql.Text = delta.Db2Sql;
|
||||||
} else if (e.Item.Equals(bbiApplyBoth)) {
|
} else if (e.Item.Equals(bbiApplyBoth)) {
|
||||||
if (string.IsNullOrEmpty(deltaSql.Text)) {
|
if (string.IsNullOrEmpty(deltaSql.Text)) {
|
||||||
MessageBox.Show("No delta SQL to apply.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show("No delta SQL to apply.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
|
|||||||
// set of attributes. Change these attribute values to modify the information
|
// set of attributes. Change these attribute values to modify the information
|
||||||
// associated with an assembly.
|
// associated with an assembly.
|
||||||
[assembly: AssemblyTitle("DbTools")]
|
[assembly: AssemblyTitle("DbTools")]
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("A small library to sync two database structures and apply migrations.")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany("")]
|
||||||
[assembly: AssemblyProduct("DbTools")]
|
[assembly: AssemblyProduct("DbTools")]
|
||||||
|
|||||||
188
SqlBuilder.cs
188
SqlBuilder.cs
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -37,6 +38,73 @@ namespace DbTools {
|
|||||||
return tables.ToArray();
|
return tables.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares the tables in two database connections and returns the names of tables that exist in the second
|
||||||
|
/// database but are missing from the first.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method ensures that both database connections are open before performing the
|
||||||
|
/// comparison. It retrieves the table names from each database and identifies the tables that are missing
|
||||||
|
/// from the first database.</remarks>
|
||||||
|
/// <param name="db1">The database connection representing the first database. The connection must be valid and open, or it will
|
||||||
|
/// be opened automatically.</param>
|
||||||
|
/// <param name="db2">The database connection representing the second database. The connection must be valid and open, or it will
|
||||||
|
/// be opened automatically.</param>
|
||||||
|
/// <returns>An array of strings containing the names of tables that are present in the second database but not in the
|
||||||
|
/// first. Returns an empty array if no such tables are found.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="db1"/> or <paramref name="db2"/> is <see langword="null"/>.</exception>
|
||||||
|
public string[] GetMissingTables(IDbConnection db1, IDbConnection db2) {
|
||||||
|
if (db1 == null) {
|
||||||
|
throw new ArgumentNullException("Must provide a valid DBConnection for db1");
|
||||||
|
}
|
||||||
|
if (db2 == null) {
|
||||||
|
throw new ArgumentNullException("Must provide a valid DBConnection for db2");
|
||||||
|
}
|
||||||
|
if (db1.State != ConnectionState.Open) {
|
||||||
|
db1.Open();
|
||||||
|
}
|
||||||
|
if (db2.State != ConnectionState.Open) {
|
||||||
|
db2.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] db1Tables = GetTableNames(db1);
|
||||||
|
string[] db2Tables = GetTableNames(db2);
|
||||||
|
|
||||||
|
return db2Tables.Where(f => !db1Tables.Contains(f)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the names of tables that exist in the first database but not in the second database.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method compares the table names in the two databases and identifies tables that
|
||||||
|
/// are unique to the first database. The connections provided will be opened if they are not already
|
||||||
|
/// open.</remarks>
|
||||||
|
/// <param name="db1">The connection to the first database. The connection must be valid and open, or it will be opened
|
||||||
|
/// automatically.</param>
|
||||||
|
/// <param name="db2">The connection to the second database. The connection must be valid and open, or it will be opened
|
||||||
|
/// automatically.</param>
|
||||||
|
/// <returns>An array of strings containing the names of tables that are present in the first database but not in the
|
||||||
|
/// second database. Returns an empty array if no such tables exist.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="db1"/> or <paramref name="db2"/> is <see langword="null"/>.</exception>
|
||||||
|
public string[] GetUnusedTables(IDbConnection db1, IDbConnection db2) {
|
||||||
|
if (db1 == null) {
|
||||||
|
throw new ArgumentNullException("Must provide a valid DBConnection for db1");
|
||||||
|
}
|
||||||
|
if (db2 == null) {
|
||||||
|
throw new ArgumentNullException("Must provide a valid DBConnection for db2");
|
||||||
|
}
|
||||||
|
if (db1.State != ConnectionState.Open) {
|
||||||
|
db1.Open();
|
||||||
|
}
|
||||||
|
if (db2.State != ConnectionState.Open) {
|
||||||
|
db2.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] db1Tables = GetTableNames(db1);
|
||||||
|
string[] db2Tables = GetTableNames(db2);
|
||||||
|
|
||||||
|
return db1Tables.Where(f => !db2Tables.Contains(f)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates the SQL script to recreate the specified table, including its schema, indexes, triggers, and
|
/// Generates the SQL script to recreate the specified table, including its schema, indexes, triggers, and
|
||||||
/// optionally its data.
|
/// optionally its data.
|
||||||
@@ -87,14 +155,14 @@ namespace DbTools {
|
|||||||
sb.AppendLine("\t" + columns.Trim());
|
sb.AppendLine("\t" + columns.Trim());
|
||||||
sb.AppendLine(sql.Substring(startIndex + length) + ";");
|
sb.AppendLine(sql.Substring(startIndex + length) + ";");
|
||||||
|
|
||||||
string indexes = await GetIndexCreateSqlAsync(cn, tableName);
|
string indexes = GetIndexCreateSql(cn, tableName);
|
||||||
if (!string.IsNullOrEmpty(indexes)) {
|
if (!string.IsNullOrEmpty(indexes)) {
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("-- INDEXES --");
|
sb.AppendLine("-- INDEXES --");
|
||||||
sb.AppendLine(indexes);
|
sb.AppendLine(indexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
string triggers = await GetTriggerCreateSqlAsync(cn, tableName);
|
string triggers = GetTriggerCreateSql(cn, tableName);
|
||||||
if (!string.IsNullOrEmpty(triggers)) {
|
if (!string.IsNullOrEmpty(triggers)) {
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("-- TRIGGERS --");
|
sb.AppendLine("-- TRIGGERS --");
|
||||||
@@ -115,6 +183,90 @@ namespace DbTools {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetTableCreateSql(IDbConnection cn, string tableName) {
|
||||||
|
if (cn == null) {
|
||||||
|
throw new ArgumentNullException("cn");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(tableName)) {
|
||||||
|
throw new ArgumentNullException("tableName");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cn.State != ConnectionState.Open) {
|
||||||
|
cn.Open();
|
||||||
|
}
|
||||||
|
using (IDbCommand cmd = cn.CreateCommand()) {
|
||||||
|
cmd.CommandText = $"select sql from sqlite_master where tbl_name='{tableName}' and type='table'";
|
||||||
|
using (IDataReader reader = cmd.ExecuteReader()) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
reader.Read();
|
||||||
|
string sql = reader.GetString(0);
|
||||||
|
|
||||||
|
Match m = Regex.Match(sql, "CREATE TABLE \\S+ \\((.*)\\)", RegexOptions.Singleline);
|
||||||
|
if (!m.Success) {
|
||||||
|
Trace.TraceWarning("Unable to match regex on table " + tableName);
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("-- TABLE " + tableName + " --");
|
||||||
|
int startIndex = m.Groups[1].Index;
|
||||||
|
int length = m.Groups[1].Length;
|
||||||
|
string columns = Regex.Replace(m.Groups[1].Value, "\\s{2,}", " ");
|
||||||
|
columns = Regex.Replace(columns.Replace(", ", ",").Replace(",\n", ","), ",(?!\\d+\\))", ",\r\n\t");
|
||||||
|
|
||||||
|
sb.AppendLine(sql.Substring(0, startIndex));
|
||||||
|
sb.AppendLine("\t" + columns.Trim());
|
||||||
|
sb.AppendLine(sql.Substring(startIndex + length) + ";");
|
||||||
|
|
||||||
|
string indexes = GetIndexCreateSql(cn, tableName);
|
||||||
|
if (!string.IsNullOrEmpty(indexes)) {
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- INDEXES --");
|
||||||
|
sb.AppendLine(indexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
string triggers = GetTriggerCreateSql(cn, tableName);
|
||||||
|
if (!string.IsNullOrEmpty(triggers)) {
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- TRIGGERS --");
|
||||||
|
sb.AppendLine(triggers);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("-- END TABLE " + tableName + " --");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string GetTableDropSql(IDbConnection cn, string tableName) {
|
||||||
|
if (cn == null) {
|
||||||
|
throw new ArgumentNullException("cn");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(tableName)) {
|
||||||
|
throw new ArgumentNullException("tableName");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
string[] indexes = GetIndexCreateSql(cn, tableName).Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (string index in indexes) {
|
||||||
|
Match m = Regex.Match(index, "CREATE INDEX (\\S+) ON .*", RegexOptions.Singleline);
|
||||||
|
if (m.Success) {
|
||||||
|
sb.AppendLine($"DROP INDEX IF EXISTS {m.Groups[1].Value};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] triggers = GetTriggerCreateSql(cn, tableName).Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (string trigger in triggers) {
|
||||||
|
Match m = Regex.Match(trigger, "CREATE TRIGGER (\\S+) .*", RegexOptions.Singleline);
|
||||||
|
if (m.Success) {
|
||||||
|
sb.AppendLine($"DROP TRIGGER IF EXISTS {m.Groups[1].Value};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append($"DROP TABLE IF EXISTS {tableName};");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the SQL statements used to create all indexes for the specified table in a SQLite database.
|
/// Retrieves the SQL statements used to create all indexes for the specified table in a SQLite database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -126,7 +278,7 @@ namespace DbTools {
|
|||||||
/// <returns>A string containing the SQL statements for creating all indexes on the specified table, separated by
|
/// <returns>A string containing the SQL statements for creating all indexes on the specified table, separated by
|
||||||
/// newlines. Returns an empty string if no indexes are found.</returns>
|
/// newlines. Returns an empty string if no indexes are found.</returns>
|
||||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cn"/> is null or if <paramref name="tableName"/> is null or empty.</exception>
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cn"/> is null or if <paramref name="tableName"/> is null or empty.</exception>
|
||||||
public async Task<string> GetIndexCreateSqlAsync(IDbConnection cn, string tableName) {
|
public string GetIndexCreateSql(IDbConnection cn, string tableName) {
|
||||||
if (cn == null) {
|
if (cn == null) {
|
||||||
throw new ArgumentNullException("cn");
|
throw new ArgumentNullException("cn");
|
||||||
}
|
}
|
||||||
@@ -138,16 +290,14 @@ namespace DbTools {
|
|||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
await Task.Run(() => {
|
using (IDbCommand cmd = cn.CreateCommand()) {
|
||||||
using (IDbCommand cmd = cn.CreateCommand()) {
|
cmd.CommandText = $"SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name='{tableName}' AND sql NOT NULL;";
|
||||||
cmd.CommandText = $"SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name='{tableName}' AND sql NOT NULL;";
|
using (IDataReader reader = cmd.ExecuteReader()) {
|
||||||
using (IDataReader reader = cmd.ExecuteReader()) {
|
while (reader.Read()) {
|
||||||
while (reader.Read()) {
|
sb.AppendLine(reader.GetString(0) + ";");
|
||||||
sb.AppendLine(reader.GetString(0) + ";");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +313,7 @@ namespace DbTools {
|
|||||||
/// <returns>A string containing the SQL definitions of all triggers for the specified table, separated by semicolons.
|
/// <returns>A string containing the SQL definitions of all triggers for the specified table, separated by semicolons.
|
||||||
/// Returns an empty string if no triggers are found.</returns>
|
/// Returns an empty string if no triggers are found.</returns>
|
||||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cn"/> is null or if <paramref name="tableName"/> is null or empty.</exception>
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cn"/> is null or if <paramref name="tableName"/> is null or empty.</exception>
|
||||||
public async Task<string> GetTriggerCreateSqlAsync(IDbConnection cn, string tableName) {
|
public string GetTriggerCreateSql(IDbConnection cn, string tableName) {
|
||||||
if (cn == null) {
|
if (cn == null) {
|
||||||
throw new ArgumentNullException("cn");
|
throw new ArgumentNullException("cn");
|
||||||
}
|
}
|
||||||
@@ -174,16 +324,14 @@ namespace DbTools {
|
|||||||
cn.Open();
|
cn.Open();
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
await Task.Run(() => {
|
using (IDbCommand cmd = cn.CreateCommand()) {
|
||||||
using (IDbCommand cmd = cn.CreateCommand()) {
|
cmd.CommandText = $"SELECT sql FROM sqlite_master WHERE type='trigger' AND tbl_name='{tableName}' AND sql NOT NULL;";
|
||||||
cmd.CommandText = $"SELECT sql FROM sqlite_master WHERE type='trigger' AND tbl_name='{tableName}' AND sql NOT NULL;";
|
using (IDataReader reader = cmd.ExecuteReader()) {
|
||||||
using (IDataReader reader = cmd.ExecuteReader()) {
|
while (reader.Read()) {
|
||||||
while (reader.Read()) {
|
sb.AppendLine(reader.GetString(0) + ";");
|
||||||
sb.AppendLine(reader.GetString(0) + ";");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user