Время фиксируется в Unix Timestamp метке с миллисекундами. Добавлена новая сущеность StorageFile, который является отправной точкой для работы со снимками. Расширен интерфейс Storage - добавлены дополнительные фозможности для работы со снимками. Введены структуры: для работы со временм - UTS, для работы с идентификатором - Identifier.
342 lines
11 KiB
D
342 lines
11 KiB
D
auto _scheme = [
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- Таблица users
|
||
-- ------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
-- Linux UID
|
||
uid INTEGER PRIMARY KEY,
|
||
-- текстовое представление пользователя
|
||
name TEXT NOT NULL UNIQUE
|
||
)
|
||
},
|
||
q{
|
||
-- Индекс по имени пользователя
|
||
CREATE INDEX IF NOT EXISTS idx_users_name
|
||
ON users(name)
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- Таблица processes
|
||
-- ------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS processes (
|
||
-- идентификатор процесса
|
||
id BLOB PRIMARY KEY,
|
||
-- имя процесса
|
||
name TEXT NOT NULL UNIQUE
|
||
) WITHOUT ROWID
|
||
},
|
||
q{
|
||
-- Индекс по имени процесса
|
||
CREATE INDEX IF NOT EXISTS idx_processes_name
|
||
ON processes(name)
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- Таблица files
|
||
-- ------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS files (
|
||
-- идентификатор файла
|
||
id BLOB PRIMARY KEY,
|
||
-- имя файла
|
||
name TEXT NOT NULL UNIQUE
|
||
) WITHOUT ROWID
|
||
},
|
||
q{
|
||
-- Индекс по имени файла
|
||
CREATE INDEX IF NOT EXISTS idx_files_name
|
||
ON files(name)
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- Таблица snapshots
|
||
-- ------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS snapshots (
|
||
-- идентификатор снимка
|
||
id BLOB PRIMARY KEY,
|
||
-- Файл
|
||
file BLOB NOT NULL,
|
||
-- SHA-256 всего файла (BLOB(32))
|
||
sha256 BLOB NOT NULL CHECK (length(sha256) = 32),
|
||
-- Комментарий/описание
|
||
description TEXT DEFAULT NULL,
|
||
-- время создания (UTC)
|
||
created_utc INTEGER NOT NULL,
|
||
-- длина исходного файла в байтах
|
||
source_length INTEGER NOT NULL,
|
||
-- UID пользователя (эффективный)
|
||
uid INTEGER NOT NULL,
|
||
-- RUID пользователя (реальный)
|
||
ruid INTEGER NOT NULL,
|
||
-- Процесс
|
||
process BLOB NOT NULL,
|
||
-- FastCDC: минимальный размер чанка
|
||
algo_min INTEGER NOT NULL,
|
||
-- FastCDC: целевой размер чанка
|
||
algo_normal INTEGER NOT NULL,
|
||
-- FastCDC: максимальный размер чанка
|
||
algo_max INTEGER NOT NULL,
|
||
-- FastCDC: маска S
|
||
mask_s INTEGER NOT NULL,
|
||
-- FastCDC: маска L
|
||
mask_l INTEGER NOT NULL,
|
||
-- 0=pending, 1=ready
|
||
status INTEGER NOT NULL DEFAULT 0
|
||
CHECK (status IN (0,1)),
|
||
-- Внешние ключи
|
||
FOREIGN KEY (uid)
|
||
REFERENCES users(uid)
|
||
ON UPDATE RESTRICT
|
||
ON DELETE RESTRICT,
|
||
FOREIGN KEY (ruid)
|
||
REFERENCES users(uid)
|
||
ON UPDATE RESTRICT
|
||
ON DELETE RESTRICT,
|
||
FOREIGN KEY (process)
|
||
REFERENCES processes(id)
|
||
ON UPDATE RESTRICT
|
||
ON DELETE RESTRICT
|
||
FOREIGN KEY (file)
|
||
REFERENCES files(id)
|
||
ON UPDATE CASCADE
|
||
ON DELETE CASCADE
|
||
) WITHOUT ROWID
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- Таблица blobs
|
||
-- ------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS blobs (
|
||
-- SHA-256 исходного содержимого (BLOB(32))
|
||
sha256 BLOB PRIMARY KEY CHECK (length(sha256) = 32),
|
||
-- SHA-256 сжатого содержимого (BLOB(32)) или NULL
|
||
z_sha256 BLOB,
|
||
-- размер исходного содержимого, байт
|
||
size INTEGER NOT NULL,
|
||
-- размер сжатого содержимого, байт
|
||
z_size INTEGER NOT NULL,
|
||
-- байты (сжатые при zstd=1, иначе исходные)
|
||
content BLOB NOT NULL,
|
||
-- время создания записи (UTC)
|
||
created_utc INTEGER NOT NULL,
|
||
-- время последней ссылки (UTC)
|
||
last_seen_utc INTEGER NOT NULL,
|
||
-- число ссылок из snapshot_chunks
|
||
refcount INTEGER NOT NULL DEFAULT 0,
|
||
-- 0=нет сжатия, 1=zstd
|
||
zstd INTEGER NOT NULL DEFAULT 0
|
||
CHECK (zstd IN (0,1)),
|
||
CHECK (refcount >= 0),
|
||
CHECK (
|
||
(zstd = 1 AND length(content) = z_size) OR
|
||
(zstd = 0 AND length(content) = size)
|
||
),
|
||
CHECK (z_sha256 IS NULL OR length(z_sha256) = 32)
|
||
) WITHOUT ROWID
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- Таблица snapshot_chunks
|
||
-- ------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS snapshot_chunks (
|
||
-- FK -> snapshots.id
|
||
snapshot_id BLOB NOT NULL,
|
||
-- порядковый номер чанка в снимке
|
||
chunk_index INTEGER NOT NULL,
|
||
-- смещение чанка в исходном файле, байт
|
||
offset INTEGER,
|
||
-- FK -> blobs.sha256 (BLOB(32))
|
||
sha256 BLOB NOT NULL,
|
||
PRIMARY KEY (snapshot_id, chunk_index),
|
||
FOREIGN KEY (snapshot_id)
|
||
REFERENCES snapshots(id)
|
||
ON UPDATE CASCADE
|
||
ON DELETE CASCADE,
|
||
FOREIGN KEY (sha256)
|
||
REFERENCES blobs(sha256)
|
||
ON UPDATE RESTRICT
|
||
ON DELETE RESTRICT
|
||
) WITHOUT ROWID
|
||
},
|
||
q{
|
||
-- Индекс для запросов вида: WHERE file=? AND sha256=?
|
||
CREATE INDEX IF NOT EXISTS idx_snapshots_path_sha
|
||
ON snapshots(file, sha256)
|
||
},
|
||
q{
|
||
-- Индекс для обратного поиска использования blob по sha256
|
||
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha
|
||
ON snapshot_chunks(sha256)
|
||
},
|
||
// ------------------------------------------------------------
|
||
// Триггеры на поддержание refcount и статуса снимка
|
||
// ------------------------------------------------------------
|
||
q{
|
||
-- AFTER INSERT: увеличить refcount и обновить 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 = cast(unixepoch("subsecond") * 1000 as int)
|
||
WHERE sha256 = NEW.sha256;
|
||
END
|
||
},
|
||
q{
|
||
-- AFTER DELETE: уменьшить refcount и удалить blob при refcount <= 0
|
||
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{
|
||
-- AFTER UPDATE OF sha256: корректировка счётчиков при смене ссылки
|
||
CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_au
|
||
AFTER UPDATE OF sha256 ON snapshot_chunks
|
||
FOR EACH ROW
|
||
WHEN NEW.sha256 <> OLD.sha256
|
||
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 = cast(unixepoch("subsecond") * 1000 as int)
|
||
WHERE sha256 = NEW.sha256;
|
||
END
|
||
},
|
||
q{
|
||
-- AFTER INSERT: установить status=1 при совпадении суммы размеров с source_length
|
||
CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_ready
|
||
AFTER INSERT ON snapshot_chunks
|
||
BEGIN
|
||
UPDATE snapshots
|
||
SET status = 1
|
||
WHERE id = NEW.snapshot_id
|
||
AND (SELECT COALESCE(SUM(b.size),0)
|
||
FROM snapshot_chunks sc
|
||
JOIN blobs b ON b.sha256 = sc.sha256
|
||
WHERE sc.snapshot_id = NEW.snapshot_id)
|
||
= (SELECT source_length FROM snapshots WHERE id = NEW.snapshot_id);
|
||
END
|
||
},
|
||
q{
|
||
-- AFTER DELETE: установить status=0 для снимка, из которого удалён чанк
|
||
CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_pending
|
||
AFTER DELETE ON snapshot_chunks
|
||
BEGIN
|
||
UPDATE snapshots
|
||
SET status = 0
|
||
WHERE id = OLD.snapshot_id;
|
||
END
|
||
},
|
||
q{
|
||
-- Проверка порядка индексов и непрерывности смещений.
|
||
CREATE TRIGGER IF NOT EXISTS trg_sc_before_insert
|
||
BEFORE INSERT ON snapshot_chunks
|
||
BEGIN
|
||
-- Ожидаемое значение: max(chunk_index)+1 для данного snapshot_id (или текущий при первой вставке).
|
||
SELECT CASE
|
||
WHEN NEW.chunk_index <> COALESCE(
|
||
(SELECT MAX(chunk_index) FROM snapshot_chunks WHERE snapshot_id = NEW.snapshot_id),
|
||
NEW.chunk_index - 1
|
||
) + 1
|
||
THEN RAISE(ABORT, "snapshot_chunks: индекс chunk_index должен быть непрерывным и только возрастающим")
|
||
END;
|
||
|
||
-- Проверка: offset равен сумме размеров предыдущих чанков.
|
||
SELECT CASE
|
||
WHEN NEW.offset <> (
|
||
SELECT COALESCE(SUM(b.size), 0)
|
||
FROM snapshot_chunks sc
|
||
JOIN blobs b ON b.sha256 = sc.sha256
|
||
WHERE sc.snapshot_id = NEW.snapshot_id
|
||
AND sc.chunk_index < NEW.chunk_index
|
||
)
|
||
THEN RAISE(ABORT, "snapshot_chunks: offset должен равняться сумме размеров предыдущих чанков")
|
||
END;
|
||
END
|
||
},
|
||
q{
|
||
-- Запрет обновления строк состава; использовать DELETE + INSERT.
|
||
CREATE TRIGGER IF NOT EXISTS trg_sc_block_update
|
||
BEFORE UPDATE ON snapshot_chunks
|
||
BEGIN
|
||
SELECT RAISE(ABORT, "snapshot_chunks: UPDATE запрещён");
|
||
END
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- snapshots: можно менять только status
|
||
-- ------------------------------------------------------------
|
||
CREATE TRIGGER IF NOT EXISTS trg_snapshots_block_update
|
||
BEFORE UPDATE ON snapshots
|
||
FOR EACH ROW
|
||
WHEN
|
||
NEW.id IS NOT OLD.id OR
|
||
NEW.file IS NOT OLD.file OR
|
||
NEW.sha256 IS NOT OLD.sha256 OR
|
||
NEW.description IS NOT OLD.description OR
|
||
NEW.created_utc IS NOT OLD.created_utc OR
|
||
NEW.source_length IS NOT OLD.source_length OR
|
||
NEW.uid IS NOT OLD.uid OR
|
||
NEW.ruid IS NOT OLD.ruid OR
|
||
NEW.process IS NOT OLD.process OR
|
||
NEW.algo_min IS NOT OLD.algo_min OR
|
||
NEW.algo_normal IS NOT OLD.algo_normal OR
|
||
NEW.algo_max IS NOT OLD.algo_max OR
|
||
NEW.mask_s IS NOT OLD.mask_s OR
|
||
NEW.mask_l IS NOT OLD.mask_l
|
||
-- status менять разрешено, поэтому его не сравниваем
|
||
BEGIN
|
||
SELECT RAISE(ABORT, "snapshots: разрешён UPDATE только поля status");
|
||
END
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- blobs: можно менять только last_seen_utc и refcount
|
||
-- ------------------------------------------------------------
|
||
CREATE TRIGGER IF NOT EXISTS trg_blobs_block_update
|
||
BEFORE UPDATE ON blobs
|
||
FOR EACH ROW
|
||
WHEN
|
||
NEW.sha256 IS NOT OLD.sha256 OR
|
||
NEW.z_sha256 IS NOT OLD.z_sha256 OR
|
||
NEW.size IS NOT OLD.size OR
|
||
NEW.z_size IS NOT OLD.z_size OR
|
||
NEW.content IS NOT OLD.content OR
|
||
NEW.created_utc IS NOT OLD.created_utc OR
|
||
NEW.zstd IS NOT OLD.zstd
|
||
-- last_seen_utc и refcount менять разрешено
|
||
BEGIN
|
||
SELECT RAISE(ABORT, "blobs: разрешён UPDATE только полей last_seen_utc и refcount");
|
||
END
|
||
},
|
||
q{
|
||
-- ------------------------------------------------------------
|
||
-- Удаление записи из files, если удалён последний snapshot
|
||
-- ------------------------------------------------------------
|
||
CREATE TRIGGER IF NOT EXISTS trg_snapshots_delete_file
|
||
AFTER DELETE ON snapshots
|
||
FOR EACH ROW
|
||
BEGIN
|
||
DELETE FROM files
|
||
WHERE id = OLD.file
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM snapshots WHERE file = OLD.file
|
||
);
|
||
END;
|
||
}
|
||
];
|