1
0
Fork 0
forked from dlang/cdcdb

Переход на другую библиотеку

This commit is contained in:
Alexander Zhirov 2025-10-03 19:35:51 +03:00
parent 1f50b21457
commit 0fc56e7c04
Signed by: alexander
GPG key ID: C8D8BE544A27C511
5 changed files with 180 additions and 148 deletions

View file

@ -7,7 +7,7 @@
"license": "BSL-1.0",
"name": "cdcdb",
"dependencies": {
"arsd-official:sqlite": "~>12.0.0",
"d2sqlite3": "~>1.0.0",
"zstd": "~>0.2.1"
},
"stringImportPaths": [

View file

@ -2,6 +2,7 @@
"fileVersion": 1,
"versions": {
"arsd-official": "12.0.0",
"d2sqlite3": "1.0.0",
"zstd": "0.2.1"
}
}

View file

@ -1,6 +1,6 @@
module cdcdb.dblite;
import arsd.sqlite : Sqlite, SqliteResult, DatabaseException;
import d2sqlite3;
import std.string : join, replace, toLower;
import std.algorithm : canFind;
@ -91,19 +91,29 @@ struct DBSnapshotChunkData {
ubyte[32] zSha256; /// Хеш сжатого содержимого.
}
final class DBLite : Sqlite
final class DBLite
{
private:
string _dbPath; /// Путь к файлу БД.
size_t _maxRetries; /// Максимум повторов при `busy/locked`.
Database _db; /// Соединение с БД (d2sqlite3).
// SQL-схема (массив строковых запросов).
mixin(import("scheme.d"));
/// Выполняет SQL с повторными попытками при `locked/busy`.
SqliteResult sql(T...)(string queryText, T args)
ResultRange sql(T...)(string queryText, T args)
{
// Готовим стейтмент сами, чтобы bindAll() работал и для BLOB.
auto attempt = () {
auto st = _db.prepare(queryText);
static if (T.length > 0)
st.bindAll(args);
return st.execute();
};
if (_maxRetries == 0) {
return cast(SqliteResult) query(queryText, args);
return attempt();
}
string msg;
@ -111,10 +121,11 @@ private:
while (tryNo) {
try {
return cast(SqliteResult) query(queryText, args);
} catch (DatabaseException e) {
return attempt();
} catch (SqliteException e) {
msg = e.msg;
if (msg.toLower.canFind("locked", "busy")) {
const code = e.code;
if (code == SQLITE_BUSY || code == SQLITE_LOCKED) {
if (--tryNo == 0) {
throw new Exception(
"Не удалось выполнить запрос к базе данных после %d неудачных попыток: %s"
@ -123,17 +134,18 @@ private:
}
continue;
}
break;
break; // другие ошибки — дальше по стеку
}
}
throw new Exception(msg);
// До сюда не дойдём, но для формальной полноты:
throw new Exception(msg.length ? msg : "SQLite error");
}
/// Проверяет наличие обязательных таблиц.
/// Если все отсутствуют — создаёт схему; если отсутствует часть — бросает ошибку.
void check()
{
SqliteResult queryResult = sql(
auto queryResult = sql(
q{
WITH required(name)
AS (VALUES ("snapshots"), ("blobs"), ("snapshot_chunks"), ("users"), ("processes"), ("files"))
@ -151,7 +163,7 @@ private:
foreach (row; queryResult)
{
missingTables ~= row["missing_table"].to!string;
missingTables ~= row["missing_table"].as!string;
}
enforce(missingTables.length == 0 || missingTables.length == 6,
@ -171,16 +183,16 @@ public:
this(string database, size_t busyTimeout, size_t maxRetries)
{
_dbPath = database;
super(database);
_db = Database(database);
check();
_maxRetries = maxRetries;
query("PRAGMA journal_mode=WAL");
query("PRAGMA synchronous=NORMAL");
query("PRAGMA foreign_keys=ON");
query("PRAGMA busy_timeout=%d".format(busyTimeout));
_db.execute("PRAGMA journal_mode=WAL");
_db.execute("PRAGMA synchronous=NORMAL");
_db.execute("PRAGMA foreign_keys=ON");
_db.execute("PRAGMA busy_timeout=%d".format(busyTimeout));
}
/// BEGIN IMMEDIATE.
@ -192,13 +204,13 @@ public:
/// COMMIT.
void commit()
{
sql("COMMIT");
_db.commit();
}
/// ROLLBACK.
void rollback()
{
sql("ROLLBACK");
_db.rollback();
}
/*************************************************
@ -218,8 +230,8 @@ public:
{
DBFile file;
file.id = cast(ubyte[]) row["id"].dup;
file.path = row["name"].to!string;
file.id = row["id"].as!Blob(Blob.init);
file.path = row["name"].as!string;
files ~= file;
}
@ -236,10 +248,10 @@ public:
DBFile file;
if (!queryResult.empty()) {
auto data = queryResult.front();
if (!queryResult.empty) {
auto data = queryResult.front;
file.id = cast(ubyte[]) data["id"].dup;
file.id = data["id"].as!Blob(Blob.init);
file.path = path;
}
@ -257,18 +269,18 @@ public:
DBFile file;
if (!queryResult.empty()) {
auto data = queryResult.front();
if (!queryResult.empty) {
auto data = queryResult.front;
file.id = id;
file.path = data["name"].to!string;
file.path = data["name"].as!string;
}
return file;
}
DBFile addFile(string path) {
SqliteResult queryResult;
ResultRange queryResult;
UUID uuid;
// Исключение одинакового UUID первичного ключа
@ -279,7 +291,7 @@ public:
SELECT id FROM files WHERE id = ?1
}, uuid.data[]
);
} while (!queryResult.empty());
} while (!queryResult.empty);
queryResult = sql(
q{
@ -289,7 +301,7 @@ public:
}, uuid.data[], path
);
enforce(!queryResult.empty(), "Не удалось добавить новый файл в базу данных");
enforce(!queryResult.empty, "Не удалось добавить новый файл в базу данных");
return DBFile(Identifier(uuid.data), path);
}
@ -307,8 +319,8 @@ public:
{
DBFile file;
file.id = cast(ubyte[]) row["id"].dup;
file.path = row["name"].to!string;
file.id = row["id"].as!Blob(Blob.init);
file.path = row["name"].as!string;
files ~= file;
}
@ -339,8 +351,8 @@ public:
{
DBFile file;
file.id = cast(ubyte[]) row["id"].dup;
file.path = row["name"].to!string;
file.id = row["id"].as!Blob(Blob.init);
file.path = row["name"].as!string;
files ~= file;
}
@ -350,12 +362,12 @@ public:
bool deleteFile(Identifier id) {
auto queryResult = sql("DELETE FROM files WHERE id = ?1 RETURNING id", id[]);
return !queryResult.empty();
return !queryResult.empty;
}
bool deleteFile(string path) {
auto queryResult = sql("DELETE FROM files WHERE name = ?1 RETURNING id", path);
return !queryResult.empty();
return !queryResult.empty;
}
/////////////////////////////////////////////////////////////////////
@ -376,8 +388,8 @@ public:
}, id[], sha256[]
);
if (!queryResult.empty())
return queryResult.front()["is_last"].to!long > 0;
if (!queryResult.empty)
return queryResult.front["is_last"].as!long > 0;
return false;
}
@ -393,7 +405,7 @@ public:
}, uid, name
);
return !queryResult.empty();
return !queryResult.empty;
}
DBProcess getProcess(string name) {
@ -405,10 +417,10 @@ public:
DBProcess process;
if (!queryResult.empty()) {
auto data = queryResult.front();
if (!queryResult.empty) {
auto data = queryResult.front;
process.id = cast(ubyte[]) data["id"].dup;
process.id = data["id"].as!Blob(Blob.init);
process.name = name;
}
@ -426,18 +438,18 @@ public:
DBProcess process;
if (!queryResult.empty()) {
auto data = queryResult.front();
if (!queryResult.empty) {
auto data = queryResult.front;
process.id = id;
process.name = data["name"].to!string;
process.name = data["name"].as!string;
}
return process;
}
DBProcess addProcess(string name) {
SqliteResult queryResult;
ResultRange queryResult;
UUID uuid;
// Исключение одинакового UUID первичного ключа
@ -448,7 +460,7 @@ public:
SELECT id FROM processes WHERE id = ?1
}, uuid.data[]
);
} while (!queryResult.empty());
} while (!queryResult.empty);
queryResult = sql(
q{
@ -458,7 +470,7 @@ public:
}, uuid.data[], name
);
enforce(!queryResult.empty(), "Не удалось добавить новый файл в базу данных");
enforce(!queryResult.empty, "Не удалось добавить новый файл в базу данных");
return DBProcess(Identifier(uuid.data), name);
}
@ -467,7 +479,7 @@ public:
bool addSnapshot(ref DBSnapshot snapshot)
{
SqliteResult queryResult;
ResultRange queryResult;
UUID uuid;
// Исключение одинакового UUID первичного ключа
@ -478,7 +490,7 @@ public:
SELECT id FROM snapshots WHERE id = ?1
}, uuid.data[]
);
} while (!queryResult.empty());
} while (!queryResult.empty);
import std.datetime : Clock;
@ -524,7 +536,7 @@ public:
snapshot.status.to!int // ?15
);
return !queryResult.empty();
return !queryResult.empty;
}
/////////////////////////////////////////////////////////////////////
@ -564,7 +576,7 @@ public:
blob.zstd.to!int // ?8
);
return !queryResult.empty();
return !queryResult.empty;
}
bool addSnapshotChunk(DBSnapshotChunk snapshotChunk)
@ -581,7 +593,7 @@ public:
snapshotChunk.sha256[]
);
return !queryResult.empty();
return !queryResult.empty;
}
DBSnapshotChunkData[] getChunks(Identifier id)
@ -603,14 +615,22 @@ public:
{
DBSnapshotChunkData sdch;
sdch.chunkIndex = row["chunk_index"].to!long;
sdch.offset = row["offset"].to!long;
sdch.size = row["size"].to!long;
sdch.content = cast(ubyte[]) row["content"].dup;
sdch.zstd = cast(bool) row["zstd"].to!int;
sdch.zSize = row["z_size"].to!long;
sdch.sha256 = cast(ubyte[]) row["sha256"].dup;
sdch.zSha256 = cast(ubyte[]) row["z_sha256"].dup;
sdch.chunkIndex = row["chunk_index"].as!long;
sdch.offset = row["offset"].as!long;
sdch.size = row["size"].as!long;
// content может быть NULL
auto contentBlob = cast(ubyte[]) row["content"].as!Blob(Blob.init);
sdch.content = contentBlob.length ? contentBlob.dup : null;
sdch.zstd = row["zstd"].as!int != 0;
sdch.zSize = row["z_size"].as!long;
auto sha = cast(ubyte[]) row["sha256"].as!Blob(Blob.init);
if (sha.length) sdch.sha256[] = sha;
auto zsha = cast(ubyte[]) row["z_sha256"].as!Blob(Blob.init);
if (zsha.length) sdch.zSha256[] = zsha;
sdchs ~= sdch;
}
@ -657,28 +677,28 @@ public:
{
DBSnapshot snapshot;
snapshot.id = cast(ubyte[]) row["id"].dup;
snapshot.id = row["id"].as!Blob(Blob.init);
snapshot.file = DBFile(
Identifier(cast(ubyte[]) row["file_id"].dup),
row["file_name"].to!string
Identifier(row["file_id"].as!Blob(Blob.init)),
row["file_name"].as!string
);
snapshot.sha256 = cast(ubyte[]) row["sha256"].dup;
snapshot.description = row["description"].to!string;
snapshot.createdUtc = row["created_utc"].to!long;
snapshot.sourceLength = row["source_length"].to!long;
snapshot.algoMin = row["algo_min"].to!long;
snapshot.algoNormal = row["algo_normal"].to!long;
snapshot.algoMax = row["algo_max"].to!long;
snapshot.maskS = row["mask_s"].to!long;
snapshot.maskL = row["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus) row["status"].to!int;
snapshot.uid = row["uid"].to!long;
snapshot.ruid = row["ruid"].to!long;
snapshot.uidName = row["uid_name"].to!string;
snapshot.ruidName = row["ruid_name"].to!string;
snapshot.sha256 = row["sha256"].as!Blob(Blob.init);
snapshot.description = row["description"].as!string(""); // может быть NULL
snapshot.createdUtc = row["created_utc"].as!long;
snapshot.sourceLength = row["source_length"].as!long;
snapshot.algoMin = row["algo_min"].as!long;
snapshot.algoNormal = row["algo_normal"].as!long;
snapshot.algoMax = row["algo_max"].as!long;
snapshot.maskS = row["mask_s"].as!long;
snapshot.maskL = row["mask_l"].as!long;
snapshot.status = cast(SnapshotStatus) row["status"].as!int;
snapshot.uid = row["uid"].as!long;
snapshot.ruid = row["ruid"].as!long;
snapshot.uidName = row["uid_name"].as!string;
snapshot.ruidName = row["ruid_name"].as!string;
snapshot.process = DBProcess(
Identifier(cast(ubyte[]) row["process_id"].dup),
row["process_name"].to!string
Identifier(row["process_id"].as!Blob(Blob.init)),
row["process_name"].as!string
);
snapshots ~= snapshot;
@ -727,30 +747,29 @@ public:
{
DBSnapshot snapshot;
snapshot.id = cast(ubyte[]) row["id"].dup;
snapshot.id = row["id"].as!Blob(Blob.init);
snapshot.file = DBFile(
Identifier(cast(ubyte[]) row["file_id"].dup),
row["file_name"].to!string
Identifier(row["file_id"].as!Blob(Blob.init)),
row["file_name"].as!string
);
snapshot.sha256 = cast(ubyte[]) row["sha256"].dup;
snapshot.description = row["description"].to!string;
snapshot.createdUtc = row["created_utc"].to!long;
snapshot.sourceLength = row["source_length"].to!long;
snapshot.algoMin = row["algo_min"].to!long;
snapshot.algoNormal = row["algo_normal"].to!long;
snapshot.algoMax = row["algo_max"].to!long;
snapshot.maskS = row["mask_s"].to!long;
snapshot.maskL = row["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus) row["status"].to!int;
snapshot.uid = row["uid"].to!long;
snapshot.ruid = row["ruid"].to!long;
snapshot.uidName = row["uid_name"].to!string;
snapshot.ruidName = row["ruid_name"].to!string;
snapshot.sha256 = row["sha256"].as!Blob(Blob.init);
snapshot.description = row["description"].as!string("");
snapshot.createdUtc = row["created_utc"].as!long;
snapshot.sourceLength = row["source_length"].as!long;
snapshot.algoMin = row["algo_min"].as!long;
snapshot.algoNormal = row["algo_normal"].as!long;
snapshot.algoMax = row["algo_max"].as!long;
snapshot.maskS = row["mask_s"].as!long;
snapshot.maskL = row["mask_l"].as!long;
snapshot.status = cast(SnapshotStatus) row["status"].as!int;
snapshot.uid = row["uid"].as!long;
snapshot.ruid = row["ruid"].as!long;
snapshot.uidName = row["uid_name"].as!string;
snapshot.ruidName = row["ruid_name"].as!string;
snapshot.process = DBProcess(
Identifier(cast(ubyte[]) row["process_id"].dup),
row["process_name"].to!string
Identifier(row["process_id"].as!Blob(Blob.init)),
row["process_name"].as!string
);
snapshots ~= snapshot;
}
@ -793,31 +812,31 @@ public:
DBSnapshot snapshot;
if (!queryResult.empty()) {
auto data = queryResult.front();
if (!queryResult.empty) {
auto data = queryResult.front;
snapshot.id = cast(ubyte[]) data["id"].dup;
snapshot.id = data["id"].as!Blob(Blob.init);
snapshot.file = DBFile(
Identifier(cast(ubyte[]) data["file_id"].dup),
data["file_name"].to!string
Identifier(data["file_id"].as!Blob(Blob.init)),
data["file_name"].as!string
);
snapshot.sha256 = cast(ubyte[]) data["sha256"].dup;
snapshot.description = data["description"].to!string;
snapshot.createdUtc = data["created_utc"].to!long;
snapshot.sourceLength = data["source_length"].to!long;
snapshot.algoMin = data["algo_min"].to!long;
snapshot.algoNormal = data["algo_normal"].to!long;
snapshot.algoMax = data["algo_max"].to!long;
snapshot.maskS = data["mask_s"].to!long;
snapshot.maskL = data["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus) data["status"].to!int;
snapshot.uid = data["uid"].to!long;
snapshot.ruid = data["ruid"].to!long;
snapshot.uidName = data["uid_name"].to!string;
snapshot.ruidName = data["ruid_name"].to!string;
snapshot.sha256 = data["sha256"].as!Blob(Blob.init);
snapshot.description = data["description"].as!string("");
snapshot.createdUtc = data["created_utc"].as!long;
snapshot.sourceLength = data["source_length"].as!long;
snapshot.algoMin = data["algo_min"].as!long;
snapshot.algoNormal = data["algo_normal"].as!long;
snapshot.algoMax = data["algo_max"].as!long;
snapshot.maskS = data["mask_s"].as!long;
snapshot.maskL = data["mask_l"].as!long;
snapshot.status = cast(SnapshotStatus) data["status"].as!int;
snapshot.uid = data["uid"].as!long;
snapshot.ruid = data["ruid"].as!long;
snapshot.uidName = data["uid_name"].as!string;
snapshot.ruidName = data["ruid_name"].as!string;
snapshot.process = DBProcess(
Identifier(cast(ubyte[]) data["process_id"].dup),
data["process_name"].to!string
Identifier(data["process_id"].as!Blob(Blob.init)),
data["process_name"].as!string
);
}
@ -870,28 +889,28 @@ public:
{
DBSnapshot snapshot;
snapshot.id = cast(ubyte[]) row["id"].dup;
snapshot.id = row["id"].as!Blob(Blob.init);
snapshot.file = DBFile(
Identifier(cast(ubyte[]) row["file_id"].dup),
row["file_name"].to!string
Identifier(row["file_id"].as!Blob(Blob.init)),
row["file_name"].as!string
);
snapshot.sha256 = cast(ubyte[]) row["sha256"].dup;
snapshot.description = row["description"].to!string;
snapshot.createdUtc = row["created_utc"].to!long;
snapshot.sourceLength = row["source_length"].to!long;
snapshot.algoMin = row["algo_min"].to!long;
snapshot.algoNormal = row["algo_normal"].to!long;
snapshot.algoMax = row["algo_max"].to!long;
snapshot.maskS = row["mask_s"].to!long;
snapshot.maskL = row["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus) row["status"].to!int;
snapshot.uid = row["uid"].to!long;
snapshot.ruid = row["ruid"].to!long;
snapshot.uidName = row["uid_name"].to!string;
snapshot.ruidName = row["ruid_name"].to!string;
snapshot.sha256 = row["sha256"].as!Blob(Blob.init);
snapshot.description = row["description"].as!string("");
snapshot.createdUtc = row["created_utc"].as!long;
snapshot.sourceLength = row["source_length"].as!long;
snapshot.algoMin = row["algo_min"].as!long;
snapshot.algoNormal = row["algo_normal"].as!long;
snapshot.algoMax = row["algo_max"].as!long;
snapshot.maskS = row["mask_s"].as!long;
snapshot.maskL = row["mask_l"].as!long;
snapshot.status = cast(SnapshotStatus) row["status"].as!int;
snapshot.uid = row["uid"].as!long;
snapshot.ruid = row["ruid"].as!long;
snapshot.uidName = row["uid_name"].as!string;
snapshot.ruidName = row["ruid_name"].as!string;
snapshot.process = DBProcess(
Identifier(cast(ubyte[]) row["process_id"].dup),
row["process_name"].to!string
Identifier(row["process_id"].as!Blob(Blob.init)),
row["process_name"].as!string
);
snapshots ~= snapshot;
@ -902,6 +921,6 @@ public:
bool deleteSnapshot(Identifier id) {
auto queryResult = sql("DELETE FROM snapshots WHERE id = ? RETURNING id", id[]);
return !queryResult.empty();
return !queryResult.empty;
}
}

View file

@ -60,12 +60,24 @@ public:
_data = data;
}
this(immutable(ubyte[]) data)
{
assert(data.length <= 16);
_data = data.dup;
}
this(ref const ubyte[16] data)
{
assert(data.length <= 16);
_data = data.dup;
}
void opAssign(immutable(ubyte[]) data)
{
assert(data.length <= 16);
_data = data.dup;
}
void opAssign(ubyte[] data)
{
assert(data.length <= 16);

View file

@ -72,13 +72,13 @@ void main()
}
// Удаление файла
if (storage.deleteFile("example_file"))
writeln("Файл example_file удален.");
// if (storage.deleteFile("example_file"))
// writeln("Файл example_file удален.");
// Проверка: снимки удалены
auto remaining = storage.getSnapshots("example_file");
assert(remaining.length == 0);
writeln("Все снимки удалены.");
// // Проверка: снимки удалены
// auto remaining = storage.getSnapshots("example_file");
// assert(remaining.length == 0);
// writeln("Все снимки удалены.");
writeln("Версия библиотеки: ", storage.getVersion());
// writeln("Версия библиотеки: ", storage.getVersion());
}