Обновлен CAS, добавлены проверки

This commit is contained in:
Alexander Zhirov 2025-09-11 19:24:24 +03:00
parent 9c5d812a49
commit b29b328f91
Signed by: alexander
GPG key ID: C8D8BE544A27C511
4 changed files with 161 additions and 117 deletions

View file

@ -14,12 +14,34 @@ import std.conv : to;
import std.file : write;
// CAS-хранилище (Content-Addressable Storage) со снапшотами
// Content-Addressable Storage (Контентно-адресуемая система хранения)
// CAS-хранилище со снапшотами
final class CAS
{
private:
DBLite _db;
bool _zstd;
ubyte[] buildContent(const ref Snapshot snapshot)
{
auto dataChunks = _db.getChunks(snapshot.id);
ubyte[] content;
foreach (chunk; dataChunks) {
ubyte[] bytes;
if (chunk.zstd) {
enforce(chunk.zSize == chunk.content.length, "Размер сжатого фрагмента не соответствует ожидаемому");
bytes = cast(ubyte[]) uncompress(chunk.content);
} else {
bytes = chunk.content;
}
enforce(chunk.size == bytes.length, "Оригинальный размер не соответствует ожидаемому");
content ~= bytes;
}
enforce(snapshot.fileSha256 == digest!SHA256(content), "Хеш-сумма файла не совпадает");
return content;
}
public:
this(string database, bool zstd = false)
{
@ -27,14 +49,12 @@ public:
_zstd = zstd;
}
size_t saveSnapshot(string filePath, const(ubyte)[] data)
size_t newSnapshot(string filePath, string label, const(ubyte)[] data)
{
ubyte[32] hashSource = digest!SHA256(data);
// Сделать запрос в БД по filePath и сверить хеш файлов
// writeln(hashSource.length);
if (_db.isLast(filePath, hashSource)) return 0;
// Параметры для CDC вынести в отдельные настройки (продумать)
auto cdc = new CDC(256, 512, 1024, 0xFF, 0x0F);
@ -42,13 +62,26 @@ public:
auto chunks = cdc.split(data);
Snapshot snapshot;
snapshot.filePath = filePath;
snapshot.fileSha256 = hashSource;
snapshot.label = "Файл для теста";
snapshot.label = label;
snapshot.sourceLength = data.length;
_db.beginImmediate();
bool ok;
scope (exit)
{
if (!ok)
_db.rollback();
}
scope (success)
{
_db.commit();
}
auto idSnapshot = _db.addSnapshot(snapshot);
SnapshotChunk snapshotChunk;
@ -56,7 +89,7 @@ public:
blob.zstd = _zstd;
// Записать фрагменты в БД
// Запись фрагментов в БД
foreach (chunk; chunks)
{
blob.sha256 = chunk.sha256;
@ -76,6 +109,7 @@ public:
blob.content = content.dup;
}
// Запись фрагментов
_db.addBlob(blob);
snapshotChunk.snapshotId = idSnapshot;
@ -83,38 +117,28 @@ public:
snapshotChunk.offset = chunk.offset;
snapshotChunk.sha256 = chunk.sha256;
// Привязка фрагментов к снимку
_db.addSnapshotChunk(snapshotChunk);
}
_db.commit();
// Записать манифест в БД
// Вернуть ID манифеста
return 0;
ok = true;
return idSnapshot;
}
void restoreSnapshot()
Snapshot[] getSnapshotList(string filePath)
{
string restoreFile = "/tmp/restore.d";
foreach (Snapshot snapshot; _db.getSnapshots("/tmp/text")) {
auto dataChunks = _db.getChunks(snapshot.id);
ubyte[] content;
foreach (SnapshotDataChunk chunk; dataChunks) {
ubyte[] bytes;
if (chunk.zstd) {
enforce(chunk.zSize == chunk.content.length, "Размер сжатого фрагмента не соответствует ожидаемому");
bytes = cast(ubyte[]) uncompress(chunk.content);
} else {
bytes = chunk.content;
}
enforce(chunk.size == bytes.length, "Оригинальный размер не соответствует ожидаемому");
content ~= bytes;
return _db.getSnapshots(filePath);
}
enforce(snapshot.fileSha256 == digest!SHA256(content), "Хеш-сумма файла не совпадает");
write(snapshot.filePath ~ snapshot.id.to!string, content);
ubyte[] getSnapshotData(const ref Snapshot snapshot)
{
ubyte[] content = buildContent(snapshot);
return content;
}
void removeSnapshot(const ref Snapshot snapshot)
{
_db.deleteSnapshot(snapshot.id);
}
}

View file

@ -4,7 +4,7 @@ import cdcdb.cdc.types;
import std.digest.sha : SHA256, digest;
// Change Data Capture
// Change Data Capture (Захват изменения данных)
final class CDC
{
private:

View file

@ -1,11 +1,13 @@
module cdcdb.db.dblite;
import cdcdb.db.types;
import arsd.sqlite;
import std.file : exists, isFile;
import std.file : exists;
import std.exception : enforce;
import std.conv : to;
import cdcdb.db.types;
import std.string : join;
final class DBLite : Sqlite
{
@ -18,16 +20,50 @@ private:
{
return cast(SqliteResult) query(queryText, args);
}
// Проверка БД на наличие существующих в ней необходимых таблиц
void check()
{
SqliteResult queryResult = sql(
q{
WITH required(name) AS (VALUES ("snapshots"), ("blobs"), ("snapshot_chunks"))
SELECT name AS missing_table
FROM required
WHERE NOT EXISTS (
SELECT 1
FROM sqlite_master
WHERE type = "table" AND name = required.name
);
}
);
string[] missingTables;
foreach (row; queryResult)
{
missingTables ~= row["missing_table"].to!string;
}
enforce(missingTables.length == 0 || missingTables.length == 3,
"База данных повреждена. Отсутствуют таблицы: " ~ missingTables.join(", ")
);
if (missingTables.length == 3)
{
foreach (schemeQuery; _scheme)
{
sql(schemeQuery);
}
}
}
public:
this(string database)
{
_dbPath = database;
super(database);
foreach (schemeQuery; _scheme)
{
sql(schemeQuery);
}
check();
query("PRAGMA journal_mode=WAL");
query("PRAGMA synchronous=NORMAL");
@ -49,16 +85,6 @@ public:
query("ROLLBACK");
}
// Snapshot getSnapshot(string filePath, immutable ubyte[32] sha256)
// {
// auto queryResult = sql(
// q{
// SELECT * FROM snapshots
// WHERE file_path = ? AND file_sha256 = ?
// }, filePath, sha256
// );
// }
long addSnapshot(Snapshot snapshot)
{
auto queryResult = sql(
@ -125,66 +151,23 @@ public:
);
}
// struct ChunkInput
// {
// long index;
// long offset;
// long size;
// ubyte[32] sha256;
// const(ubyte)[] content;
// }
bool isLast(string filePath, ubyte[] fileSha256) {
auto queryResult = sql(
q{
SELECT COALESCE(
(SELECT (file_path = ? AND file_sha256 = ?)
FROM snapshots
ORDER BY created_utc DESC
LIMIT 1),
0
) AS is_last;
}, filePath, fileSha256
);
// long saveSnapshotWithChunks(
// string filePath, string label, long sourceLength,
// long algoMin, long algoNormal, long algoMax,
// long maskS, long maskL,
// const ChunkInput[] chunks
// )
// {
// beginImmediate();
// bool ok;
// scope (exit)
// {
// if (!ok)
// rollback();
// }
// scope (success)
// {
// commit();
// }
// const snapId = insertSnapshotMeta(
// filePath, label, sourceLength,
// algoMin, algoNormal, algoMax,
// maskS, maskL, SnapshotStatus.pending
// );
// foreach (c; chunks)
// {
// insertBlobIfMissing(c.sha256, c.size, c.content);
// insertSnapshotChunk(snapId, c.index, c.offset, c.size, c.sha256);
// }
// ok = true;
// return snapId;
// }
// // --- чтение ---
if (!queryResult.empty())
return queryResult.front()["is_last"].to!long > 0;
return false;
}
Snapshot[] getSnapshots(string filePath)
{
@ -197,7 +180,7 @@ public:
);
Snapshot[] snapshots;
// bool found = false;
foreach (row; queryResult)
{
Snapshot snapshot;
@ -213,15 +196,53 @@ public:
snapshot.algoMax = row["algo_max"].to!long;
snapshot.maskS = row["mask_s"].to!long;
snapshot.maskL = row["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus)row["status"].to!int;
// found = true;
snapshot.status = cast(SnapshotStatus) row["status"].to!int;
snapshots ~= snapshot;
}
// enforce(found, "getSnapshot: not found");
return snapshots;
}
SnapshotDataChunk[] getChunks(long snapshotId) {
Snapshot getSnapshot(long id)
{
auto queryResult = sql(
q{
SELECT id, file_path, file_sha256, label, created_utc, source_length,
algo_min, algo_normal, algo_max, mask_s, mask_l, status
FROM snapshots WHERE id = ?
}, id
);
Snapshot snapshot;
if (!queryResult.empty())
{
auto data = queryResult.front();
snapshot.id = data["id"].to!long;
snapshot.filePath = data["file_path"].to!string;
snapshot.fileSha256 = cast(ubyte[]) data["file_sha256"].dup;
snapshot.label = data["label"].to!string;
snapshot.createdUtc = data["created_utc"].to!string;
snapshot.sourceLength = data["source_length"].to!long;
snapshot.algoMin = data["algo_min"].to!long;
snapshot.algoNormal = data["algo_normal"].to!long;
snapshot.algoMax = data["algo_max"].to!long;
snapshot.maskS = data["mask_s"].to!long;
snapshot.maskL = data["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus) data["status"].to!int;
}
return snapshot;
}
void deleteSnapshot(long id) {
sql("DELETE FROM snapshots WHERE id = ?", id);
}
SnapshotDataChunk[] getChunks(long snapshotId)
{
auto queryResult = sql(
q{
SELECT sc.chunk_index, sc.offset,

View file

@ -7,6 +7,5 @@ import std.file : read;
void main()
{
auto cas = new CAS("/tmp/base.db", true);
cas.saveSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text"));
// cas.restoreSnapshot();
cas.newSnapshot("/tmp/text", "Файл для тестирования", cast(ubyte[]) read("/tmp/text"));
}