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.RegularExpressions; using System.Threading.Tasks; namespace DbMigrate { public class SqlDatabase { public string ConnectionString { get; set; } public string SqlScript { get; set; } public List Tables { get; set; } = new List(); public SqlDatabase() { } public SqlDatabase(string connectionString) { ConnectionString = connectionString; } /// /// Loads the specified SQL script and initializes an in-memory SQLite database using the script. /// /// 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 property. /// The SQL script to be executed for creating and populating the database. /// A representing the asynchronous operation. public async Task LoadSql(string sql) { SqlScript = sql; string tempDbFile = Path.GetTempFileName(); ConnectionString = "Data Source=" + tempDbFile + "; Version=3;"; BuildDbFromSql(ConnectionString); await BuildSql(ConnectionString); } /// /// Determines whether a table with the specified name exists in the collection. /// /// The name of the table to search for. The comparison is case-insensitive. /// if a table with the specified name exists; otherwise, . public bool ContainsTable(string tableName) { return Tables.Count(f => f.TableName.ToLower() == tableName.ToLower()) > 0; } /// /// Executes the provided SQL script to build or modify a database using the specified connection string. /// /// This method opens a connection to the database, executes the SQL script defined in /// the SqlScript field, and then closes the connection. Ensure that SqlScript contains valid SQL /// commands appropriate for the database schema. /// The connection string used to establish a connection to the SQLite database. This must be a valid SQLite /// connection string. public void BuildDbFromSql(string dbConnectionString) { using (SQLiteConnection cn = new SQLiteConnection(dbConnectionString)) { ConnectionString = dbConnectionString; using (SQLiteCommand cmd = new SQLiteCommand(cn)) { cmd.CommandText = SqlScript; cmd.ExecuteNonQuery(); } } } /// /// Gets the with the specified table name, or null if no matching table is found. /// /// The name of the table to retrieve. The comparison is case-insensitive. /// public SqlTable this[string tableName] { get { return Tables.FirstOrDefault(f => f.TableName.ToLower() == tableName.ToLower()); } } //public IEnumerable ParseTablesFromSql(string sql) { // SqlTable table = null; // StringBuilder sb = new StringBuilder(); // Dictionary> indexes = new Dictionary>(); // Dictionary> triggers = new Dictionary>(); // 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"); // } // 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(); // } // 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 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 triggerName = matches.Groups[2].Value.Trim(); // string tableName = matches.Groups[3].Value.Trim(); // triggers[tableName].Add(triggerName + ";" + triggerSql); // } // currentElementType = ""; // } // continue; // } // 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 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]; // } // } // } // } //} /// /// Generates a SQL script that recreates the database schema, including tables, indexes, and triggers. /// /// 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. /// Tables are processed in alphabetical order, and the "sqlite_sequence" table is excluded from the /// output. /// The connection string used to connect to the SQLite database. /// A value indicating whether to include "IF NOT EXISTS" clauses in the generated SQL script. Defaults to . /// A representing the asynchronous operation. The task result contains the generated /// SQL script as a string. public async Task BuildSql(string dbConnectionString, bool includeIfNotExist = false) { using (SQLiteConnection cn = new SQLiteConnection(dbConnectionString)) { string sql = ""; IEnumerable TableDefs = await cn.QueryAsync("select * from sqlite_master"); foreach (SqliteTableDefinition table in TableDefs.Where(f => f.type == "table").OrderBy(f => f.tbl_name)) { if (table.tbl_name == "sqlite_sequence") { continue; } Match m = Regex.Match(table.sql, "CREATE TABLE \\S+ \\((.*)\\)", RegexOptions.Singleline); if (!m.Success) { Trace.TraceWarning("Unable to match regex on table " + table.name); continue; } string tableSql = ""; 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"); tableSql += "-- BEGIN TABLE " + table.tbl_name + " --\r\n"; tableSql += table.sql.Substring(0, startIndex) + "\r\n\t" + columns.Trim() + "\r\n" + table.sql.Substring(startIndex + length) + ";\r\n"; List indexes = TableDefs.Where(f => f.type == "index" && f.tbl_name == table.tbl_name && !string.IsNullOrEmpty(f.sql)).ToList(); if (indexes.Count > 0) { tableSql += "\r\n-- INDEXES --\r\n"; foreach (var index in indexes) { if (string.IsNullOrEmpty(index.sql)) { continue; } tableSql += index.sql.Replace(Environment.NewLine, " ") + ";\r\n"; } } List triggers = TableDefs.Where(f => f.type == "trigger" && f.tbl_name == table.tbl_name && !string.IsNullOrEmpty(f.sql)).ToList(); if (triggers.Count > 0) { tableSql += "\r\n-- TRIGGERS --\r\n"; foreach (var trigger in triggers) { if (string.IsNullOrEmpty(trigger.sql)) { continue; } tableSql += trigger.sql.Replace(Environment.NewLine, " ") + ";\r\n"; } } tableSql += "-- END TABLE " + table.tbl_name + " --\r\n\r\n"; Tables.Add(new SqlTable(tableSql)); sql += tableSql; } SqlScript = sql; return sql; } } } }