forked from dlang/cdcdb
		
	Переход на другую библиотеку
This commit is contained in:
		
							parent
							
								
									1f50b21457
								
							
						
					
					
						commit
						0fc56e7c04
					
				
					 5 changed files with 180 additions and 148 deletions
				
			
		
							
								
								
									
										2
									
								
								dub.json
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								dub.json
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -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": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
	"fileVersion": 1,
 | 
			
		||||
	"versions": {
 | 
			
		||||
		"arsd-official": "12.0.0",
 | 
			
		||||
		"d2sqlite3": "1.0.0",
 | 
			
		||||
		"zstd": "0.2.1"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								test/app.d
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								test/app.d
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -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());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue