forked from dlang/cdcdb
Добавлен англоязычный DDoc
This commit is contained in:
parent
85aa3c8f53
commit
5d0cf228d6
4 changed files with 173 additions and 27 deletions
|
@ -5,12 +5,12 @@ import std.digest.sha : SHA256, digest;
|
||||||
struct Chunk
|
struct Chunk
|
||||||
{
|
{
|
||||||
size_t index; // 1..N
|
size_t index; // 1..N
|
||||||
size_t offset; // смещение в исходном буфере
|
size_t offset; // offset in the source buffer
|
||||||
size_t size; // размер чанка
|
size_t size; // chunk size
|
||||||
immutable(ubyte)[32] sha256; // hex(SHA-256) содержимого
|
immutable(ubyte)[32] sha256; // hex(SHA-256) of the content
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change Data Capture (Захват изменения данных)
|
// Change Data Capture (CDC)
|
||||||
final class CDC
|
final class CDC
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -36,13 +36,13 @@ private:
|
||||||
ulong fingerprint = 0;
|
ulong fingerprint = 0;
|
||||||
size_t index;
|
size_t index;
|
||||||
|
|
||||||
// инициализация без cut-check
|
// initialization without a cut-check
|
||||||
while (index < _minSize)
|
while (index < _minSize)
|
||||||
{
|
{
|
||||||
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
// строгая маска
|
// strict mask
|
||||||
while (index < normalSize)
|
while (index < normalSize)
|
||||||
{
|
{
|
||||||
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
||||||
|
@ -50,7 +50,7 @@ private:
|
||||||
return index;
|
return index;
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
// слабая маска
|
// weak mask
|
||||||
while (index < size)
|
while (index < size)
|
||||||
{
|
{
|
||||||
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
||||||
|
@ -65,7 +65,7 @@ public:
|
||||||
this(size_t minSize, size_t normalSize, size_t maxSize, ulong maskS, ulong maskL) @safe @nogc nothrow
|
this(size_t minSize, size_t normalSize, size_t maxSize, ulong maskS, ulong maskL) @safe @nogc nothrow
|
||||||
{
|
{
|
||||||
assert(minSize > 0 && minSize < normalSize && normalSize < maxSize,
|
assert(minSize > 0 && minSize < normalSize && normalSize < maxSize,
|
||||||
"Неверные размеры: требуется min < normal < max и min > 0");
|
"Invalid sizes: require min < normal < max and min > 0");
|
||||||
_minSize = minSize;
|
_minSize = minSize;
|
||||||
_normalSize = normalSize;
|
_normalSize = normalSize;
|
||||||
_maxSize = maxSize;
|
_maxSize = maxSize;
|
||||||
|
|
|
@ -87,7 +87,7 @@ private:
|
||||||
if (msg.toLower.canFind("locked", "busy")) {
|
if (msg.toLower.canFind("locked", "busy")) {
|
||||||
if (--tryNo == 0) {
|
if (--tryNo == 0) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"Не удалось выполнить подключение к базе данных после %d неудачных попыток: %s"
|
"Failed to connect to the database after %d failed attempts: %s"
|
||||||
.format(_maxRetries, msg)
|
.format(_maxRetries, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ private:
|
||||||
throw new Exception(msg);
|
throw new Exception(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка БД на наличие существующих в ней необходимых таблиц
|
// Check that the database contains the required tables; otherwise create them
|
||||||
void check()
|
void check()
|
||||||
{
|
{
|
||||||
SqliteResult queryResult = sql(
|
SqliteResult queryResult = sql(
|
||||||
|
@ -123,7 +123,7 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
enforce(missingTables.length == 0 || missingTables.length == 3,
|
enforce(missingTables.length == 0 || missingTables.length == 3,
|
||||||
"База данных повреждена. Отсутствуют таблицы: " ~ missingTables.join(", ")
|
"Database is corrupted. Missing tables: " ~ missingTables.join(", ")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (missingTables.length == 3)
|
if (missingTables.length == 3)
|
||||||
|
@ -221,7 +221,7 @@ public:
|
||||||
);
|
);
|
||||||
|
|
||||||
if (queryResult.empty()) {
|
if (queryResult.empty()) {
|
||||||
throw new Exception("Ошибка при добавлении нового снимока в базу данных");
|
throw new Exception("Error adding a new snapshot to the database");
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryResult.front()["id"].to!long;
|
return queryResult.front()["id"].to!long;
|
||||||
|
|
|
@ -8,6 +8,28 @@ import std.digest.sha : SHA256, digest;
|
||||||
import std.datetime : DateTime;
|
import std.datetime : DateTime;
|
||||||
import std.exception : enforce;
|
import std.exception : enforce;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snapshot reader and lifecycle helper.
|
||||||
|
*
|
||||||
|
* This class reconstructs full file content from chunked storage persisted
|
||||||
|
* via `DBLite`, verifies integrity (per-chunk SHA-256 and final file hash),
|
||||||
|
* and provides a safe way to remove a snapshot record.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ---
|
||||||
|
* auto s1 = new Snapshot(db, snapshotId);
|
||||||
|
* auto bytes = s1.data(); // materialize full content in memory
|
||||||
|
*
|
||||||
|
* // or stream into a sink to avoid large allocations:
|
||||||
|
* s1.data((const(ubyte)[] part) {
|
||||||
|
* // consume part
|
||||||
|
* });
|
||||||
|
* ---
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - All integrity checks are enforced; any mismatch throws.
|
||||||
|
* - `data(void delegate(...))` is preferred for very large files.
|
||||||
|
*/
|
||||||
final class Snapshot
|
final class Snapshot
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -19,32 +41,50 @@ private:
|
||||||
ubyte[] bytes;
|
ubyte[] bytes;
|
||||||
if (chunk.zstd)
|
if (chunk.zstd)
|
||||||
{
|
{
|
||||||
enforce(chunk.zSize == chunk.content.length, "Размер сжатого фрагмента не соответствует ожидаемому");
|
enforce(chunk.zSize == chunk.content.length, "Compressed chunk size does not match the expected value");
|
||||||
bytes = cast(ubyte[]) uncompress(chunk.content);
|
bytes = cast(ubyte[]) uncompress(chunk.content);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bytes = chunk.content.dup;
|
bytes = chunk.content.dup;
|
||||||
}
|
}
|
||||||
enforce(chunk.size == bytes.length, "Оригинальный размер не соответствует ожидаемому");
|
enforce(chunk.size == bytes.length, "Original size does not match the expected value");
|
||||||
enforce(chunk.sha256 == digest!SHA256(bytes), "Хеш-сумма фрагмента не совпадает");
|
enforce(chunk.sha256 == digest!SHA256(bytes), "Chunk hash does not match");
|
||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/// Construct a `Snapshot` from an already fetched `DBSnapshot` row.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// dblite = database handle
|
||||||
|
/// dbSnapshot = snapshot row (metadata) previously retrieved
|
||||||
this(DBLite dblite, DBSnapshot dbSnapshot)
|
this(DBLite dblite, DBSnapshot dbSnapshot)
|
||||||
{
|
{
|
||||||
_db = dblite;
|
_db = dblite;
|
||||||
_snapshot = dbSnapshot;
|
_snapshot = dbSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a `Snapshot` by loading metadata from the database.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// dblite = database handle
|
||||||
|
/// idSnapshot = snapshot id to load
|
||||||
this(DBLite dblite, long idSnapshot)
|
this(DBLite dblite, long idSnapshot)
|
||||||
{
|
{
|
||||||
_db = dblite;
|
_db = dblite;
|
||||||
_snapshot = _db.getSnapshot(idSnapshot);
|
_snapshot = _db.getSnapshot(idSnapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Materialize the full file content in memory.
|
||||||
|
///
|
||||||
|
/// Reassembles all chunks in order, validates each chunk SHA-256 and the
|
||||||
|
/// final file SHA-256 (`snapshots.sha256`).
|
||||||
|
///
|
||||||
|
/// Returns: full file content as a newly allocated `ubyte[]`
|
||||||
|
///
|
||||||
|
/// Throws: Exception on any integrity check failure
|
||||||
ubyte[] data()
|
ubyte[] data()
|
||||||
{
|
{
|
||||||
auto chunks = _db.getChunks(_snapshot.id);
|
auto chunks = _db.getChunks(_snapshot.id);
|
||||||
|
@ -60,11 +100,20 @@ public:
|
||||||
fctx.put(bytes);
|
fctx.put(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
enforce(_snapshot.sha256 == fctx.finish(), "Хеш-сумма файла не совпадает");
|
enforce(_snapshot.sha256 == fctx.finish(), "File hash does not match");
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stream the full file content into a caller-provided sink.
|
||||||
|
///
|
||||||
|
/// This variant avoids allocating a single large buffer. Chunks are
|
||||||
|
/// decoded, verified, and passed to `sink` in order.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// sink = delegate invoked for each verified chunk (may be called many times)
|
||||||
|
///
|
||||||
|
/// Throws: Exception on any integrity check failure
|
||||||
void data(void delegate(const(ubyte)[]) sink)
|
void data(void delegate(const(ubyte)[]) sink)
|
||||||
{
|
{
|
||||||
auto chunks = _db.getChunks(_snapshot.id);
|
auto chunks = _db.getChunks(_snapshot.id);
|
||||||
|
@ -77,9 +126,17 @@ public:
|
||||||
fctx.put(bytes);
|
fctx.put(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
enforce(_snapshot.sha256 == fctx.finish(), "Хеш-сумма файла не совпадает");
|
enforce(_snapshot.sha256 == fctx.finish(), "File hash does not match");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove this snapshot from the database inside a transaction.
|
||||||
|
///
|
||||||
|
/// Starts an IMMEDIATE transaction, deletes the snapshot row, and commits.
|
||||||
|
/// On any failure it rolls back.
|
||||||
|
///
|
||||||
|
/// Returns: `true` if the snapshot row was deleted, `false` otherwise
|
||||||
|
///
|
||||||
|
/// Note: Does not garbage-collect unreferenced blobs; perform that separately.
|
||||||
bool remove()
|
bool remove()
|
||||||
{
|
{
|
||||||
_db.beginImmediate();
|
_db.beginImmediate();
|
||||||
|
@ -103,31 +160,37 @@ public:
|
||||||
return _snapshot.id == idDeleted;
|
return _snapshot.id == idDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Snapshot id (primary key).
|
||||||
@property long id() const nothrow @safe
|
@property long id() const nothrow @safe
|
||||||
{
|
{
|
||||||
return _snapshot.id;
|
return _snapshot.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// User-defined label.
|
||||||
@property string label() const @safe
|
@property string label() const @safe
|
||||||
{
|
{
|
||||||
return _snapshot.label;
|
return _snapshot.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creation timestamp (UTC) from the database.
|
||||||
@property DateTime created() const @safe
|
@property DateTime created() const @safe
|
||||||
{
|
{
|
||||||
return _snapshot.createdUtc;
|
return _snapshot.createdUtc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Original file length in bytes.
|
||||||
@property long length() const nothrow @safe
|
@property long length() const nothrow @safe
|
||||||
{
|
{
|
||||||
return _snapshot.sourceLength;
|
return _snapshot.sourceLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expected SHA-256 of the full file (32 raw bytes).
|
||||||
@property ubyte[32] sha256() const nothrow @safe
|
@property ubyte[32] sha256() const nothrow @safe
|
||||||
{
|
{
|
||||||
return _snapshot.sha256;
|
return _snapshot.sha256;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Snapshot status as a string (enum to string).
|
||||||
@property string status() const
|
@property string status() const
|
||||||
{
|
{
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
|
@ -135,6 +198,7 @@ public:
|
||||||
return _snapshot.status.to!string;
|
return _snapshot.status.to!string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optional human-readable description.
|
||||||
@property string description() const nothrow @safe
|
@property string description() const nothrow @safe
|
||||||
{
|
{
|
||||||
return _snapshot.description;
|
return _snapshot.description;
|
||||||
|
|
|
@ -6,14 +6,35 @@ import cdcdb.snapshot;
|
||||||
|
|
||||||
import zstd : compress, Level;
|
import zstd : compress, Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High-level storage facade: splits data into CDC chunks, stores chunks/blobs
|
||||||
|
* into SQLite via `DBLite`, links them into snapshots, and returns `Snapshot`
|
||||||
|
* objects for retrieval and deletion.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - FastCDC-based content-defined chunking (configurable sizes/masks)
|
||||||
|
* - Optional Zstandard compression (level configurable)
|
||||||
|
* - Idempotent snapshot creation: skips if identical to the latest for label
|
||||||
|
*
|
||||||
|
* Typical usage:
|
||||||
|
* ---
|
||||||
|
* auto store = new Storage("cdc.sqlite", true, Level.default_);
|
||||||
|
* store.setupCDC(4096, 8192, 16384, 0x3FFF, 0x03FF);
|
||||||
|
*
|
||||||
|
* auto snap = store.newSnapshot("my.txt", data, "initial import");
|
||||||
|
* auto bytes = snap.data(); // retrieve
|
||||||
|
*
|
||||||
|
* auto removed = store.removeSnapshots("my.txt"); // remove by label
|
||||||
|
* ---
|
||||||
|
*/
|
||||||
final class Storage
|
final class Storage
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// Параметры работы с базой данных
|
// Database parameters
|
||||||
DBLite _db;
|
DBLite _db;
|
||||||
bool _zstd;
|
bool _zstd;
|
||||||
int _level;
|
int _level;
|
||||||
// Настройки CDC механизма
|
// CDC settings
|
||||||
CDC _cdc;
|
CDC _cdc;
|
||||||
size_t _minSize;
|
size_t _minSize;
|
||||||
size_t _normalSize;
|
size_t _normalSize;
|
||||||
|
@ -29,11 +50,19 @@ private:
|
||||||
_maxSize = maxSize;
|
_maxSize = maxSize;
|
||||||
_maskS = maskS;
|
_maskS = maskS;
|
||||||
_maskL = maskL;
|
_maskL = maskL;
|
||||||
// CDC не хранит динамически выделенных данных, переинициализация безопасна
|
// CDC holds no dynamically allocated state; reinitialization is safe
|
||||||
_cdc = new CDC(_minSize, _normalSize, _maxSize, _maskS, _maskL);
|
_cdc = new CDC(_minSize, _normalSize, _maxSize, _maskS, _maskL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/// Construct the storage facade and open (or create) the database.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// database = path to SQLite file
|
||||||
|
/// zstd = enable Zstandard compression for stored blobs
|
||||||
|
/// level = Zstd compression level (see `zstd.Level`)
|
||||||
|
/// busyTimeout = SQLite busy timeout in milliseconds
|
||||||
|
/// maxRetries = max retries on SQLITE_BUSY/LOCKED errors
|
||||||
this(string database, bool zstd = false, int level = Level.base, size_t busyTimeout = 3000, size_t maxRetries = 3)
|
this(string database, bool zstd = false, int level = Level.base, size_t busyTimeout = 3000, size_t maxRetries = 3)
|
||||||
{
|
{
|
||||||
_db = new DBLite(database, busyTimeout, maxRetries);
|
_db = new DBLite(database, busyTimeout, maxRetries);
|
||||||
|
@ -42,23 +71,44 @@ public:
|
||||||
initCDC();
|
initCDC();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reconfigure CDC parameters (takes effect for subsequent snapshots).
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// minSize, normalSize, maxSize, maskS, maskL = FastCDC parameters
|
||||||
void setupCDC(size_t minSize, size_t normalSize, size_t maxSize, size_t maskS, size_t maskL)
|
void setupCDC(size_t minSize, size_t normalSize, size_t maxSize, size_t maskS, size_t maskL)
|
||||||
{
|
{
|
||||||
initCDC(minSize, normalSize, maxSize, maskS, maskL);
|
initCDC(minSize, normalSize, maxSize, maskS, maskL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new snapshot from raw data.
|
||||||
|
///
|
||||||
|
/// - Splits data with FastCDC using current settings.
|
||||||
|
/// - Optionally compresses chunks with Zstd.
|
||||||
|
/// - Stores unique blobs and links them to the created snapshot.
|
||||||
|
/// - If the latest snapshot for `label` already has the same file SHA-256,
|
||||||
|
/// returns `null` (idempotent).
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// label = user-provided snapshot label (file identifier)
|
||||||
|
/// data = raw file bytes
|
||||||
|
/// description = optional human-readable description
|
||||||
|
///
|
||||||
|
/// Returns: a `Snapshot` instance for the created snapshot, or `null`
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// Exception if `data` is empty or on database/storage errors
|
||||||
Snapshot newSnapshot(string label, const(ubyte)[] data, string description = string.init)
|
Snapshot newSnapshot(string label, const(ubyte)[] data, string description = string.init)
|
||||||
{
|
{
|
||||||
if (data.length == 0)
|
if (data.length == 0)
|
||||||
{
|
{
|
||||||
throw new Exception("Данные имеют нулевой размер");
|
throw new Exception("Data has zero length");
|
||||||
}
|
}
|
||||||
|
|
||||||
import std.digest.sha : SHA256, digest;
|
import std.digest.sha : SHA256, digest;
|
||||||
|
|
||||||
ubyte[32] sha256 = digest!SHA256(data);
|
ubyte[32] sha256 = digest!SHA256(data);
|
||||||
|
|
||||||
// Если последний снимок файла соответствует текущему состоянию
|
// If the last snapshot for the label matches current content
|
||||||
if (_db.isLast(label, sha256))
|
if (_db.isLast(label, sha256))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -95,10 +145,10 @@ public:
|
||||||
|
|
||||||
dbBlob.zstd = _zstd;
|
dbBlob.zstd = _zstd;
|
||||||
|
|
||||||
// Разбить на фрагменты
|
// Split into chunks
|
||||||
Chunk[] chunks = _cdc.split(data);
|
Chunk[] chunks = _cdc.split(data);
|
||||||
|
|
||||||
// Запись фрагментов в БД
|
// Write chunks to DB
|
||||||
foreach (chunk; chunks)
|
foreach (chunk; chunks)
|
||||||
{
|
{
|
||||||
dbBlob.sha256 = chunk.sha256;
|
dbBlob.sha256 = chunk.sha256;
|
||||||
|
@ -118,7 +168,7 @@ public:
|
||||||
dbBlob.content = content.dup;
|
dbBlob.content = content.dup;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запись фрагментов
|
// Store/ensure blob
|
||||||
_db.addBlob(dbBlob);
|
_db.addBlob(dbBlob);
|
||||||
|
|
||||||
dbSnapshotChunk.snapshotId = idSnapshot;
|
dbSnapshotChunk.snapshotId = idSnapshot;
|
||||||
|
@ -126,7 +176,7 @@ public:
|
||||||
dbSnapshotChunk.offset = chunk.offset;
|
dbSnapshotChunk.offset = chunk.offset;
|
||||||
dbSnapshotChunk.sha256 = chunk.sha256;
|
dbSnapshotChunk.sha256 = chunk.sha256;
|
||||||
|
|
||||||
// Привязка фрагментов к снимку
|
// Link chunk to snapshot
|
||||||
_db.addSnapshotChunk(dbSnapshotChunk);
|
_db.addSnapshotChunk(dbSnapshotChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,23 +187,52 @@ public:
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаляет снимок по метке, возвращает количество удаленных снимков
|
/// Delete snapshots by label.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// label = snapshot label
|
||||||
|
///
|
||||||
|
/// Returns: number of deleted snapshots
|
||||||
long removeSnapshots(string label) {
|
long removeSnapshots(string label) {
|
||||||
return _db.deleteSnapshot(label);
|
return _db.deleteSnapshot(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete a specific snapshot instance.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// snapshot = `Snapshot` to remove
|
||||||
|
///
|
||||||
|
/// Returns: `true` on success, `false` otherwise
|
||||||
bool removeSnapshots(Snapshot snapshot) {
|
bool removeSnapshots(Snapshot snapshot) {
|
||||||
return removeSnapshots(snapshot.id);
|
return removeSnapshots(snapshot.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete a snapshot by id.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// idSnapshot = snapshot id
|
||||||
|
///
|
||||||
|
/// Returns: `true` if the row was deleted
|
||||||
bool removeSnapshots(long idSnapshot) {
|
bool removeSnapshots(long idSnapshot) {
|
||||||
return _db.deleteSnapshot(idSnapshot) == idSnapshot;
|
return _db.deleteSnapshot(idSnapshot) == idSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a `Snapshot` object by id.
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// idSnapshot = snapshot id
|
||||||
|
///
|
||||||
|
/// Returns: `Snapshot` handle (metadata loaded lazily via constructor)
|
||||||
Snapshot getSnapshot(long idSnapshot) {
|
Snapshot getSnapshot(long idSnapshot) {
|
||||||
return new Snapshot(_db, idSnapshot);
|
return new Snapshot(_db, idSnapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List snapshots (optionally filtered by label).
|
||||||
|
///
|
||||||
|
/// Params:
|
||||||
|
/// label = filter by exact label; empty string returns all
|
||||||
|
///
|
||||||
|
/// Returns: array of `Snapshot` handles
|
||||||
Snapshot[] getSnapshots(string label = string.init) {
|
Snapshot[] getSnapshots(string label = string.init) {
|
||||||
Snapshot[] snapshots;
|
Snapshot[] snapshots;
|
||||||
|
|
||||||
|
@ -164,6 +243,9 @@ public:
|
||||||
return snapshots;
|
return snapshots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Library version string.
|
||||||
|
///
|
||||||
|
/// Returns: semantic version of the `cdcdb` library
|
||||||
string getVersion() const @safe nothrow
|
string getVersion() const @safe nothrow
|
||||||
{
|
{
|
||||||
import cdcdb.version_ : cdcdbVersion;
|
import cdcdb.version_ : cdcdbVersion;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue