Updates
This commit is contained in:
289
SqlDatabase.cs
289
SqlDatabase.cs
@@ -1,11 +1,11 @@
|
||||
using Dapper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -16,161 +16,199 @@ namespace DbMigrate {
|
||||
public List<SqlTable> Tables { get; set; } = new List<SqlTable>();
|
||||
|
||||
|
||||
public SqlDatabase() {
|
||||
public SqlDatabase() { }
|
||||
|
||||
}
|
||||
|
||||
public void Connect(string connectionString) {
|
||||
public SqlDatabase(string connectionString) {
|
||||
ConnectionString = connectionString;
|
||||
|
||||
}
|
||||
|
||||
public void LoadSql(string sql) {
|
||||
/// <summary>
|
||||
/// Loads the specified SQL script and initializes an in-memory SQLite database using the script.
|
||||
/// </summary>
|
||||
/// <remarks>This method creates a temporary SQLite database file, sets up a connection string,
|
||||
/// and executes the provided SQL script to initialize the database. The connection string for the database is
|
||||
/// stored in the <see cref="ConnectionString"/> property.</remarks>
|
||||
/// <param name="sql">The SQL script to be executed for creating and populating the database.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task LoadSql(string sql) {
|
||||
SqlScript = sql;
|
||||
Tables = ParseTablesFromSql(sql).ToList();
|
||||
string tempDbFile = Path.GetTempFileName();
|
||||
ConnectionString = "Data Source=" + tempDbFile + "; Version=3;";
|
||||
BuildDbFromSql(ConnectionString);
|
||||
await BuildSql(ConnectionString);
|
||||
}
|
||||
|
||||
public void LoadSqlFromFile(string fileName) {
|
||||
if (!File.Exists(fileName)) {
|
||||
throw new FileNotFoundException("SQL file '" + fileName + "' was not found and could not be loaded.");
|
||||
}
|
||||
|
||||
string sql = File.ReadAllText(fileName);
|
||||
LoadSql(sql);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a table with the specified name exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="tableName">The name of the table to search for. The comparison is case-insensitive.</param>
|
||||
/// <returns><see langword="true"/> if a table with the specified name exists; otherwise, <see langword="false"/>.</returns>
|
||||
public bool ContainsTable(string tableName) {
|
||||
return Tables.Count(f => f.TableName.ToLower() == tableName.ToLower()) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the provided SQL script to build or modify a database using the specified connection string.
|
||||
/// </summary>
|
||||
/// <remarks>This method opens a connection to the database, executes the SQL script defined in
|
||||
/// the <c>SqlScript</c> field, and then closes the connection. Ensure that <c>SqlScript</c> contains valid SQL
|
||||
/// commands appropriate for the database schema.</remarks>
|
||||
/// <param name="dbConnectionString">The connection string used to establish a connection to the SQLite database. This must be a valid SQLite
|
||||
/// connection string.</param>
|
||||
public void BuildDbFromSql(string dbConnectionString) {
|
||||
using (SQLiteConnection cn = new SQLiteConnection(dbConnectionString)) {
|
||||
ConnectionString = dbConnectionString;
|
||||
using (SQLiteCommand cmd = new SQLiteCommand(cn)) {
|
||||
cmd.CommandText = SqlScript;
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="SqlTable"/> with the specified table name, or <c>null</c> if no matching table is found.
|
||||
/// </summary>
|
||||
/// <param name="tableName">The name of the table to retrieve. The comparison is case-insensitive.</param>
|
||||
/// <returns></returns>
|
||||
public SqlTable this[string tableName] {
|
||||
get {
|
||||
return Tables.FirstOrDefault(f => f.TableName.ToLower() == tableName.ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<SqlTable> ParseTablesFromSql(string sql) {
|
||||
SqlTable table = null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
//public IEnumerable<SqlTable> ParseTablesFromSql(string sql) {
|
||||
// SqlTable table = null;
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
|
||||
|
||||
Dictionary<string, List<string>> indexes = new Dictionary<string, List<string>>();
|
||||
Dictionary<string, List<string>> triggers = new Dictionary<string, List<string>>();
|
||||
// Dictionary<string, List<string>> indexes = new Dictionary<string, List<string>>();
|
||||
// Dictionary<string, List<string>> triggers = new Dictionary<string, List<string>>();
|
||||
|
||||
string currentElementType = "";
|
||||
foreach (string line in Regex.Split(sql, "\\r\\n")) {
|
||||
if (string.IsNullOrEmpty(line) || line.StartsWith("--")) {
|
||||
continue;
|
||||
}
|
||||
// string currentElementType = "";
|
||||
// foreach (string line in Regex.Split(sql, "\\r\\n")) {
|
||||
// if (string.IsNullOrEmpty(line) || line.StartsWith("--")) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
string trimmedLine = line;
|
||||
if (currentElementType == "trigger" || currentElementType == "index") {
|
||||
trimmedLine = line.Trim();
|
||||
} else {
|
||||
trimmedLine = line.StartsWith(" ") ? "&&" + line.Trim() : line;
|
||||
trimmedLine = Regex.Replace(trimmedLine, @"\s+", " ");
|
||||
trimmedLine = trimmedLine.Replace("&&", "\t");
|
||||
}
|
||||
// string trimmedLine = line;
|
||||
// if (currentElementType == "trigger" || currentElementType == "index") {
|
||||
// trimmedLine = line.Trim();
|
||||
// } else {
|
||||
// trimmedLine = line.StartsWith(" ") ? "&&" + line.Trim() : line;
|
||||
// trimmedLine = Regex.Replace(trimmedLine, @"\s+", " ");
|
||||
// trimmedLine = trimmedLine.Replace("&&", "\t");
|
||||
// }
|
||||
|
||||
|
||||
if (trimmedLine.ToUpper().StartsWith("CREATE TABLE ")) {
|
||||
// Start a new table
|
||||
table = new SqlTable();
|
||||
sb = new StringBuilder();
|
||||
sb.AppendLine(trimmedLine);
|
||||
currentElementType = "table";
|
||||
continue;
|
||||
}
|
||||
// if (trimmedLine.ToUpper().StartsWith("CREATE TABLE ")) {
|
||||
// // Start a new table
|
||||
// table = new SqlTable();
|
||||
// sb = new StringBuilder();
|
||||
// sb.AppendLine(trimmedLine);
|
||||
// currentElementType = "table";
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (trimmedLine.ToUpper().StartsWith("CREATE INDEX ")) {
|
||||
var matches = Regex.Match(trimmedLine, "CREATE INDEX( IF NOT EXISTS)? (\\w*) ON (\\w*)");
|
||||
if (matches.Success) {
|
||||
sb = new StringBuilder();
|
||||
sb.AppendLine(trimmedLine);
|
||||
currentElementType = "index";
|
||||
if (!indexes.ContainsKey(matches.Groups[2].Value.Trim())) {
|
||||
indexes[matches.Groups[2].Value.Trim()] = new List<string>();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// if (trimmedLine.ToUpper().StartsWith("CREATE INDEX ")) {
|
||||
// var matches = Regex.Match(trimmedLine, "CREATE INDEX( IF NOT EXISTS)? (\\w*) ON (\\w*)");
|
||||
// if (matches.Success) {
|
||||
// sb = new StringBuilder();
|
||||
// sb.AppendLine(trimmedLine);
|
||||
// currentElementType = "index";
|
||||
// if (!indexes.ContainsKey(matches.Groups[2].Value.Trim())) {
|
||||
// indexes[matches.Groups[2].Value.Trim()] = new List<string>();
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (trimmedLine.ToUpper().StartsWith("CREATE TRIGGER ")) {
|
||||
sb = new StringBuilder();
|
||||
sb.AppendLine(trimmedLine);
|
||||
currentElementType = "trigger";
|
||||
continue;
|
||||
}
|
||||
// if (trimmedLine.ToUpper().StartsWith("CREATE TRIGGER ")) {
|
||||
// sb = new StringBuilder();
|
||||
// sb.AppendLine(trimmedLine);
|
||||
// currentElementType = "trigger";
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// The element has concluded (may occur on the same line as above)
|
||||
if (trimmedLine.EndsWith(");") || trimmedLine.EndsWith("END;")) {
|
||||
sb.AppendLine(trimmedLine);
|
||||
if (currentElementType == "table" && table != null) {
|
||||
SqlScript += sb.ToString() + Environment.NewLine + Environment.NewLine;
|
||||
table.ParseSql(sb.ToString());
|
||||
Tables.Add(table);
|
||||
currentElementType = "";
|
||||
yield return table;
|
||||
} else if (currentElementType == "index") {
|
||||
string indexSql = sb.ToString().Replace(Environment.NewLine, " ");
|
||||
SqlScript += indexSql + Environment.NewLine + Environment.NewLine;
|
||||
var matches = Regex.Match(indexSql, "CREATE INDEX( IF NOT EXISTS)? (\\w*) ON (\\w*)");
|
||||
if (matches.Success) {
|
||||
if (!indexes.ContainsKey(matches.Groups[3].Value.Trim())) {
|
||||
indexes[matches.Groups[3].Value.Trim()] = new List<string>();
|
||||
}
|
||||
string indexName = matches.Groups[2].Value.Trim();
|
||||
string tableName = matches.Groups[3].Value.Trim();
|
||||
indexes[tableName].Add(indexName + ";" + indexSql);
|
||||
}
|
||||
currentElementType = "";
|
||||
} else if (currentElementType == "trigger") {
|
||||
string triggerSql = sb.ToString().Replace(Environment.NewLine, " ");
|
||||
SqlScript += triggerSql + Environment.NewLine + Environment.NewLine;
|
||||
var matches = Regex.Match(triggerSql, "CREATE TRIGGER( IF NOT EXISTS)? (\\w*) ON (\\w*)");
|
||||
if (matches.Success) {
|
||||
if (!triggers.ContainsKey(matches.Groups[3].Value.Trim())) {
|
||||
triggers[matches.Groups[3].Value.Trim()] = new List<string>();
|
||||
}
|
||||
string triggerName = matches.Groups[2].Value.Trim();
|
||||
string tableName = matches.Groups[3].Value.Trim();
|
||||
triggers[tableName].Add(triggerName + ";" + triggerSql);
|
||||
}
|
||||
currentElementType = "";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// // The element has concluded (may occur on the same line as above)
|
||||
// if (trimmedLine.EndsWith(");") || trimmedLine.EndsWith("END;")) {
|
||||
// sb.AppendLine(trimmedLine);
|
||||
// if (currentElementType == "table" && table != null) {
|
||||
// SqlScript += sb.ToString() + Environment.NewLine + Environment.NewLine;
|
||||
// table.ParseSql(sb.ToString());
|
||||
// Tables.Add(table);
|
||||
// currentElementType = "";
|
||||
// yield return table;
|
||||
// } else if (currentElementType == "index") {
|
||||
// string indexSql = sb.ToString().Replace(Environment.NewLine, " ");
|
||||
// SqlScript += indexSql + Environment.NewLine + Environment.NewLine;
|
||||
// var matches = Regex.Match(indexSql, "CREATE INDEX( IF NOT EXISTS)? (\\w*) ON (\\w*)");
|
||||
// if (matches.Success) {
|
||||
// if (!indexes.ContainsKey(matches.Groups[3].Value.Trim())) {
|
||||
// indexes[matches.Groups[3].Value.Trim()] = new List<string>();
|
||||
// }
|
||||
// string indexName = matches.Groups[2].Value.Trim();
|
||||
// string tableName = matches.Groups[3].Value.Trim();
|
||||
// indexes[tableName].Add(indexName + ";" + indexSql);
|
||||
// }
|
||||
// currentElementType = "";
|
||||
// } else if (currentElementType == "trigger") {
|
||||
// string triggerSql = sb.ToString().Replace(Environment.NewLine, " ");
|
||||
// SqlScript += triggerSql + Environment.NewLine + Environment.NewLine;
|
||||
// var matches = Regex.Match(triggerSql, "CREATE TRIGGER( IF NOT EXISTS)? (\\w*) ON (\\w*)");
|
||||
// if (matches.Success) {
|
||||
// if (!triggers.ContainsKey(matches.Groups[3].Value.Trim())) {
|
||||
// triggers[matches.Groups[3].Value.Trim()] = new List<string>();
|
||||
// }
|
||||
// string triggerName = matches.Groups[2].Value.Trim();
|
||||
// string tableName = matches.Groups[3].Value.Trim();
|
||||
// triggers[tableName].Add(triggerName + ";" + triggerSql);
|
||||
// }
|
||||
// currentElementType = "";
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
|
||||
sb.AppendLine(trimmedLine);
|
||||
}
|
||||
// sb.AppendLine(trimmedLine);
|
||||
// }
|
||||
|
||||
foreach (string index in indexes.Keys) {
|
||||
table = Tables.FirstOrDefault(t => t.TableName == index);
|
||||
if (table != null) {
|
||||
foreach (string indexSql in indexes[index]) {
|
||||
var parts = indexSql.Split(new char[] { ';' }, 2);
|
||||
if (parts.Length == 2) {
|
||||
table.Indexes[parts[0]] = parts[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// foreach (string index in indexes.Keys) {
|
||||
// table = Tables.FirstOrDefault(t => t.TableName == index);
|
||||
// if (table != null) {
|
||||
// foreach (string indexSql in indexes[index]) {
|
||||
// var parts = indexSql.Split(new char[] { ';' }, 2);
|
||||
// if (parts.Length == 2) {
|
||||
// table.Indexes[parts[0]] = parts[1];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
foreach (string trigger in triggers.Keys) {
|
||||
table = Tables.FirstOrDefault(t => t.TableName == trigger);
|
||||
if (table != null) {
|
||||
foreach (string triggerSql in triggers[trigger]) {
|
||||
var parts = triggerSql.Split(new char[] { ';' }, 2);
|
||||
if (parts.Length == 2) {
|
||||
table.Triggers[parts[0]] = parts[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// foreach (string trigger in triggers.Keys) {
|
||||
// table = Tables.FirstOrDefault(t => t.TableName == trigger);
|
||||
// if (table != null) {
|
||||
// foreach (string triggerSql in triggers[trigger]) {
|
||||
// var parts = triggerSql.Split(new char[] { ';' }, 2);
|
||||
// if (parts.Length == 2) {
|
||||
// table.Triggers[parts[0]] = parts[1];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a SQL script that recreates the database schema, including tables, indexes, and triggers.
|
||||
/// </summary>
|
||||
/// <remarks>This method queries the SQLite database schema to retrieve definitions for tables,
|
||||
/// indexes, and triggers. It then constructs a SQL script that can be used to recreate the schema. The script
|
||||
/// includes detailed CREATE statements for each table, along with associated indexes and triggers, if any.
|
||||
/// <para> Tables are processed in alphabetical order, and the "sqlite_sequence" table is excluded from the
|
||||
/// output. </para></remarks>
|
||||
/// <param name="dbConnectionString">The connection string used to connect to the SQLite database.</param>
|
||||
/// <param name="includeIfNotExist">A value indicating whether to include "IF NOT EXISTS" clauses in the generated SQL script. Defaults to <see
|
||||
/// langword="false"/>.</param>
|
||||
/// <returns>A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the generated
|
||||
/// SQL script as a string.</returns>
|
||||
public async Task<string> BuildSql(string dbConnectionString, bool includeIfNotExist = false) {
|
||||
using (SQLiteConnection cn = new SQLiteConnection(dbConnectionString)) {
|
||||
string sql = "";
|
||||
@@ -221,6 +259,7 @@ namespace DbMigrate {
|
||||
sql += tableSql;
|
||||
}
|
||||
|
||||
SqlScript = sql;
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user