1
0
Fork 0
forked from dlang/cdcdb

Compare commits

...
Sign in to create a new pull request.

3 commits

8 changed files with 127 additions and 33 deletions

View file

@ -1,5 +1,11 @@
# Changelog # Changelog
## [0.1.1] - 2025-09-14
### Added
- Table `labels` for snapshot labels.
### Fixed
- Improved data integrity with label normalization.
## [0.1.0] — 2025-09-13 ## [0.1.0] — 2025-09-13
### Added ### Added
- SQLite-backed snapshot library with content-defined chunking (FastCDC). - SQLite-backed snapshot library with content-defined chunking (FastCDC).

View file

@ -1,6 +1,12 @@
# Changelog # Changelog
## [0.1.0] — 2025-09-13 ## [0.1.1] - 2025-09-14
### Added
- Таблица `labels` для меток снимков.
### Fixed
- Улучшена целостность данных при нормализации меток.
## [0.1.0] - 2025-09-13
### Added ### Added
- Библиотека для снимков данных на базе SQLite с контентно-зависимым разбиением (FastCDC). - Библиотека для снимков данных на базе SQLite с контентно-зависимым разбиением (FastCDC).
- Дедупликация по SHA-256 чанков, опциональная компрессия Zstd. - Дедупликация по SHA-256 чанков, опциональная компрессия Zstd.

View file

@ -92,7 +92,7 @@ chmod +x ./tools/gen.d
```json ```json
"dependencies": { "dependencies": {
"cdcdb": "~>0.1.0" "cdcdb": "~>0.1"
} }
``` ```
* **Build**: `dub build`. * **Build**: `dub build`.

View file

@ -89,7 +89,7 @@ chmod +x ./tools/gen.d
- **В `dub.json`**: - **В `dub.json`**:
```json ```json
"dependencies": { "dependencies": {
"cdcdb": "~>0.1.0" "cdcdb": "~>0.1"
} }
``` ```
- **Сборка**: `dub build`. - **Сборка**: `dub build`.

View file

@ -104,7 +104,7 @@ private:
{ {
SqliteResult queryResult = sql( SqliteResult queryResult = sql(
q{ q{
WITH required(name) AS (VALUES ("snapshots"), ("blobs"), ("snapshot_chunks")) WITH required(name) AS (VALUES ("snapshots"), ("blobs"), ("snapshot_chunks"), ("labels"))
SELECT name AS missing_table SELECT name AS missing_table
FROM required FROM required
WHERE NOT EXISTS ( WHERE NOT EXISTS (
@ -122,11 +122,11 @@ private:
missingTables ~= row["missing_table"].to!string; missingTables ~= row["missing_table"].to!string;
} }
enforce(missingTables.length == 0 || missingTables.length == 3, enforce(missingTables.length == 0 || missingTables.length == 4,
"Database is corrupted. Missing tables: " ~ missingTables.join(", ") "Database is corrupted. Missing tables: " ~ missingTables.join(", ")
); );
if (missingTables.length == 3) if (missingTables.length == 4)
{ {
foreach (schemeQuery; _scheme) foreach (schemeQuery; _scheme)
{ {
@ -176,10 +176,14 @@ public:
auto queryResult = sql( auto queryResult = sql(
q{ q{
SELECT COALESCE( SELECT COALESCE(
(SELECT (label = ? AND sha256 = ?) (
FROM snapshots SELECT (s.sha256 = ?2)
ORDER BY created_utc DESC FROM snapshots s
LIMIT 1), JOIN labels l ON l.id = s.label
WHERE l.name = ?1
ORDER BY s.created_utc DESC
LIMIT 1
),
0 0
) AS is_last; ) AS is_last;
}, label, sha256 }, label, sha256
@ -205,7 +209,10 @@ public:
mask_s, mask_s,
mask_l, mask_l,
status status
) VALUES (?,?,?,?,?,?,?,?,?,?) )
SELECT
(SELECT id FROM labels WHERE name = ?),
?,?,?,?,?,?,?,?,?
RETURNING id RETURNING id
}, },
snapshot.label, snapshot.label,
@ -247,6 +254,18 @@ public:
return !queryResult.empty(); return !queryResult.empty();
} }
bool addLabel(string name)
{
auto queryResult = sql(
q{
INSERT INTO labels (name) VALUES (?)
ON CONFLICT(name) DO NOTHING
}, name
);
return !queryResult.empty();
}
bool addSnapshotChunk(DBSnapshotChunk snapshotChunk) bool addSnapshotChunk(DBSnapshotChunk snapshotChunk)
{ {
auto queryResult = sql( auto queryResult = sql(
@ -268,9 +287,22 @@ public:
{ {
auto queryResult = sql( auto queryResult = sql(
q{ q{
SELECT id, label, sha256, description, created_utc, source_length, SELECT
algo_min, algo_normal, algo_max, mask_s, mask_l, status s.id,
FROM snapshots WHERE id = ? l.name label,
s.sha256,
s.description,
s.created_utc,
s.source_length,
s.algo_min,
s.algo_normal,
s.algo_max,
s.mask_s,
s.mask_l,
s.status
FROM snapshots s
JOIN labels l ON l.id = s.label
WHERE s.id = ?
}, id }, id
); );
@ -301,9 +333,22 @@ public:
{ {
auto queryResult = sql( auto queryResult = sql(
q{ q{
SELECT id, label, sha256, description, created_utc, source_length, SELECT
algo_min, algo_normal, algo_max, mask_s, mask_l, status s.id,
FROM snapshots WHERE (length(?) = 0 OR label = ?1); l.name label,
s.sha256,
s.description,
s.created_utc,
s.source_length,
s.algo_min,
s.algo_normal,
s.algo_max,
s.mask_s,
s.mask_l,
s.status
FROM snapshots s
JOIN labels l ON l.id = s.label AND (length(?) = 0 OR l.name = ?1)
ORDER BY s.created_utc, s.id;
}, label }, label
); );
@ -379,7 +424,7 @@ public:
auto queryResult = sql( auto queryResult = sql(
q{ q{
DELETE FROM snapshots DELETE FROM snapshots
WHERE label = ? WHERE label = (SELECT id FROM labels WHERE name = ?)
RETURNING 1; RETURNING 1;
}, label); }, label);

View file

@ -1,4 +1,20 @@
auto _scheme = [ auto _scheme = [
q{
-- ------------------------------------------------------------
-- Таблица labels
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS labels (
-- идентификатор метки
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- имя метки
name TEXT NOT NULL UNIQUE
)
},
q{
-- Индекс по имени метки
CREATE INDEX IF NOT EXISTS idx_labels_name
ON labels(name)
},
q{ q{
-- ------------------------------------------------------------ -- ------------------------------------------------------------
-- Таблица snapshots -- Таблица snapshots
@ -7,7 +23,7 @@ auto _scheme = [
-- идентификатор снимка -- идентификатор снимка
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
-- метка/название снимка -- метка/название снимка
label TEXT NOT NULL, label INTEGER NOT NULL,
-- SHA-256 всего файла (BLOB(32)) -- SHA-256 всего файла (BLOB(32))
sha256 BLOB NOT NULL CHECK (length(sha256) = 32), sha256 BLOB NOT NULL CHECK (length(sha256) = 32),
-- Комментарий/описание -- Комментарий/описание
@ -28,7 +44,11 @@ auto _scheme = [
mask_l INTEGER NOT NULL, mask_l INTEGER NOT NULL,
-- 0=pending, 1=ready -- 0=pending, 1=ready
status INTEGER NOT NULL DEFAULT 0 status INTEGER NOT NULL DEFAULT 0
CHECK (status IN (0,1)) CHECK (status IN (0,1)),
FOREIGN KEY (label)
REFERENCES labels(id)
ON UPDATE CASCADE
ON DELETE CASCADE
) )
}, },
q{ q{
@ -249,5 +269,20 @@ auto _scheme = [
BEGIN BEGIN
SELECT RAISE(ABORT, "blobs: разрешён UPDATE только полей last_seen_utc и refcount"); SELECT RAISE(ABORT, "blobs: разрешён UPDATE только полей last_seen_utc и refcount");
END END
},
q{
-- ------------------------------------------------------------
-- Удаление записи из labels, если удалён последний snapshot
-- ------------------------------------------------------------
CREATE TRIGGER IF NOT EXISTS trg_snapshots_delete_label
AFTER DELETE ON snapshots
FOR EACH ROW
BEGIN
DELETE FROM labels
WHERE id = OLD.label
AND NOT EXISTS (
SELECT 1 FROM snapshots WHERE label = OLD.label
);
END;
} }
]; ];

View file

@ -112,18 +112,6 @@ public:
if (_db.isLast(label, sha256)) if (_db.isLast(label, sha256))
return null; return null;
DBSnapshot dbSnapshot;
dbSnapshot.label = label;
dbSnapshot.sha256 = sha256;
dbSnapshot.description = description;
dbSnapshot.sourceLength = data.length;
dbSnapshot.algoMin = _minSize;
dbSnapshot.algoNormal = _normalSize;
dbSnapshot.algoMax = _maxSize;
dbSnapshot.maskS = _maskS;
dbSnapshot.maskL = _maskL;
_db.beginImmediate(); _db.beginImmediate();
bool ok; bool ok;
@ -138,6 +126,20 @@ public:
_db.commit(); _db.commit();
} }
_db.addLabel(label);
DBSnapshot dbSnapshot;
dbSnapshot.label = label;
dbSnapshot.sha256 = sha256;
dbSnapshot.description = description;
dbSnapshot.sourceLength = data.length;
dbSnapshot.algoMin = _minSize;
dbSnapshot.algoNormal = _normalSize;
dbSnapshot.algoMax = _maxSize;
dbSnapshot.maskS = _maskS;
dbSnapshot.maskL = _maskL;
auto idSnapshot = _db.addSnapshot(dbSnapshot); auto idSnapshot = _db.addSnapshot(dbSnapshot);
DBSnapshotChunk dbSnapshotChunk; DBSnapshotChunk dbSnapshotChunk;

View file

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