using System; using System.Collections.Generic; using System.Data; using Migrator.Framework; using ForeignKeyConstraint=Migrator.Framework.ForeignKeyConstraint; #if DOTNET2 using SqliteConnection=System.Data.SQLite.SQLiteConnection; #else using Mono.Data.Sqlite; #endif namespace Migrator.Providers.SQLite { /// /// Summary description for SQLiteTransformationProvider. /// public class SQLiteTransformationProvider : TransformationProvider { public SQLiteTransformationProvider(Dialect dialect, string connectionString) : base(dialect, connectionString) { _connection = new SqliteConnection(_connectionString); _connection.ConnectionString = _connectionString; _connection.Open(); } public override void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, string[] refColumns, ForeignKeyConstraint constraint) { // NOOP Because SQLite doesn't support foreign keys } public override void RemoveForeignKey(string name, string table) { // NOOP Because SQLite doesn't support foreign keys } public override void RemoveColumn(string table, string column) { if (! (TableExists(table) && ColumnExists(table, column))) return; string[] origColDefs = GetColumnDefs(table); List colDefs = new List(); foreach (string origdef in origColDefs) { if (! ColumnMatch(column, origdef)) colDefs.Add(origdef); } string[] newColDefs = colDefs.ToArray(); string colDefsSql = String.Join(",", newColDefs); string[] colNames = ParseSqlForColumnNames(newColDefs); string colNamesSql = String.Join(",", colNames); AddTable(table + "_temp", null, colDefsSql); ExecuteQuery(String.Format("INSERT INTO {0}_temp SELECT {1} FROM {0}", table, colNamesSql)); RemoveTable(table); ExecuteQuery(String.Format("ALTER TABLE {0}_temp RENAME TO {0}", table)); } public override void RenameColumn(string tableName, string oldColumnName, string newColumnName) { if (ColumnExists(tableName, newColumnName)) throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName)); if (ColumnExists(tableName, oldColumnName)) { string[] columnDefs = GetColumnDefs(tableName); string columnDef = Array.Find(columnDefs, delegate(string col) { return ColumnMatch(oldColumnName, col); }); string newColumnDef = columnDef.Replace(oldColumnName, newColumnName); AddColumn(tableName, newColumnDef); ExecuteQuery(String.Format("UPDATE {0} SET {1}={2}", tableName, newColumnName, oldColumnName)); RemoveColumn(tableName, oldColumnName); } } public override void ChangeColumn(string table, Column column) { if (! ColumnExists(table, column.Name)) { Logger.Warn("Column {0}.{1} does not exist", table, column.Name); return; } string tempColumn = "temp_" + column.Name; RenameColumn(table, column.Name, tempColumn); AddColumn(table, column); ExecuteQuery(String.Format("UPDATE {0} SET {1}={2}", table, column.Name, tempColumn)); RemoveColumn(table, tempColumn); } public override bool TableExists(string table) { using (IDataReader reader = ExecuteQuery(String.Format("SELECT name FROM sqlite_master WHERE type='table' and name='{0}'",table))) { return reader.Read(); } } public override bool ConstraintExists(string table, string name) { return false; } public override string[] GetTables() { List tables = new List(); using (IDataReader reader = ExecuteQuery("SELECT name FROM sqlite_master WHERE type='table' AND name <> 'sqlite_sequence' ORDER BY name")) { while (reader.Read()) { tables.Add((string) reader[0]); } } return tables.ToArray(); } public override Column[] GetColumns(string table) { List columns = new List(); foreach (string columnDef in GetColumnDefs(table)) { string name = ExtractNameFromColumnDef(columnDef); // FIXME: Need to get the real type information Column column = new Column(name, DbType.String); bool isNullable = IsNullable(columnDef); column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; columns.Add(column); } return columns.ToArray(); } public string GetSqlDefString(string table) { string sqldef = null; using (IDataReader reader = ExecuteQuery(String.Format("SELECT sql FROM sqlite_master WHERE type='table' AND name='{0}'",table))) { if (reader.Read()) { sqldef = (string) reader[0]; } } return sqldef; } public string[] GetColumnNames(string table) { return ParseSqlForColumnNames(GetSqlDefString(table)); } public string[] GetColumnDefs(string table) { return ParseSqlColumnDefs(GetSqlDefString(table)); } /// /// Turn something like 'columnName INTEGER NOT NULL' into just 'columnName' /// public string[] ParseSqlForColumnNames(string sqldef) { string[] parts = ParseSqlColumnDefs(sqldef); return ParseSqlForColumnNames(parts); } public string[] ParseSqlForColumnNames(string[] parts) { if (null == parts) return null; for (int i = 0; i < parts.Length; i ++) { parts[i] = ExtractNameFromColumnDef(parts[i]); } return parts; } /// /// Name is the first value before the space. /// /// /// public string ExtractNameFromColumnDef(string columnDef) { int idx = columnDef.IndexOf(" "); if (idx > 0) { return columnDef.Substring(0, idx); } return null; } public bool IsNullable(string columnDef) { return ! columnDef.Contains("NOT NULL"); } public string[] ParseSqlColumnDefs(string sqldef) { if (String.IsNullOrEmpty(sqldef)) { return null; } sqldef = sqldef.Replace(Environment.NewLine, " "); int start = sqldef.IndexOf("("); int end = sqldef.LastIndexOf(")"); sqldef = sqldef.Substring(0, end); sqldef = sqldef.Substring(start + 1); string[] cols = sqldef.Split(new char[]{','}); for (int i = 0; i < cols.Length; i ++) { cols[i] = cols[i].Trim(); } return cols; } public bool ColumnMatch(string column, string columnDef) { return columnDef.StartsWith(column + " ") || columnDef.StartsWith(_dialect.Quote(column)); } } }