1
0
Fork 0
forked from dlang/cdcdb

Добавлены описания

This commit is contained in:
Alexander Zhirov 2025-09-13 02:50:01 +03:00
parent 8716a90463
commit 8639c36f46
Signed by: alexander
GPG key ID: C8D8BE544A27C511
8 changed files with 388 additions and 34 deletions

105
README.md
View file

@ -1,14 +1,103 @@
# cdcdb
Подход с использованием CDC (Capture Data Change) для хранения блоков данных в базе данных SQLite.
A library for storing and managing snapshots of textual data in an SQLite database. It uses content-defined chunking (CDC) based on the FastCDC algorithm to split data into variable-size chunks for efficient deduplication. Supports optional Zstd compression, transactions, and end-to-end integrity verification via SHA-256. Primary use cases: backups and versioning of text files while minimizing storage footprint.
## Сборка
## FastCDC algorithm
FastCDC splits data into variable-size chunks using content hashing. A Gear table is used to compute rolling “fingerprints” and choose cut points while respecting minimum, target, and maximum chunk sizes. This efficiently detects changes and stores only unique chunks, reducing storage usage.
## Core classes
### Storage
High-level API for the SQLite store and snapshot management.
- **Constructor**: Initializes a connection to SQLite.
- **Methods**:
- `newSnapshot`: Creates a snapshot. Returns a `Snapshot` object or `null` if the data matches the latest snapshot.
- `getSnapshots`: Returns a list of snapshots (all, or filtered by label). Returns an array of `Snapshot`.
- `getSnapshot`: Fetches a snapshot by ID. Returns a `Snapshot`.
- `setupCDC`: Configures CDC splitting parameters. Returns nothing.
- `getVersion`: Returns the library version string (e.g., `"0.0.2"`).
- `removeSnapshots`: Deletes snapshots by label, ID, or a `Snapshot` object. Returns the number of deleted snapshots (for label) or `true`/`false` (for ID or object).
### Snapshot
Work with an individual snapshot.
- **Constructor**: Creates a snapshot handle by its ID.
- **Methods**:
- `data`: Restores full snapshot data. Returns a byte array (`ubyte[]`).
- `data`: Streams restored data via a delegate sink. Returns nothing.
- `remove`: Deletes the snapshot from the database. Returns `true` on success, otherwise `false`.
- **Properties**:
- `id`: Snapshot ID (`long`).
- `label`: Snapshot label (`string`).
- `created`: Creation timestamp (UTC, `DateTime`).
- `length`: Original data length (`long`).
- `sha256`: Data SHA-256 hash (`ubyte[32]`).
- `status`: Snapshot status (`"pending"` or `"ready"`).
- `description`: Snapshot description (`string`).
## Example
```d
import cdcdb;
import std.stdio : writeln, File;
import std.file : exists, remove;
import zstd : Level;
void main()
{
// Create DB
string dbPath = "example.db";
// Initialize Storage with Zstd compression
auto storage = new Storage(dbPath, true, Level.speed);
// Create a snapshot
ubyte[] data = cast(ubyte[]) "Hello, cdcdb!".dup;
auto snap = storage.newSnapshot("example_file", data, "Version 1.0");
if (snap)
{
writeln("Snapshot created: ID=", snap.id, ", Label=", snap.label);
}
// Restore data
auto snapshots = storage.getSnapshots("example_file");
if (snapshots.length > 0)
{
auto lastSnap = snapshots[0];
File outFile = File("restored.txt", "wb");
lastSnap.data((const(ubyte)[] chunk) => outFile.rawWrite(chunk));
outFile.close();
writeln("Data restored to restored.txt");
}
// Delete snapshots
long deleted = storage.removeSnapshots("example_file");
writeln("Deleted snapshots: ", deleted);
}
```
## Tools
The `tools` directory contains a small D script for generating a Gear table used by FastCDC. It lets you build custom hash tables to tune splitting behavior. To generate a new table:
```bash
# Статическая библиотека
dub build -c static
# Динамическая библиотека
dub build -c dynamic
# Тест-утилита
dub build -c binary
chmod +x ./tools/gen.d
./tools/gen.d > ./source/gear.d
```
## Installation
* **In `dub.json`**:
```json
"dependencies": {
"cdcdb": "~>0.1.0"
}
```
* **Build**: `dub build`.
## License
Boost Software License 1.0 (BSL-1.0).

