using DbTools.Model; using System; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace DbTools { internal static class TableExtensions { /// /// Retrieves the column names that are common between the current table and the specified table. /// /// The source table from which to retrieve column names. /// The table to compare against for common column names. /// An array of strings containing the names of columns that exist in both tables. The array will be empty if no /// common columns are found. public static string[] GetCommonColumns(this Table table1, Table table2) { return table1.GetColumnNames().Where(f => table2.HasColumn(f)).ToArray(); } /// /// Parses the provided SQL script to extract and populate table metadata, including the table name, columns, /// indexes, and triggers. /// /// This method processes the SQL script line by line to identify and extract the table /// definition, column definitions, indexes, and triggers. - If is specified, only /// metadata for the specified table is extracted. - If is not specified, the table /// name is inferred from the first `CREATE TABLE` statement in the script. Lines starting with `--` are /// ignored as comments. The method stops processing once the metadata for the specified or inferred table is /// fully extracted. /// The instance to populate with metadata extracted from the SQL script. /// The SQL script to parse. The script should define a table and optionally include indexes and triggers. /// An optional name of the table to extract from the SQL script. If not provided, the table name will be /// inferred from the SQL script. public static void ParseSql(this Table table, string sql, string tableName = null) { if (tableName != null) { table.TableName = tableName; } bool inTable = false; bool inColumns = false; Match m = null; foreach (string line in Regex.Split(sql, "\\r\\n")) { if (string.IsNullOrEmpty(line) || line.StartsWith("--")) { continue; } m = Regex.Match(line, "^CREATE TABLE( IF NOT EXISTS)? (\\S+) "); if (m.Success) { if (string.IsNullOrEmpty(tableName) && string.IsNullOrEmpty(table.TableName)) { // Set table name from the above regex match table.TableName = m.Groups[2].Value.Trim(); } if (!inTable && (table.TableName == null || m.Groups[2].Value.Trim() == table.TableName)) { table.CreateTableSql += line + Environment.NewLine; inTable = true; inColumns = true; continue; } else { if (inTable) { // We are done with this table return; } continue; } } m = Regex.Match(line, "^CREATE INDEX( IF NOT EXISTS)? (\\S+) "); if (m.Success && inTable) { table.Indexes.Add(m.Groups[2].Value.Trim(), line.Trim()); continue; } m = Regex.Match(line, "^CREATE TRIGGER( IF NOT EXISTS)? (\\S+) "); if (m.Success && inTable) { table.Triggers.Add(m.Groups[2].Value.Trim(), line.Trim()); continue; } if (inColumns) { if (line.Trim().StartsWith(")")) { inColumns = false; } else { m = Regex.Match(line.Trim(), "^(\\S+).*"); if (m.Success) { string columnLine = line.Trim(); if (columnLine.EndsWith(",")) { columnLine = columnLine.Substring(0, columnLine.Length - 1); } table.Columns.Add(m.Groups[1].Value.Trim(), columnLine); } } } table.CreateTableSql += line + Environment.NewLine; } } public static string GenerateDropTable(this Table table) { StringBuilder sb = new StringBuilder(); if (table.HasTriggers()) { foreach (var trigger in table.Triggers.Keys) { sb.AppendLine($"DROP TRIGGER IF EXISTS {trigger};"); } } if (table.HasIndexes()) { foreach (var index in table.Indexes.Keys) { sb.AppendLine($"DROP INDEX IF EXISTS {index};"); } } sb.AppendLine($"DROP TABLE IF EXISTS {table.TableName};"); return sb.ToString(); } public static string GenerateTableMigration(this Table newTable, Table oldTable) { StringBuilder sb = new StringBuilder(); string tableName = newTable.TableName; sb.AppendLine("\r\n-- Table " + tableName + " requires alteration - Create temp table and move data"); string[] commonColumns = newTable.GetCommonColumns(oldTable); string columnList = string.Join(",", commonColumns); string sql = newTable.CreateTableSql; string newTableName = tableName + "_" + DateTime.Now.ToString("yyyyMMddHHmmss"); sql = Regex.Replace(sql, "CREATE TABLE (\\w*)", "CREATE TABLE IF NOT EXISTS $1_" + newTableName.Substring(newTableName.IndexOf("_") + 1)); sb.AppendLine(sql); sb.AppendLine("\r\n-- Copy data from existing table to new table"); sb.AppendLine("INSERT INTO " + newTableName + " (" + columnList + ")"); sb.AppendLine("\tSELECT " + columnList); sb.AppendLine("\tFROM " + tableName + ";"); sb.AppendLine("\r\n-- Drop existing table"); sb.AppendLine("DROP TABLE " + tableName + ";"); sb.AppendLine("\r\n-- Rename the new table to replace the old table"); sb.AppendLine("ALTER TABLE " + newTableName + " RENAME TO " + tableName + ";"); return sb.ToString(); } } }