using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Text; using System.Text.RegularExpressions; namespace DbTools { public class SqlBuilder { /// /// Retrieves the names of all user-defined tables in the connected SQLite database. /// /// System tables (e.g., those starting with "sqlite_") are excluded from the results. /// The method ensures the connection is open before executing the query. /// An open to the SQLite database. The connection must not be null. /// An array of strings containing the names of all user-defined tables in the database. The array will be empty /// if no tables are found. /// Thrown if is . public string[] GetTableNames(IDbConnection cn) { if (cn == null) { throw new ArgumentNullException("cn"); } List tables = new List(); if (cn.State != ConnectionState.Open) { cn.Open(); } using (IDbCommand cmd = cn.CreateCommand()) { cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;"; using (IDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { tables.Add(reader.GetString(0)); } } } return tables.ToArray(); } /// /// Generates the SQL script to recreate the specified table, including its schema, indexes, triggers, and /// optionally its data. /// /// The generated script includes the table's schema, indexes, and triggers. If a /// is provided, the script will also include the data returned by the SELECT /// statement. The method ensures the database connection is open before executing any commands. /// The database connection to use. The connection must be to a SQLite database and can be in any state; the /// method will ensure it is open. /// The name of the table for which to generate the SQL script. Cannot be or empty. /// An optional SQL SELECT statement to retrieve data from the table. If provided, the script will include the /// data as part of the output. /// A string containing the SQL script to recreate the table, including its schema, indexes, triggers, and /// optionally its data. Returns an empty string if the table's schema cannot be determined. /// Thrown if is or if is or empty. public string GetTableCreateSql(IDbConnection cn, string tableName, string selectStatement = null) { 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); } if (!string.IsNullOrEmpty(selectStatement)) { string data = GetTableDataSql(cn, tableName, selectStatement); if (!string.IsNullOrEmpty(data)) { sb.AppendLine(); sb.AppendLine("-- DATA --"); sb.Append(data); } } sb.AppendLine("-- END TABLE " + tableName + " --"); return sb.ToString(); } } } /// /// Retrieves the SQL statements used to create all indexes for the specified table in a SQLite database. /// /// This method queries the SQLite system table sqlite_master to retrieve the SQL /// definitions of indexes associated with the specified table. The connection must be open before executing the /// query; if it is not, the method will attempt to open it. /// The open database connection to use for the query. The connection must be to a SQLite database. /// The name of the table whose index creation SQL statements are to be retrieved. Cannot be null or empty. /// 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. /// Thrown if is null or if is null or empty. public string GetIndexCreateSql(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 type='index' AND tbl_name='{tableName}' AND sql NOT NULL;"; StringBuilder sb = new StringBuilder(); using (IDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { sb.AppendLine(reader.GetString(0) + ";"); } } return sb.ToString(); } } /// /// Retrieves the SQL definition for all triggers associated with the specified table. /// /// This method queries the SQLite system table `sqlite_master` to retrieve the SQL /// definitions of triggers. Ensure that the provided connection is valid and points to a SQLite /// database. /// An open database connection used to query the trigger definitions. The connection must be to a SQLite /// database. /// The name of the table whose triggers are to be retrieved. Cannot be null or empty. /// 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. /// Thrown if is null or if is null or empty. public string GetTriggerCreateSql(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 type='trigger' AND tbl_name='{tableName}' AND sql NOT NULL;"; StringBuilder sb = new StringBuilder(); using (IDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { sb.AppendLine(reader.GetString(0) + ";"); } } return sb.ToString(); } } /// /// Generates an SQL script to insert data from a specified table into another database. /// /// This method generates an SQL script that can be used to insert data into a table. The /// script is constructed based on the data retrieved by the provided SELECT statement. The method ensures that /// string and date values are properly escaped, and null values are represented as NULL in the /// script. /// An open used to execute the query. The connection must be open before calling /// this method. /// The name of the table whose data will be used in the generated SQL script. Cannot be null or empty. /// A SQL SELECT statement that retrieves the data to be included in the script. The placeholder $table /// in the statement will be replaced with the value of . /// A string containing the generated SQL script. The script includes INSERT statements for the data retrieved /// by the . /// Thrown if is null or is null or empty. public string GetTableDataSql(IDbConnection cn, string tableName, string selectStatement) { if (cn == null) { throw new ArgumentNullException("cn"); } if (string.IsNullOrEmpty(tableName)) { throw new ArgumentNullException("tableName"); } if (cn.State != ConnectionState.Open) { cn.Open(); } selectStatement = selectStatement.Replace("$table", tableName); using (IDbCommand cmd = cn.CreateCommand()) { cmd.CommandText = selectStatement; using (IDataReader reader = cmd.ExecuteReader()) { StringBuilder sb = new StringBuilder(); int rowCount = 0; while (reader.Read()) { List values = new List(); for (int i = 0; i < reader.FieldCount; i++) { object value = DBNull.Value; try { value = reader.GetValue(i); } catch { } if (value == DBNull.Value) { values.Add("NULL"); } else if (value is string || value is DateTime) { values.Add("'" + value.ToString().Replace("'", "''") + "'"); } else if (value is bool) { values.Add((bool)value ? "1" : "0"); } else { values.Add(value.ToString()); } } if (rowCount % 100 == 0) { if (rowCount != 0) sb.AppendLine(";"); sb.Append($"INSERT INTO {tableName} ({string.Join(", ", GetColumnNames(reader))}) VALUES"); sb.AppendLine(); sb.Append($" ({string.Join(", ", values)})"); } else { sb.AppendLine(","); sb.Append($" ({string.Join(", ", values)})"); } rowCount++; } if (rowCount > 0) sb.AppendLine(";"); return sb.ToString(); } } } /// /// Retrieves the names of all columns from the specified . /// /// The instance from which to retrieve column names. Must not be . /// An enumerable collection of strings representing the names of all columns in the data reader. private IEnumerable GetColumnNames(IDataReader reader) { for (int i = 0; i < reader.FieldCount; i++) { yield return reader.GetName(i); } } } }