99
README.ru.md Normal file
View file

@ -0,0 +1,99 @@
# cdcdb
Библиотека для хранения и управления снимками текстовых данных в базе SQLite. Использует механизм content-defined chunking (CDC) на основе алгоритма FastCDC для разделения данных на чанки переменного размера, что обеспечивает эффективную дедупликацию. Поддерживает опциональную компрессию Zstd, транзакции и проверку целостности данных через SHA-256. Основное применение — резервное копирование и версионирование текстовых файлов с минимизацией занимаемого пространства.
## Алгоритм FastCDC
FastCDC — это алгоритм разделения данных на чанки переменного размера, основанный на хэшировании содержимого. Он использует таблицу Gear для вычисления "отпечатков" данных, определяя точки разделения с учетом минимального, целевого и максимального размеров чанка. Это позволяет эффективно выявлять изменения в данных и хранить только уникальные чанки, снижая объем хранилища.
## Основные классы
### Storage
Класс для работы с базой SQLite и управления снимками.
- **Конструктор**: Инициализирует подключение к базе SQLite.
- **Методы**:
- `newSnapshot`: Создает снимок данных. Возвращает объект `Snapshot` или `null`, если данные совпадают с последним снимком.
- `getSnapshots`: Получает список снимков (все или по метке). Возвращает массив объектов `Snapshot`.
- `getSnapshot`: Получает снимок по ID. Возвращает объект `Snapshot`.
- `setupCDC`: Настраивает параметры разделения данных CDC. Ничего не возвращает.
- `getVersion`: Возвращает строку с версией библиотеки (например, "0.0.2").
- `removeSnapshots`: Удаляет снимки по метке, ID или объекту Snapshot. Возвращает количество удаленных снимков (для метки) или `true`/`false` (для ID или объекта).
### Snapshot
Класс для работы с отдельным снимком данных.
- **Конструктор**: Создает объект снимка для работы с данными по его ID.
- **Методы**:
- `data`: Извлекает полные данные снимка. Возвращает массив байтов (`ubyte[]`).
- `data`: Извлекает данные снимка потоково через делегат. Ничего не возвращает.
- `remove`: Удаляет снимок из базы. Возвращает `true` при успешном удалении, иначе `false`.
- **Свойства**:
- `id`: ID снимка (long).
- `label`: Метка снимка (string).
- `created`: Временная метка создания (UTC, `DateTime`).
- `length`: Длина исходных данных (long).
- `sha256`: Хэш SHA-256 данных (ubyte[32]).
- `status`: Статус снимка ("pending" или "ready").
- `description`: Описание снимка (string).
## Пример использования
```d
import cdcdb;
import std.stdio : writeln, File;
import std.file : exists, remove;
import zstd : Level;
void main()
{
// Создаем базу
string dbPath = "example.db";
// Инициализация Storage с компрессией Zstd
auto storage = new Storage(dbPath, true, Level.speed);
// Создание снимка
ubyte[] data = cast(ubyte[]) "Hello, cdcdb!".dup;
auto snap = storage.newSnapshot("example_file", data, "Версия 1.0");
if (snap)
{
writeln("Создан снимок: ID=", snap.id, ", Метка=", snap.label);
}
// Восстановление данных
auto snapshots = storage.getSnapshots("example_file");
if (snapshots.length > 0)
{
auto lastSnap = snapshots[0];
File outFile = File("restored.txt", "wb");
lastSnap.data((const(ubyte)[] chunk) => outFile.rawWrite(chunk));
outFile.close();
writeln("Данные восстановлены в restored.txt");
}
// Удаление снимков
long deleted = storage.removeSnapshots("example_file");
writeln("Удалено снимков: ", deleted);
}
```
## Инструменты
В директории `tools` находится скрипт на D для создания таблицы Gear, используемой в алгоритме FastCDC. Он позволяет генерировать пользовательские таблицы хэшей для настройки разделения данных. Для создания новой таблицы выполните:
```bash
chmod +x ./tools/gen.d
./tools/gen.d > ./source/gear.d
```
## Установка
- **В `dub.json`**:
```json
"dependencies": {
"cdcdb": "~>0.1.0"
}
```
- **Сборка**: `dub build`.
## Лицензия
Boost Software License 1.0 (BSL-1.0).

View file

@ -18,20 +18,41 @@
"name": "static",
"targetType": "staticLibrary",
"targetPath": "lib",
"sourcePaths": ["source"]
"sourcePaths": [
"source"
]
},
{
"name": "dynamic",
"targetType": "dynamicLibrary",
"targetPath": "lib",
"sourcePaths": ["source"]
"sourcePaths": [
"source"
]
},
{
"name": "binary",
"targetType": "executable",
"targetPath": "bin",
"mainSourceFile": "test/app.d",
"sourcePaths": ["source", "test"]
"sourcePaths": [
"source",
"test"
]
},
{
"name": "unittest",
"targetType": "executable",
"targetPath": "bin",
"sourcePaths": [
"source",
"test"
],
"buildOptions": [
"unittests"
],
"dflags": ["-main"],
"mainSourceFile": "test/unittest.d"
}
]
}

View file

@ -369,10 +369,24 @@ public:
long deleteSnapshot(long id) {
auto queryResult = sql("DELETE FROM snapshots WHERE id = ? RETURNING id", id);
if (queryResult.empty()) {
throw new Exception("Ошибка при удалении снимка из базы данных");
}
if (!queryResult.empty()) {
return queryResult.front()["id"].to!long;
}
return 0;
}
long deleteSnapshot(string label) {
auto queryResult = sql(
q{
DELETE FROM snapshots
WHERE label = ?
RETURNING 1;
}, label);
if (!queryResult.empty()) {
return queryResult.length.to!long;
}
return 0;
}
}

View file

@ -137,6 +137,23 @@ public:
return snapshot;
}
// Удаляет снимок по метке, возвращает количество удаленных снимков
long removeSnapshots(string label) {
return _db.deleteSnapshot(label);
}
bool removeSnapshots(Snapshot snapshot) {
return removeSnapshots(snapshot.id);
}
bool removeSnapshots(long idSnapshot) {
return _db.deleteSnapshot(idSnapshot) == idSnapshot;
}
Snapshot getSnapshot(long idSnapshot) {
return new Snapshot(_db, idSnapshot);
}
Snapshot[] getSnapshots(string label = string.init) {
Snapshot[] snapshots;

View file

@ -1,3 +1,3 @@
module cdcdb.version_;
enum cdcdbVersion = "0.0.1";
enum cdcdbVersion = "0.1.0";

View file

@ -1,27 +1,69 @@
import std.stdio;
import cdcdb;
import std.file : read, write;
import std.stdio : File, writeln;
import std.conv : to;
import std.stdio : writeln, File;
import std.file : exists, remove, read;
import zstd : Level;
void main()
{
auto storage = new Storage("/tmp/base.db", true, 22);
storage.newSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text"));
// Создаем временную базу для примера
string dbPath = "./bin/example.db";
// if (snapshot !is null) {
// writeln(cast(string) snapshot.data);
// snapshot.remove();
// }
// Инициализация Storage с компрессией Zstd
auto storage = new Storage(dbPath, true, Level.speed);
foreach (snapshot; storage.getSnapshots()) {
auto file = File("/tmp/restore" ~ snapshot.id.to!string, "wb");
snapshot.data((const(ubyte)[] content) {
file.rawWrite(content);
});
file.close();
// Настройка параметров CDC (опционально)
storage.setupCDC(256, 512, 1024, 0xFF, 0x0F);
// Тестовые данные
ubyte[] data1 = cast(ubyte[]) "Hello, cdcdb!".dup;
ubyte[] data2 = cast(ubyte[]) "Hello, updated cdcdb!".dup;
// Создание первого снимка
auto snap1 = storage.newSnapshot("example_file", data1, "Версия 1.0");
if (snap1)
{
writeln("Создан снимок с ID: ", snap1.id);
writeln("Метка: ", snap1.label);
writeln("Размер: ", snap1.length, " байт");
writeln("Статус: ", snap1.status);
}
// Создание второго снимка (обновление)
auto snap2 = storage.newSnapshot("example_file", data2, "Версия 2.0");
if (snap2)
{
writeln("Создан обновленный снимок с ID: ", snap2.id);
}
// Получение всех снимков по метке
auto snapshots = storage.getSnapshots("example_file");
writeln("Найдено снимков: ", snapshots.length);
// Восстановление данных из последнего снимка (потоково, для экономии памяти)
if (snapshots.length > 0)
{
auto lastSnap = snapshots[$ - 1]; // Последний снимок
File outFile = File("./bin/restored.txt", "wb");
lastSnap.data((const(ubyte)[] chunk) { outFile.rawWrite(chunk); });
outFile.close();
writeln("Данные восстановлены в restored.txt");
// Проверка хэша (опционально)
import std.digest.sha : digest, SHA256;
auto restoredData = cast(ubyte[]) read("./bin/restored.txt");
assert(restoredData == data2);
writeln("Хэш совпадает: ", lastSnap.sha256 == digest!SHA256(restoredData));
}
// Удаление снимков по метке
long deleted = storage.removeSnapshots("example_file");
writeln("Удалено снимков: ", deleted);
// Проверка: снимки удалены
auto remaining = storage.getSnapshots("example_file");
assert(remaining.length == 0);
writeln("Все снимки удалены.");
writeln("Версия библиотеки: ", storage.getVersion());
}

72
test/unittest.d Normal file
View file

@ -0,0 +1,72 @@
import cdcdb;
import std.file : read, write, remove, exists;
import std.path : buildPath;
import std.digest.sha : digest, SHA256;
import std.exception : assertThrown, assertNotThrown;
import std.datetime : DateTime;
import core.thread : Thread;
import core.time : msecs, seconds;
unittest
{
const string dbPath = "./bin/test_cdcdb.db";
if (exists(dbPath)) {
remove(dbPath);
}
// Тест конструктора Storage
auto storage = new Storage(dbPath, true, 22);
// Тест настройки CDC
storage.setupCDC(128, 256, 512, 0xFF, 0x0F);
// Тест создания снимка
ubyte[] data1 = cast(ubyte[]) "Hello, World!".dup;
auto snap1 = storage.newSnapshot("test_label", data1, "First snapshot");
assert(snap1 !is null);
assert(snap1.label == "test_label");
assert(snap1.length == data1.length);
assert(snap1.sha256 == digest!SHA256(data1));
assert(snap1.status == "ready");
assert(snap1.description == "First snapshot");
// Тест дубликата (не должен создать новый)
auto snapDup = storage.newSnapshot("test_label", data1);
assert(snapDup is null);
// Тест изменения данных
ubyte[] data2 = cast(ubyte[]) "Hello, Changed World!".dup;
auto snap2 = storage.newSnapshot("test_label", data2);
assert(snap2 !is null);
assert(snap2.sha256 == digest!SHA256(data2));
// Тест восстановления данных
auto restored = snap1.data();
assert(restored == data1);
bool streamedOk = false;
snap2.data((const(ubyte)[] chunk) {
assert(chunk == data2); // Поскольку маленький файл — один фрагмент
streamedOk = true;
});
assert(streamedOk);
// Тест getSnapshots
auto snaps = storage.getSnapshots("test_label");
assert(snaps.length == 2);
assert(snaps[0].id == snap1.id);
assert(snaps[1].id == snap2.id);
auto allSnaps = storage.getSnapshots();
assert(allSnaps.length == 2);
// Тест удаления
assert(snap1.remove());
snaps = storage.getSnapshots("test_label");
assert(snaps.length == 1);
assert(snaps[0].id == snap2.id);
// Тест пустых данных
assertThrown!Exception(storage.newSnapshot("empty", []));
}