aboutsummaryrefslogtreecommitdiff
path: root/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations
diff options
context:
space:
mode:
Diffstat (limited to 'dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations')
-rw-r--r--dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/DatabaseMigrator.cs44
-rw-r--r--dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/SqliteDatabaseMigrator.cs175
2 files changed, 219 insertions, 0 deletions
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/DatabaseMigrator.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/DatabaseMigrator.cs
new file mode 100644
index 0000000..f1ae616
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/DatabaseMigrator.cs
@@ -0,0 +1,44 @@
+using System.Data;
+
+namespace CrupestApi.Commons.Crud.Migrations;
+
+public class TableColumn
+{
+ public TableColumn(string name, string type, bool notNull, int primaryKey)
+ {
+ Name = name;
+ Type = type;
+ NotNull = notNull;
+ PrimaryKey = primaryKey;
+ }
+
+ public string Name { get; set; }
+ public string Type { get; set; }
+ public bool NotNull { get; set; }
+
+ /// <summary>
+ /// 0 if not primary key. 1-based index if in primary key.
+ /// </summary>
+ public int PrimaryKey { get; set; }
+}
+
+public class Table
+{
+ public Table(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; set; }
+ public List<TableColumn> Columns { get; set; } = new List<TableColumn>();
+}
+
+public interface IDatabaseMigrator
+{
+ Table? GetTable(IDbConnection dbConnection, string tableName);
+ Table ConvertTableInfoToTable(TableInfo tableInfo);
+ string GenerateCreateTableColumnSqlSegment(TableColumn column);
+ string GenerateCreateTableSql(string tableName, IEnumerable<TableColumn> columns);
+ bool NeedMigrate(IDbConnection dbConnection, TableInfo tableInfo);
+ void AutoMigrate(IDbConnection dbConnection, TableInfo tableInfo);
+}
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/SqliteDatabaseMigrator.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/SqliteDatabaseMigrator.cs
new file mode 100644
index 0000000..33310d6
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/Migrations/SqliteDatabaseMigrator.cs
@@ -0,0 +1,175 @@
+using System.Data;
+using System.Text;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using Dapper;
+
+namespace CrupestApi.Commons.Crud.Migrations;
+
+public class SqliteDatabaseMigrator : IDatabaseMigrator
+{
+ private void CheckTableName(string name)
+ {
+ if (Regex.Match(name, @"^[_0-9a-zA-Z]+$").Success is false)
+ {
+ throw new ArgumentException("Fxxk, what have you passed as table name.");
+ }
+ }
+
+ public Table? GetTable(IDbConnection dbConnection, string tableName)
+ {
+ var count = dbConnection.QuerySingle<int>(
+ "SELECT count(*) FROM sqlite_schema WHERE type = 'table' AND name = @TableName;",
+ new { TableName = tableName });
+ if (count == 0)
+ {
+ return null;
+ }
+ else if (count > 1)
+ {
+ throw new Exception($"More than 1 table has name {tableName}. What happened?");
+ }
+ else
+ {
+ var table = new Table(tableName);
+ var queryColumns = dbConnection.Query<dynamic>($"PRAGMA table_info({tableName})");
+
+ foreach (var column in queryColumns)
+ {
+ var columnName = (string)column.name;
+ var columnType = (string)column.type;
+ var isNullable = Convert.ToBoolean(column.notnull);
+ var primaryKey = Convert.ToInt32(column.pk);
+
+ table.Columns.Add(new TableColumn(columnName, columnType, isNullable, primaryKey));
+ }
+
+ return table;
+ }
+ }
+
+ public Table ConvertTableInfoToTable(TableInfo tableInfo)
+ {
+ var table = new Table(tableInfo.TableName);
+
+ foreach (var columnInfo in tableInfo.Columns)
+ {
+ table.Columns.Add(new TableColumn(columnInfo.ColumnName, columnInfo.ColumnType.GetSqlTypeString(),
+ columnInfo.IsNotNull, columnInfo.IsPrimaryKey ? 1 : 0));
+ }
+
+ return table;
+ }
+
+ public string GenerateCreateTableColumnSqlSegment(TableColumn column)
+ {
+ StringBuilder result = new StringBuilder();
+ result.Append(column.Name);
+ result.Append(' ');
+ result.Append(column.Type);
+ if (column.PrimaryKey is not 0)
+ {
+ result.Append(" PRIMARY KEY AUTOINCREMENT");
+ }
+ else if (column.NotNull)
+ {
+ result.Append(" NOT NULL");
+ }
+
+ return result.ToString();
+ }
+
+ public string GenerateCreateTableSql(string tableName, IEnumerable<TableColumn> columns)
+ {
+ CheckTableName(tableName);
+
+ var sql = $@"
+CREATE TABLE {tableName} (
+ {string.Join(",\n ", columns.Select(GenerateCreateTableColumnSqlSegment))}
+);
+ ".Trim();
+
+ return sql;
+
+ }
+
+ public void AutoMigrate(IDbConnection dbConnection, TableInfo tableInfo)
+ {
+ var tableName = tableInfo.TableName;
+ var databaseTable = GetTable(dbConnection, tableName);
+ var wantedTable = ConvertTableInfoToTable(tableInfo);
+ var databaseTableColumnNames = databaseTable is null ? new List<string>() : databaseTable.Columns.Select(column => column.Name).ToList();
+ var wantedTableColumnNames = wantedTable.Columns.Select(column => column.Name).ToList();
+
+ var notChangeColumns = wantedTableColumnNames.Where(column => databaseTableColumnNames.Contains(column)).ToList();
+ var addColumns = wantedTableColumnNames.Where(column => !databaseTableColumnNames.Contains(column)).ToList();
+
+ if (databaseTable is not null && dbConnection.QuerySingle<int>($"SELECT count(*) FROM {tableName}") > 0)
+ {
+ foreach (var columnName in addColumns)
+ {
+ var columnInfo = tableInfo.GetColumn(columnName);
+ if (!columnInfo.CanBeGenerated)
+ {
+ throw new Exception($"Column {columnName} cannot be generated. So we can't auto-migrate.");
+ }
+ }
+ }
+
+ // We are sqlite, so it's a little bit difficult.
+ using var transaction = dbConnection.BeginTransaction();
+
+ if (databaseTable is not null)
+ {
+ var tempTableName = tableInfo.TableName + "_temp";
+ dbConnection.Execute($"ALTER TABLE {tableName} RENAME TO {tempTableName}", new { TableName = tableName, tempTableName });
+
+ var createTableSql = GenerateCreateTableSql(tableName, wantedTable.Columns);
+ dbConnection.Execute(createTableSql);
+
+ // Copy old data to new table.
+ var originalRows = dbConnection.Query<dynamic>($"SELECT * FROM {tempTableName}").Cast<IDictionary<string, object?>>().ToList();
+ foreach (var originalRow in originalRows)
+ {
+ var parameters = new DynamicParameters();
+
+ foreach (var columnName in notChangeColumns)
+ {
+ parameters.Add(columnName, originalRow[columnName]);
+ }
+
+ foreach (var columnName in addColumns)
+ {
+ parameters.Add(columnName, tableInfo.GetColumn(columnName).GenerateDefaultValue());
+ }
+
+ string columnSql = string.Join(", ", wantedTableColumnNames);
+ string valuesSql = string.Join(", ", wantedTableColumnNames.Select(c => "@" + c));
+
+ string sql = $"INSERT INTO {tableName} ({columnSql}) VALUES {valuesSql})";
+ dbConnection.Execute(sql, parameters);
+ }
+
+ // Finally drop old table
+ dbConnection.Execute($"DROP TABLE {tempTableName}");
+ }
+ else
+ {
+ var createTableSql = GenerateCreateTableSql(tableName, wantedTable.Columns);
+ dbConnection.Execute(createTableSql);
+ }
+
+ // Commit transaction.
+ transaction.Commit();
+ }
+
+ public bool NeedMigrate(IDbConnection dbConnection, TableInfo tableInfo)
+ {
+ var tableName = tableInfo.TableName;
+ var databaseTable = GetTable(dbConnection, tableName);
+ var wantedTable = ConvertTableInfoToTable(tableInfo);
+ var databaseTableColumns = databaseTable is null ? new HashSet<string>() : new HashSet<string>(databaseTable.Columns.Select(c => c.Name));
+ var wantedTableColumns = new HashSet<string>(wantedTable.Columns.Select(c => c.Name));
+ return !databaseTableColumns.SetEquals(wantedTableColumns);
+ }
+}