From a732c6a4b233b44eed08f565221076277a01a63e Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Wed, 10 Sep 2025 00:13:02 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D1=85=D0=B5=D0=BC=D1=8B=20=D0=91?= =?UTF-8?q?=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/cdcdb/cdc/cas.d | 12 +- source/cdcdb/db/dblite.d | 10 +- source/cdcdb/db/scheme.d | 239 +++++++++++++++++++++++++++------------ test/app.d | 2 +- 4 files changed, 183 insertions(+), 80 deletions(-) diff --git a/source/cdcdb/cdc/cas.d b/source/cdcdb/cdc/cas.d index 023c139..8f11b23 100644 --- a/source/cdcdb/cdc/cas.d +++ b/source/cdcdb/cdc/cas.d @@ -3,6 +3,9 @@ module cdcdb.cdc.cas; import cdcdb.db; import cdcdb.cdc.core; +import std.digest.sha : SHA256, digest; +import std.format : format; + final class CAS { private: @@ -13,8 +16,13 @@ public: _db = new DBLite(database); } - size_t saveSnapshot(const(ubyte)[] data) + size_t saveSnapshot(string filePath, const(ubyte)[] data) { + ubyte[32] hashSource = digest!SHA256(data); + // Сделать запрос в БД по filePath и сверить хеш файлов + + + // Параметры для CDC вынести в отдельные настройки (продумать) auto cdc = new CDC(100, 200, 500, 0xFF, 0x0F); // Разбить на фрагменты @@ -26,7 +34,7 @@ public: // Записать фрагменты в БД foreach (chunk; chunks) { - writeln(chunk.index); + writeln(format("%(%02x%)", chunk.sha256)); } _db.commit(); // Записать манифест в БД diff --git a/source/cdcdb/db/dblite.d b/source/cdcdb/db/dblite.d index 184238c..3bba325 100644 --- a/source/cdcdb/db/dblite.d +++ b/source/cdcdb/db/dblite.d @@ -9,6 +9,11 @@ private: string _dbPath; // _scheme mixin(import("scheme.d")); + + SqliteResult sql(T...)(string queryText, T args) + { + return cast(SqliteResult) query(queryText, args); + } public: this(string database) { @@ -40,8 +45,5 @@ public: query("ROLLBACK"); } - SqliteResult sql(T...)(string queryText, T args) - { - return cast(SqliteResult) query(queryText, args); - } + // findFile() } diff --git a/source/cdcdb/db/scheme.d b/source/cdcdb/db/scheme.d index 557e0cf..c3dc51e 100644 --- a/source/cdcdb/db/scheme.d +++ b/source/cdcdb/db/scheme.d @@ -1,75 +1,168 @@ auto _scheme = [ - q{ - -- Метаданные снапшота - CREATE TABLE IF NOT EXISTS snapshots ( - -- Уникальный числовой идентификатор снимка. Используется во внешних ключах. - id INTEGER PRIMARY KEY AUTOINCREMENT, - -- Произвольная метка/название снимка. - label TEXT, - -- Время создания записи в UTC. По умолчанию - сейчас. - created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), - -- Полная длина исходного файла в байтах для этого снимка (до разбиения на чанки). - source_length INTEGER NOT NULL, - -- Пороговые размеры FastCDC (минимальный/целевой/максимальный размер чанка) в байтах. - -- Фиксируются здесь, чтобы позже можно было корректно пересобрать/сравнить. - algo_min INTEGER NOT NULL, - algo_normal INTEGER NOT NULL, - algo_max INTEGER NOT NULL, - -- Маски для определения границ чанков (быстрый роллинг-хэш/FastCDC). - -- Обычно степени вида 2^n - 1. Хранятся для воспроизводимости. - mask_s INTEGER NOT NULL, - mask_l INTEGER NOT NULL, - -- Состояние снимка: - -- pending - метаданные созданы, состав не полностью загружен; - -- ready - все чанки привязаны, снимок готов к использованию. - status TEXT NOT NULL DEFAULT "pending" CHECK (status IN ("pending","ready")) - ) - }, - q{ - -- Уникальные куски содержимого (сам контент в БД) - CREATE TABLE IF NOT EXISTS blobs ( - -- Хэш содержимого чанка. Ключ обеспечивает дедупликацию: одинаковый контент хранится один раз. - sha256 TEXT PRIMARY KEY, - -- Размер этого чанка в байтах. - size INTEGER NOT NULL, - -- Сырые байты чанка. - content BLOB NOT NULL, - -- Когда этот контент впервые появился в базе (UTC). - created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP) - ) - }, - q{ - -- Состав снапшота (порядок чанков важен) - CREATE TABLE IF NOT EXISTS snapshot_chunks ( - -- Ссылка на snapshots.id. Определяет, к какому снимку относится строка. - snapshot_id INTEGER NOT NULL, - -- Позиция чанка в снимке (индексация). - -- Обеспечивает порядок сборки. - chunk_index INTEGER NOT NULL, - -- Смещение чанка в исходном файле в байтах. - -- Можно восстановить как сумму size предыдущих чанков по chunk_index, - -- но хранение ускоряет проверки/отладку. - offset INTEGER, - -- Размер именно этого чанка в байтах (дублирует blobs.size для быстрого доступа и валидации). - size INTEGER NOT NULL, - -- Ссылка на blobs.sha256. Привязывает позицию в снимке к конкретному содержимому. - sha256 TEXT NOT NULL, - -- Гарантирует уникальность позиции чанка в рамках снимка и задаёт естественный порядок. - PRIMARY KEY (snapshot_id, chunk_index), - -- При удалении снимка его строки состава удаляются автоматически. - -- Обновления id каскадятся (на практике id не меняют). - FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON UPDATE CASCADE ON DELETE CASCADE, - -- Нельзя удалить blob, если он где-то используется (RESTRICT). - -- Обновление хэша каскадится (редкий случай). - FOREIGN KEY (sha256) REFERENCES blobs(sha256) ON UPDATE CASCADE ON DELETE RESTRICT - ) - }, - q{ - -- Быстрый выбор всех чанков конкретного снимка (частый запрос). - CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_snapshot ON snapshot_chunks(snapshot_id) - }, - q{ - -- Быстрый обратный поиск: где используется данный blob (для GC/аналитики). - CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha ON snapshot_chunks(sha256) - } + q{ + -- ------------------------------------------------------------ + -- Метаданные снимка + -- ------------------------------------------------------------ + CREATE TABLE IF NOT EXISTS snapshots ( + -- Уникальный числовой идентификатор снимка. Используется во внешних ключах. + id INTEGER PRIMARY KEY AUTOINCREMENT, + + -- Путь к исходному файлу, для удобства навигации/поиска. + file_path TEXT, + + -- Произвольная метка/название снимка (для человека). + label TEXT, + + -- Время создания записи (UTC). По умолчанию - текущее. + created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), + + -- Полная длина исходного файла в байтах (до разбиения на чанки). + source_length INTEGER NOT NULL, + + -- Пороговые размеры FastCDC: минимальный/целевой/максимальный размер чанка (в байтах). + -- Фиксируются для воспроизводимости и сравнения результатов. + algo_min INTEGER NOT NULL, + algo_normal INTEGER NOT NULL, + algo_max INTEGER NOT NULL, + + -- Маски FastCDC для определения границ чанков (обычно вида 2^n - 1). + -- Хранятся для воспроизводимости. + mask_s INTEGER NOT NULL, + mask_l INTEGER NOT NULL, + + -- Состояние снимка: + -- "pending" - метаданные созданы, состав не полностью загружен; + -- "ready" - все чанки привязаны, снимок готов к использованию. + status TEXT NOT NULL DEFAULT "pending" CHECK (status IN ("pending","ready")) + ) + }, + q{ + -- ------------------------------------------------------------ + -- Уникальные куски содержимого (дедупликация по sha256) + -- ------------------------------------------------------------ + CREATE TABLE IF NOT EXISTS blobs ( + -- Хэш содержимого чанка (hex, 64 символа). Обеспечивает уникальность контента. + sha256 TEXT PRIMARY KEY, + + -- Размер чанка в байтах. Должен совпадать с длиной content. + size INTEGER NOT NULL, + + -- Сырые байты чанка. + content BLOB NOT NULL, + + -- Момент, когда этот контент впервые появился в базе (UTC). + created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), + + -- Последний раз, когда на контент сослались (для аналитики/GC). + last_seen_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), + + -- Счётчик ссылок: сколько строк в snapshot_chunks ссылаются на этот sha256. + -- Используется для безопасного удаления неиспользуемых blob-ов. + refcount INTEGER NOT NULL DEFAULT 0, + + -- Дополнительные гарантии целостности: + CHECK (refcount >= 0), + CHECK (size = length(content)) + ) + }, + q{ + -- ------------------------------------------------------------ + -- Состав снимка (упорядоченный список чанков) + -- ------------------------------------------------------------ + CREATE TABLE IF NOT EXISTS snapshot_chunks ( + -- Ссылка на snapshots.id. Определяет, к какому снимку относится строка. + snapshot_id INTEGER NOT NULL, + + -- Позиция чанка в снимке (индексация на твой выбор - 0/1-based; важно быть последовательным). + -- Обеспечивает порядок сборки. + chunk_index INTEGER NOT NULL, + + -- Смещение чанка в исходном файле (в байтах). + -- Можно восстановить суммой size предыдущих чанков, но хранение ускоряет проверки/отладку. + offset INTEGER, + + -- Размер конкретного чанка в составе (дублирует blobs.size для ускорения/валидации). + size INTEGER NOT NULL, + + -- Ссылка на blobs.sha256. Привязывает позицию к конкретному содержимому. + sha256 TEXT NOT NULL, + + -- Уникальность позиции чанка в рамках одного снимка. + PRIMARY KEY (snapshot_id, chunk_index), + + -- Внешние ключи и их поведение: + -- При удалении снимка его строки состава удаляются автоматически. + FOREIGN KEY (snapshot_id) + REFERENCES snapshots(id) + ON UPDATE CASCADE + ON DELETE CASCADE, + + -- Нельзя удалить blob, если он где-то используется; обновление ключа запрещено. + FOREIGN KEY (sha256) + REFERENCES blobs(sha256) + ON UPDATE RESTRICT + ON DELETE RESTRICT + ) + }, + q{ + -- Быстрый выбор всех чанков конкретного снимка (частый запрос). + CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_snapshot + ON snapshot_chunks(snapshot_id) + }, + q{ + -- Быстрый обратный поиск: где используется данный blob (для GC/аналитики). + CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha + ON snapshot_chunks(sha256) + }, + // -- ------------------------------------------------------------ + // -- Триггеры для управления refcount и GC blob'ов + // -- ------------------------------------------------------------ + q{ + -- Инкремент счётчика при привязке чанка к снимку. + -- Обновляем last_seen_utc, чтобы фиксировать "живость" контента. + CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ai + AFTER INSERT ON snapshot_chunks + BEGIN + UPDATE blobs + SET refcount = refcount + 1, + last_seen_utc = CURRENT_TIMESTAMP + WHERE sha256 = NEW.sha256; + END + }, + q{ + -- Декремент счётчика при удалении строки состава. + -- Если счётчик стал 0 - удаляем неиспользуемый blob (авто-GC). + CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ad + AFTER DELETE ON snapshot_chunks + BEGIN + UPDATE blobs + SET refcount = refcount - 1 + WHERE sha256 = OLD.sha256; + + DELETE FROM blobs + WHERE sha256 = OLD.sha256 + AND refcount <= 0; + END + }, + q{ + -- Корректировка счётчиков при смене sha256 в составе. + -- ВАЖНО: с ON UPDATE RESTRICT на FK обновление sha256 произойти не может, + -- поэтому этот триггер сработает только если изменить поведение FK (например, на CASCADE). + CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_au + AFTER UPDATE OF sha256 ON snapshot_chunks + BEGIN + UPDATE blobs + SET refcount = refcount - 1 + WHERE sha256 = OLD.sha256; + + DELETE FROM blobs + WHERE sha256 = OLD.sha256 + AND refcount <= 0; + + UPDATE blobs + SET refcount = refcount + 1, + last_seen_utc = CURRENT_TIMESTAMP + WHERE sha256 = NEW.sha256; + END + } ]; diff --git a/test/app.d b/test/app.d index a3526d6..5383fda 100644 --- a/test/app.d +++ b/test/app.d @@ -7,5 +7,5 @@ import std.file : read; void main() { auto cas = new CAS("/tmp/base.db"); - cas.saveSnapshot(cast(ubyte[]) read("/tmp/text")); + cas.saveSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text")); }