forked from dlang/cdcdb
		
	Compare commits
	
		
			3 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 872e29cba1 | |||
| c9623f87e8 | |||
| 48a9c39caa | 
					 8 changed files with 127 additions and 33 deletions
				
			
		| 
						 | 
				
			
			@ -1,5 +1,11 @@
 | 
			
		|||
# 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
 | 
			
		||||
### Added
 | 
			
		||||
- SQLite-backed snapshot library with content-defined chunking (FastCDC).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,12 @@
 | 
			
		|||
# Changelog
 | 
			
		||||
 | 
			
		||||
## [0.1.0] — 2025-09-13
 | 
			
		||||
## [0.1.1] - 2025-09-14
 | 
			
		||||
### Added
 | 
			
		||||
- Таблица `labels` для меток снимков.
 | 
			
		||||
### Fixed
 | 
			
		||||
- Улучшена целостность данных при нормализации меток.
 | 
			
		||||
 | 
			
		||||
## [0.1.0] - 2025-09-13
 | 
			
		||||
### Added
 | 
			
		||||
- Библиотека для снимков данных на базе SQLite с контентно-зависимым разбиением (FastCDC).
 | 
			
		||||
- Дедупликация по SHA-256 чанков, опциональная компрессия Zstd.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,7 +92,7 @@ chmod +x ./tools/gen.d
 | 
			
		|||
 | 
			
		||||
	```json
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"cdcdb": "~>0.1.0"
 | 
			
		||||
		"cdcdb": "~>0.1"
 | 
			
		||||
	}
 | 
			
		||||
	```
 | 
			
		||||
* **Build**: `dub build`.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,7 +89,7 @@ chmod +x ./tools/gen.d
 | 
			
		|||
- **В `dub.json`**:
 | 
			
		||||
	```json
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"cdcdb": "~>0.1.0"
 | 
			
		||||
		"cdcdb": "~>0.1"
 | 
			
		||||
	}
 | 
			
		||||
	```
 | 
			
		||||
- **Сборка**: `dub build`.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -104,7 +104,7 @@ private:
 | 
			
		|||
	{
 | 
			
		||||
		SqliteResult queryResult = sql(
 | 
			
		||||
			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
 | 
			
		||||
				FROM required
 | 
			
		||||
				WHERE NOT EXISTS (
 | 
			
		||||
| 
						 | 
				
			
			@ -122,11 +122,11 @@ private:
 | 
			
		|||
			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(", ")
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (missingTables.length == 3)
 | 
			
		||||
		if (missingTables.length == 4)
 | 
			
		||||
		{
 | 
			
		||||
			foreach (schemeQuery; _scheme)
 | 
			
		||||
			{
 | 
			
		||||
| 
						 | 
				
			
			@ -176,10 +176,14 @@ public:
 | 
			
		|||
		auto queryResult = sql(
 | 
			
		||||
			q{
 | 
			
		||||
				SELECT COALESCE(
 | 
			
		||||
					(SELECT (label = ? AND sha256 = ?)
 | 
			
		||||
					FROM snapshots
 | 
			
		||||
					ORDER BY created_utc DESC
 | 
			
		||||
					LIMIT 1),
 | 
			
		||||
					(
 | 
			
		||||
						SELECT (s.sha256 = ?2)
 | 
			
		||||
						FROM snapshots s
 | 
			
		||||
						JOIN labels l ON l.id = s.label
 | 
			
		||||
						WHERE l.name = ?1
 | 
			
		||||
						ORDER BY s.created_utc DESC
 | 
			
		||||
						LIMIT 1
 | 
			
		||||
					),
 | 
			
		||||
					0
 | 
			
		||||
				) AS is_last;
 | 
			
		||||
			}, label, sha256
 | 
			
		||||
| 
						 | 
				
			
			@ -205,7 +209,10 @@ public:
 | 
			
		|||
					mask_s,
 | 
			
		||||
					mask_l,
 | 
			
		||||
					status
 | 
			
		||||
				) VALUES (?,?,?,?,?,?,?,?,?,?)
 | 
			
		||||
				)
 | 
			
		||||
				SELECT
 | 
			
		||||
					(SELECT id FROM labels WHERE name = ?),
 | 
			
		||||
					?,?,?,?,?,?,?,?,?
 | 
			
		||||
				RETURNING id
 | 
			
		||||
			},
 | 
			
		||||
			snapshot.label,
 | 
			
		||||
| 
						 | 
				
			
			@ -247,6 +254,18 @@ public:
 | 
			
		|||
		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)
 | 
			
		||||
	{
 | 
			
		||||
		auto queryResult = sql(
 | 
			
		||||
| 
						 | 
				
			
			@ -268,9 +287,22 @@ public:
 | 
			
		|||
	{
 | 
			
		||||
		auto queryResult = sql(
 | 
			
		||||
			q{
 | 
			
		||||
				SELECT id, label, sha256, description, created_utc, source_length,
 | 
			
		||||
					algo_min, algo_normal, algo_max, mask_s, mask_l, status
 | 
			
		||||
				FROM snapshots WHERE id = ?
 | 
			
		||||
				SELECT
 | 
			
		||||
					s.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
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -301,9 +333,22 @@ public:
 | 
			
		|||
	{
 | 
			
		||||
		auto queryResult = sql(
 | 
			
		||||
			q{
 | 
			
		||||
				SELECT id, label, sha256, description, created_utc, source_length,
 | 
			
		||||
					algo_min, algo_normal, algo_max, mask_s, mask_l, status
 | 
			
		||||
				FROM snapshots WHERE (length(?) = 0 OR label = ?1);
 | 
			
		||||
				SELECT
 | 
			
		||||
					s.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 AND (length(?) = 0 OR l.name = ?1)
 | 
			
		||||
				ORDER BY s.created_utc, s.id;
 | 
			
		||||
			}, label
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -379,7 +424,7 @@ public:
 | 
			
		|||
		auto queryResult = sql(
 | 
			
		||||
			q{
 | 
			
		||||
				DELETE FROM snapshots
 | 
			
		||||
				WHERE label = ?
 | 
			
		||||
				WHERE label = (SELECT id FROM labels WHERE name = ?)
 | 
			
		||||
				RETURNING 1;
 | 
			
		||||
			}, label);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,20 @@
 | 
			
		|||
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{
 | 
			
		||||
		-- ------------------------------------------------------------
 | 
			
		||||
		-- Таблица snapshots
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +23,7 @@ auto _scheme = [
 | 
			
		|||
			-- идентификатор снимка
 | 
			
		||||
			id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
			
		||||
			-- метка/название снимка
 | 
			
		||||
			label TEXT NOT NULL,
 | 
			
		||||
			label INTEGER NOT NULL,
 | 
			
		||||
			-- SHA-256 всего файла (BLOB(32))
 | 
			
		||||
			sha256 BLOB NOT NULL CHECK (length(sha256) = 32),
 | 
			
		||||
			-- Комментарий/описание
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +44,11 @@ auto _scheme = [
 | 
			
		|||
			mask_l INTEGER NOT NULL,
 | 
			
		||||
			-- 0=pending, 1=ready
 | 
			
		||||
			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{
 | 
			
		||||
| 
						 | 
				
			
			@ -249,5 +269,20 @@ auto _scheme = [
 | 
			
		|||
		BEGIN
 | 
			
		||||
			SELECT RAISE(ABORT, "blobs: разрешён UPDATE только полей last_seen_utc и refcount");
 | 
			
		||||
		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;
 | 
			
		||||
	}
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -112,18 +112,6 @@ public:
 | 
			
		|||
		if (_db.isLast(label, sha256))
 | 
			
		||||
			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();
 | 
			
		||||
 | 
			
		||||
		bool ok;
 | 
			
		||||
| 
						 | 
				
			
			@ -138,6 +126,20 @@ public:
 | 
			
		|||
			_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);
 | 
			
		||||
 | 
			
		||||
		DBSnapshotChunk dbSnapshotChunk;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,3 @@
 | 
			
		|||
module cdcdb.version_;
 | 
			
		||||
 | 
			
		||||
enum cdcdbVersion = "0.1.0";
 | 
			
		||||
enum cdcdbVersion = "0.1.1";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue