Export: Implement exporting and importing of sqlite3 and sql files
All checks were successful
Test / Run tests (push) Successful in 2m15s
All checks were successful
Test / Run tests (push) Successful in 2m15s
This commit is contained in:
188
Elwig/Helpers/Export/Database.cs
Normal file
188
Elwig/Helpers/Export/Database.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public static class Database {
|
||||
|
||||
public static async Task ExportSqlite(string filename, bool zipFile) {
|
||||
if (zipFile) {
|
||||
File.Delete(filename);
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
|
||||
await zip.CheckIntegrity();
|
||||
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
|
||||
} else {
|
||||
File.Copy(App.Config.DatabaseFile, filename, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExportSql(string filename, bool zipFile) {
|
||||
if (zipFile) {
|
||||
File.Delete(filename);
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
|
||||
var entry = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
|
||||
using var stream = entry.Open();
|
||||
using var writer = new StreamWriter(stream, Utils.UTF8);
|
||||
await ExportSql(writer);
|
||||
} else {
|
||||
using var stream = File.OpenWrite(filename);
|
||||
using var writer = new StreamWriter(stream, Utils.UTF8);
|
||||
await ExportSql(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExportSql(StreamWriter writer) {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
|
||||
var tables = new List<(string Name, string Sql)>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = "SELECT name, sql FROM sqlite_schema WHERE type = 'table'";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync()) {
|
||||
tables.Add((reader.GetString(0), reader.GetString(1)));
|
||||
}
|
||||
}
|
||||
|
||||
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
|
||||
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
|
||||
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0;
|
||||
|
||||
await writer.WriteLineAsync($"-- Elwig database dump, {DateTime.Now:yyyy-MM-dd, HH:mm:ss}");
|
||||
await writer.WriteLineAsync($"-- {Environment.MachineName}, Zwst. {App.BranchName}, {App.Client.Name}");
|
||||
await writer.WriteLineAsync("BEGIN TRANSACTION;");
|
||||
await writer.WriteLineAsync("PRAGMA foreign_keys=OFF;");
|
||||
await writer.WriteLineAsync($"PRAGMA application_id=0x{applId:X8};");
|
||||
await writer.WriteLineAsync($"PRAGMA user_version=0x{userVers:X8};");
|
||||
|
||||
foreach (var t in tables) {
|
||||
await writer.WriteAsync(t.Sql);
|
||||
await writer.WriteLineAsync(";");
|
||||
|
||||
var columnNames = new List<string>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"PRAGMA table_info({t.Name})";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync()) {
|
||||
columnNames.Add(reader.GetString(1));
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"SELECT {string.Join(',', columnNames)} FROM {t.Name}";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var columns = await reader.GetColumnSchemaAsync();
|
||||
var values = new object[reader.FieldCount];
|
||||
while (await reader.ReadAsync()) {
|
||||
await writer.WriteAsync($"INSERT INTO {t.Name} VALUES (");
|
||||
|
||||
reader.GetValues(values);
|
||||
for (int i = 0; i < columns.Count; i++) {
|
||||
var c = columns[i];
|
||||
var v = values[i];
|
||||
if (i > 0) await writer.WriteAsync(",");
|
||||
if (v == null || v is DBNull) {
|
||||
await writer.WriteAsync("NULL");
|
||||
} else if (c.DataTypeName == "TEXT") {
|
||||
await writer.WriteAsync($"'{v.ToString()?.Replace("'", "''")}'");
|
||||
} else {
|
||||
await writer.WriteAsync(v.ToString()?.Replace(',', '.'));
|
||||
}
|
||||
}
|
||||
|
||||
await writer.WriteLineAsync(");");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = "SELECT sql FROM sqlite_schema WHERE type != 'table' AND sql IS NOT NULL";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync()) {
|
||||
await writer.WriteAsync(reader.GetString(0));
|
||||
await writer.WriteLineAsync(";");
|
||||
}
|
||||
}
|
||||
|
||||
await writer.WriteLineAsync($"PRAGMA schema_version={schemaVers};");
|
||||
await writer.WriteLineAsync("PRAGMA foreign_keys=ON;");
|
||||
await writer.WriteLineAsync("COMMIT;");
|
||||
await writer.WriteLineAsync("VACUUM;");
|
||||
}
|
||||
|
||||
public static async Task Import(string filename) {
|
||||
if (filename.EndsWith(".sql")) {
|
||||
await ImportSql(filename, false);
|
||||
} else if (filename.EndsWith(".sql.zip")) {
|
||||
await ImportSql(filename, true);
|
||||
} else if (filename.EndsWith(".sqlite3")) {
|
||||
await ImportSqlite(filename, false);
|
||||
} else if (filename.EndsWith(".sqlite3.zip")) {
|
||||
await ImportSqlite(filename, true);
|
||||
} else {
|
||||
throw new ArgumentException($"Unknown file extension for importing: '{filename}'");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ImportSql(string filename, bool zipFile = false) {
|
||||
if (zipFile) {
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
|
||||
await zip.CheckIntegrity();
|
||||
foreach (var entry in zip.Entries) {
|
||||
if (entry.Name.EndsWith(".sql")) {
|
||||
using var stream = entry.Open();
|
||||
using var reader = new StreamReader(stream, Utils.UTF8);
|
||||
await ImportSql(reader);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new FileFormatException("ZIP archive has to contain at least one .sql file");
|
||||
} else {
|
||||
using var stream = File.Open(filename, FileMode.Open);
|
||||
using var reader = new StreamReader(stream, Utils.UTF8);
|
||||
await ImportSql(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ImportSqlite(string filename, bool zipFile = false) {
|
||||
if (zipFile) {
|
||||
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
|
||||
try {
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
|
||||
await zip.CheckIntegrity();
|
||||
foreach (var entry in zip.Entries) {
|
||||
if (entry.Name.EndsWith(".sqlite3")) {
|
||||
entry.ExtractToFile(newName);
|
||||
await ImportSqlite(newName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new FileFormatException("ZIP archive has to contain at least one .sqlite3 file");
|
||||
} finally {
|
||||
if (File.Exists(newName)) File.Delete(newName);
|
||||
}
|
||||
}
|
||||
|
||||
var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3");
|
||||
File.Move(App.Config.DatabaseFile, oldName, true);
|
||||
File.Move(filename, App.Config.DatabaseFile, false);
|
||||
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, "VACUUM");
|
||||
}
|
||||
|
||||
public static async Task ImportSql(StreamReader reader) {
|
||||
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
|
||||
File.Delete(newName);
|
||||
try {
|
||||
using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) {
|
||||
await AppDbContext.ExecuteBatch(cnx, await reader.ReadToEndAsync());
|
||||
}
|
||||
await ImportSqlite(newName);
|
||||
} finally {
|
||||
if (File.Exists(newName)) File.Delete(newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user