From 71dba2bac792f15df844ac0980a05f9e960e5f16 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Mon, 6 Mar 2023 03:20:13 +0700 Subject: [PATCH] Add request ID support to `DBAPI` --- sources/Data/Database/DBTask.uc | 24 +- sources/Data/Database/Database.uc | 129 +++++++-- .../Database/Local/LocalDatabaseInstance.uc | 97 ++++--- sources/Data/Database/Tasks/DBCheckTask.uc | 7 +- .../Data/Database/Tasks/DBIncrementTask.uc | 9 +- sources/Data/Database/Tasks/DBKeysTask.uc | 9 +- sources/Data/Database/Tasks/DBReadTask.uc | 9 +- sources/Data/Database/Tasks/DBRemoveTask.uc | 9 +- sources/Data/Database/Tasks/DBSizeTask.uc | 10 +- sources/Data/Database/Tasks/DBWriteTask.uc | 9 +- .../Database/Tests/TEST_DatabaseCommon.uc | 10 +- .../Data/Database/Tests/TEST_LocalDatabase.uc | 252 +++++++++++++++--- 12 files changed, 440 insertions(+), 134 deletions(-) diff --git a/sources/Data/Database/DBTask.uc b/sources/Data/Database/DBTask.uc index 40e947c..2324725 100644 --- a/sources/Data/Database/DBTask.uc +++ b/sources/Data/Database/DBTask.uc @@ -6,7 +6,7 @@ * completed and will self-destruct afterwards. Concrete delegates are * declared in child classes of this `DBTask`, since they can have different * signatures, depending on the query. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -54,6 +54,7 @@ var private int previousTaskLifeVersion; var private Database.DBQueryResult taskResult; var private bool isReadyToComplete; +var private int requestID; var private LoggerAPI.Definition errLoopInTaskChain; @@ -65,6 +66,18 @@ protected function Finalizer() previousTask = none; previousTaskLifeVersion = -1; isReadyToComplete = false; + requestID = 0; +} + +/** + * Returns ID of the request set inside `SetResult()` for the caller `DBTask`. + * + * @return ID of the request set inside `SetResult()` for the caller `DBTask`. + * If `SetResult()` wasn't yet called returns `0`. + */ +protected function int GetRequestID() +{ + return requestID; } /** @@ -105,12 +118,17 @@ public final function Database.DBQueryResult GetResult() * This value can be assigned several times and the last assigned value will * be used. * - * @param result Result of the query, relevant to the caller task. + * @param result Result of the query, relevant to the caller task. + * @param requestID ID of the request this task is responding to, specified + * at the time request was made. */ -public final function SetResult(Database.DBQueryResult result) +public final function SetResult( + Database.DBQueryResult result, + optional int completedRequestID) { taskResult = result; isReadyToComplete = true; + requestID = completedRequestID; } /** diff --git a/sources/Data/Database/Database.uc b/sources/Data/Database/Database.uc index e1f6f83..a296020 100644 --- a/sources/Data/Database/Database.uc +++ b/sources/Data/Database/Database.uc @@ -7,7 +7,7 @@ * All of the methods are asynchronous - they do not return requested * values immediately and instead require user to provide a handler function * that will be called once operation is completed. - * Copyright 2021-2022 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -73,15 +73,27 @@ enum DBQueryResult * to load data as immutable Acedia's types and `true` will make it load * data as mutable types. This setting does not affect `Collection`s into * which JSON arrays and objects are converted - they are always mutable. + * @param requestID ID of this request. It will be reported when + * database's task is completed. Can be used to correspond database's + * responses with particular requests. * @return Task object that corresponds to this `ReadData()` call. * * Guaranteed to be not `none`; * * Use it to connect a handler for when reading task is complete: - * `ReadData(...).connect = handler`, - * where `handler` must have the following signature: - * `connect(DBQueryResult result, AcediaObject data)`; + * `ReadData(...).connect = handler`, + * where `handler` must have the following signature: + * ``` + * connect( + * DBQueryResult result, + * take AcediaObject data, + * Database source, + * int requestID)`; + * ``` * * Ownership of `data` object returned in the `connect()` is considered * to be transferred to whoever handled result of this query. * It must be deallocated once no longer needed. + * * `source` provides reference to the database, whose data was + * requested, `requestID` provides the same number as `requestID` + * parameter of this method. * * Possible `DBQueryResult` types are `DBR_Success`, * `DBR_InvalidPointer` and `DBR_InvalidDatabase`; * * `data` is guaranteed to be `none` if `result != DBR_Success`; @@ -90,7 +102,8 @@ enum DBQueryResult */ public function DBReadTask ReadData( JSONPointer pointer, - optional bool makeMutable) + optional bool makeMutable, + optional int requestID) { return none; } @@ -110,12 +123,18 @@ public function DBReadTask ReadData( * @param data Data that needs to be written at the specified location * inside the database. For method to succeed this object needs to have * JSON-compatible type (see `_.json.IsCompatible()` for more details). + * @param requestID ID of this request. It will be reported when + * database's task is completed. Can be used to correspond database's + * responses with particular requests. * @return Task object that corresponds to this `WriteData()` call. * * Guaranteed to be not `none`; * * Use it to connect a handler for when writing task is complete: * `WriteData(...).connect = handler`, * where `handler` must have the following signature: - * `connect(DBQueryResult result)`; + * `connect(DBQueryResult result, Database source, int requestID)`; + * * `source` provides reference to the database, whose data was + * requested, `requestID` provides the same number as `requestID` + * parameter of this method. * * Possible `DBQueryResult` types are `DBR_Success`, * `DBR_InvalidPointer`, `DBR_InvalidDatabase` and `DBR_InvalidData`; * * Data is actually written inside the database iff @@ -128,7 +147,10 @@ public function DBReadTask ReadData( * Example: writing data at "/sub-object/valueA" will always fail if * "sub-object" does not exist. */ -public function DBWriteTask WriteData(JSONPointer pointer, AcediaObject data) +public function DBWriteTask WriteData( + JSONPointer pointer, + AcediaObject data, + optional int requestID) { return none; } @@ -141,12 +163,18 @@ public function DBWriteTask WriteData(JSONPointer pointer, AcediaObject data) * * @param pointer JSON pointer to the location of the data to remove from * database. `none` is always treated as an invalid JSON pointer. + * @param requestID ID of this request. It will be reported when + * database's task is completed. Can be used to correspond database's + * responses with particular requests. * @return Task object that corresponds to this `RemoveData()` call. * * Guaranteed to be not `none`; * * Use it to connect a handler for when writing task is complete: * `RemoveData(...).connect = handler`, * where `handler` must have the following signature: - * `connect(DBQueryResult result)`. + * `connect(DBQueryResult result, Database source, int requestID)`. + * * `source` provides reference to the database, whose data was + * requested, `requestID` provides the same number as `requestID` + * parameter of this method. * * Possible `DBQueryResult` types are `DBR_Success`, * `DBR_InvalidPointer` and `DBR_InvalidDatabase`; * * Data is actually removed from the database iff @@ -154,7 +182,9 @@ public function DBWriteTask WriteData(JSONPointer pointer, AcediaObject data) * * `DBR_InvalidPointer` can be produced if either `pointer == none` or * it does not point at any existing value inside the caller database. */ -public function DBRemoveTask RemoveData(JSONPointer pointer) +public function DBRemoveTask RemoveData( + JSONPointer pointer, + optional int requestID) { return none; } @@ -166,12 +196,24 @@ public function DBRemoveTask RemoveData(JSONPointer pointer) * @param pointer JSON pointer to the location of the data for which type * needs to be checked. * `none` is always treated as an invalid JSON pointer. + * @param requestID ID of this request. It will be reported when + * database's task is completed. Can be used to correspond database's + * responses with particular requests. * @return Task object that corresponds to this `CheckDataType()` call. * * Guaranteed to be not `none`; * * Use it to connect a handler for when reading task is complete: * `CheckDataType(...).connect = handler`, * where `handler` must have the following signature: - * `connect(DBQueryResult result, Database.DataType type)`; + * ``` + * connect( + * DBQueryResult result, + * Database.DataType type, + * Database source, + * int requestID) + * ``` + * * `source` provides reference to the database, whose data was + * requested, `requestID` provides the same number as `requestID` + * parameter of this method. * * Possible `DBQueryResult` types are `DBR_Success`, * `DBR_InvalidPointer` and `DBR_InvalidDatabase`; * * This task can only fail if either caller database is broken @@ -181,7 +223,9 @@ public function DBRemoveTask RemoveData(JSONPointer pointer) * * Data is actually removed from the database iff * `result == DBR_Success`. */ -public function DBCheckTask CheckDataType(JSONPointer pointer) +public function DBCheckTask CheckDataType( + JSONPointer pointer, + optional int requestID) { return none; } @@ -197,12 +241,24 @@ public function DBCheckTask CheckDataType(JSONPointer pointer) * @param pointer JSON pointer to the location of the JSON object or array * for which size needs to be obtained. * `none` is always treated as an invalid JSON pointer. + * @param requestID ID of this request. It will be reported when + * database's task is completed. Can be used to correspond database's + * responses with particular requests. * @return Task object that corresponds to this `GetDataSize()` call. * * Guaranteed to be not `none`; * * Use it to connect a handler for when reading task is complete: * `GetDataSize(...).connect = handler`, * where `handler` must have the following signature: - * `connect(DBQueryResult result, int size)`. + * ``` + * connect( + * DBQueryResult result, + * int size, + * Database source, + * int requestID) + * ``` + * * `source` provides reference to the database, whose data was + * requested, `requestID` provides the same number as `requestID` + * parameter of this method. * * Possible `DBQueryResult` types are `DBR_Success`, * `DBR_InvalidPointer` and `DBR_InvalidDatabase`; * * Returned `size` value is actually a size of referred @@ -211,7 +267,9 @@ public function DBCheckTask CheckDataType(JSONPointer pointer) * it does not point at a JSON object or array inside the * caller database. */ -public function DBSizeTask GetDataSize(JSONPointer pointer) +public function DBSizeTask GetDataSize( + JSONPointer pointer, + optional int requestID) { return none; } @@ -225,15 +283,27 @@ public function DBSizeTask GetDataSize(JSONPointer pointer) * @param pointer JSON pointer to the location of the JSON object for which * keys need to be obtained. * `none` is always treated as an invalid JSON pointer. + * @param requestID ID of this request. It will be reported when + * database's task is completed. Can be used to correspond database's + * responses with particular requests. * @return Task object that corresponds to this `GetDataKeys()` call. * * Guaranteed to be not `none`; * * Use it to connect a handler for when reading task is complete: * `GetDataKeys(...).connect = handler`, * where `handler` must have the following signature: - * `connect(DBQueryResult result, ArrayList keys)`. + * ``` + * connect( + * DBQueryResult result, + * take ArrayList keys, + * Database source, + * int requestID) + * ``` * * Ownership of `keys` array returned in the `connect()` is considered * to be transferred to whoever handled result of this query. * It must be deallocated once no longer needed. + * * `source` provides reference to the database, whose data was + * requested, `requestID` provides the same number as `requestID` + * parameter of this method. * * Possible `DBQueryResult` types are `DBR_Success`, * `DBR_InvalidPointer`, `DBR_InvalidData` and `DBR_InvalidDatabase`; * * Returned `keys` will be non-`none` and contain keys of the referred @@ -243,7 +313,9 @@ public function DBSizeTask GetDataSize(JSONPointer pointer) * point at a JSON object inside caller database * (value can either not exist at all or have some other type). */ -public function DBKeysTask GetDataKeys(JSONPointer pointer) +public function DBKeysTask GetDataKeys( + JSONPointer pointer, + optional int requestID) { return none; } @@ -255,19 +327,21 @@ public function DBKeysTask GetDataKeys(JSONPointer pointer) * "Incrementing" is an operation that is safe from the point of view of * simultaneous access. What "incrementing" actually does depends on * the passed JSON value (`increment` parameter): - * (0. Unless `pointer` points at the JSON null value - then "increment" - * acts as a `WriteData()` method regardless of `increment`'s value); + * + * (0. ...unless `pointer` points at the JSON null or missing value (within + * existing container - then "increment" acts as a `WriteData()` method + * regardless of `increment`'s value;) * 1. JSON null: it never modifies existing value and reports an error if * existing value was not itself JSON null; - * 2. JSON bool: if combines with stored JSON bool value - + * 2. JSON bool: if combined with stored JSON bool value - * performs logical "or" operation. Otherwise fails; - * 3. JSON number: if combines with stored JSON numeric value - + * 3. JSON number: if combined with stored JSON numeric value - * adds values together. Otherwise fails. - * 4. JSON string: if combines with stored JSON string value - + * 4. JSON string: if combined with stored JSON string value - * concatenates itself at the end. Otherwise fails. - * 5. JSON array: if combines with stored JSON array value - + * 5. JSON array: if combined with stored JSON array value - * concatenates itself at the end. Otherwise fails. - * 6. JSON object: if combines with stored JSON object value - + * 6. JSON object: if combined with stored JSON object value - * `increment` adds it's own values with new keys into the stored * JSON object. Does not override old values. * Fails when combined with any other type. @@ -280,12 +354,18 @@ public function DBKeysTask GetDataKeys(JSONPointer pointer) * with `increment` parameter. * @param increment JSON-compatible value to be used as an increment for * the data at the specified location inside the database. + * @param requestID ID of this request. It will be reported when + * database's task is completed. Can be used to correspond database's + * responses with particular requests. * @return Task object that corresponds to this `IncrementData()` call. * * Guaranteed to be not `none`; * * Use it to connect a handler for when reading task is complete: * `IncrementData(...).connect = handler`, * where `handler` must have the following signature: - * `connect(DBQueryResult result)`. + * `connect(DBQueryResult result, Database source, int requestID)`. + * * `source` provides reference to the database, whose data was + * requested, `requestID` provides the same number as `requestID` + * parameter of this method. * * Possible `DBQueryResult` types are `DBR_Success`, * `DBR_InvalidPointer`, `DBR_InvalidData` and `DBR_InvalidDatabase`; * * Data is actually incremented iff `result == DBR_Success`; @@ -299,7 +379,8 @@ public function DBKeysTask GetDataKeys(JSONPointer pointer) */ public function DBIncrementTask IncrementData( JSONPointer pointer, - AcediaObject increment) + AcediaObject increment, + optional int requestID) { return none; } diff --git a/sources/Data/Database/Local/LocalDatabaseInstance.uc b/sources/Data/Database/Local/LocalDatabaseInstance.uc index cc36ef0..0f8715b 100644 --- a/sources/Data/Database/Local/LocalDatabaseInstance.uc +++ b/sources/Data/Database/Local/LocalDatabaseInstance.uc @@ -149,54 +149,61 @@ private final function DBTask MakeNewTask(class newTaskClass) return newTask; } -private function bool ValidatePointer(JSONPointer pointer, DBTask relevantTask) +private function bool ValidatePointer( + JSONPointer pointer, + DBTask relevantTask, + int requestID) { if (pointer != none) { return true; } - relevantTask.SetResult(DBR_InvalidPointer); + relevantTask.SetResult(DBR_InvalidPointer, requestID); return false; } -private function bool ValidateRootRecord(DBTask relevantTask) +private function bool ValidateRootRecord(DBTask relevantTask, int requestID) { if (rootRecord != none) { return true; } - relevantTask.SetResult(DBR_InvalidDatabase); + relevantTask.SetResult(DBR_InvalidDatabase, requestID); return false; } public function DBReadTask ReadData( JSONPointer pointer, - optional bool makeMutable) + optional bool makeMutable, + optional int requestID) { local AcediaObject queryResult; local DBReadTask readTask; readTask = DBReadTask(MakeNewTask(class'DBReadTask')); - if (!ValidatePointer(pointer, readTask)) return readTask; - if (!ValidateRootRecord(readTask)) return readTask; + if (!ValidatePointer(pointer, readTask, requestID)) return readTask; + if (!ValidateRootRecord(readTask, requestID)) return readTask; if (rootRecord.LoadObject(pointer, queryResult, makeMutable)) { readTask.SetReadData(queryResult); - readTask.SetResult(DBR_Success); + readTask.SetResult(DBR_Success, requestID); } else { - readTask.SetResult(DBR_InvalidPointer); + readTask.SetResult(DBR_InvalidPointer, requestID); _.memory.Free(queryResult); // just in case } return readTask; } -public function DBWriteTask WriteData(JSONPointer pointer, AcediaObject data) +public function DBWriteTask WriteData( + JSONPointer pointer, + AcediaObject data, + optional int requestID) { local bool isDataStorable; local DBWriteTask writeTask; writeTask = DBWriteTask(MakeNewTask(class'DBWriteTask')); - if (!ValidatePointer(pointer, writeTask)) return writeTask; - if (!ValidateRootRecord(writeTask)) return writeTask; + if (!ValidatePointer(pointer, writeTask, requestID)) return writeTask; + if (!ValidateRootRecord(writeTask, requestID)) return writeTask; // We can only write JSON array as the root value if (data != none && pointer.GetLength() <= 0) { @@ -207,99 +214,111 @@ public function DBWriteTask WriteData(JSONPointer pointer, AcediaObject data) } if (!isDataStorable) { - writeTask.SetResult(DBR_InvalidData); + writeTask.SetResult(DBR_InvalidData, requestID); return writeTask; } if (rootRecord.SaveObject(pointer, data)) { - writeTask.SetResult(DBR_Success); + writeTask.SetResult(DBR_Success, requestID); ScheduleDiskUpdate(); } else { - writeTask.SetResult(DBR_InvalidPointer); + writeTask.SetResult(DBR_InvalidPointer, requestID); } return writeTask; } -public function DBRemoveTask RemoveData(JSONPointer pointer) +public function DBRemoveTask RemoveData( + JSONPointer pointer, + optional int requestID) { local DBRemoveTask removeTask; removeTask = DBRemoveTask(MakeNewTask(class'DBRemoveTask')); - if (!ValidatePointer(pointer, removeTask)) return removeTask; - if (!ValidateRootRecord(removeTask)) return removeTask; + if (!ValidatePointer(pointer, removeTask, requestID)) return removeTask; + if (!ValidateRootRecord(removeTask, requestID)) return removeTask; if (pointer.GetLength() == 0) { rootRecord.EmptySelf(); - removeTask.SetResult(DBR_Success); + removeTask.SetResult(DBR_Success, requestID); return removeTask; } if (rootRecord.RemoveObject(pointer)) { - removeTask.SetResult(DBR_Success); + removeTask.SetResult(DBR_Success, requestID); ScheduleDiskUpdate(); } else { - removeTask.SetResult(DBR_InvalidPointer); + removeTask.SetResult(DBR_InvalidPointer, requestID); } return removeTask; } -public function DBCheckTask CheckDataType(JSONPointer pointer) +public function DBCheckTask CheckDataType( + JSONPointer pointer, + optional int requestID) { local DBCheckTask checkTask; checkTask = DBCheckTask(MakeNewTask(class'DBCheckTask')); - if (!ValidatePointer(pointer, checkTask)) return checkTask; - if (!ValidateRootRecord(checkTask)) return checkTask; + if (!ValidatePointer(pointer, checkTask, requestID)) return checkTask; + if (!ValidateRootRecord(checkTask, requestID)) return checkTask; checkTask.SetDataType(rootRecord.GetObjectType(pointer)); - checkTask.SetResult(DBR_Success); + checkTask.SetResult(DBR_Success, requestID); return checkTask; } -public function DBSizeTask GetDataSize(JSONPointer pointer) +public function DBSizeTask GetDataSize( + JSONPointer pointer, + optional int requestID) { local DBSizeTask sizeTask; sizeTask = DBSizeTask(MakeNewTask(class'DBSizeTask')); - if (!ValidatePointer(pointer, sizeTask)) return sizeTask; - if (!ValidateRootRecord(sizeTask)) return sizeTask; + if (!ValidatePointer(pointer, sizeTask, requestID)) return sizeTask; + if (!ValidateRootRecord(sizeTask, requestID)) return sizeTask; sizeTask.SetDataSize(rootRecord.GetObjectSize(pointer)); - sizeTask.SetResult(DBR_Success); + sizeTask.SetResult(DBR_Success, requestID); return sizeTask; } -public function DBKeysTask GetDataKeys(JSONPointer pointer) +public function DBKeysTask GetDataKeys( + JSONPointer pointer, + optional int requestID) { local ArrayList keys; local DBKeysTask keysTask; keysTask = DBKeysTask(MakeNewTask(class'DBKeysTask')); - if (!ValidatePointer(pointer, keysTask)) return keysTask; - if (!ValidateRootRecord(keysTask)) return keysTask; + if (!ValidatePointer(pointer, keysTask, requestID)) return keysTask; + if (!ValidateRootRecord(keysTask, requestID)) return keysTask; keys = rootRecord.GetObjectKeys(pointer); keysTask.SetDataKeys(keys); if (keys == none) { - keysTask.SetResult(DBR_InvalidData); + keysTask.SetResult(DBR_InvalidData, requestID); } else { - keysTask.SetResult(DBR_Success); + keysTask.SetResult(DBR_Success, requestID); } return keysTask; } public function DBIncrementTask IncrementData( JSONPointer pointer, - AcediaObject increment) + AcediaObject increment, + optional int requestID) { local DBQueryResult queryResult; local DBIncrementTask incrementTask; incrementTask = DBIncrementTask(MakeNewTask(class'DBIncrementTask')); - if (!ValidatePointer(pointer, incrementTask)) return incrementTask; - if (!ValidateRootRecord(incrementTask)) return incrementTask; - + if (!ValidatePointer(pointer, incrementTask, requestID)) { + return incrementTask; + } + if (!ValidateRootRecord(incrementTask, requestID)) { + return incrementTask; + } queryResult = rootRecord.IncrementObject(pointer, increment); - incrementTask.SetResult(queryResult); + incrementTask.SetResult(queryResult, requestID); if (queryResult == DBR_Success) { ScheduleDiskUpdate(); } diff --git a/sources/Data/Database/Tasks/DBCheckTask.uc b/sources/Data/Database/Tasks/DBCheckTask.uc index 694343a..e355262 100644 --- a/sources/Data/Database/Tasks/DBCheckTask.uc +++ b/sources/Data/Database/Tasks/DBCheckTask.uc @@ -1,6 +1,6 @@ /** * Variant of `DBTask` for `CheckDataType()` query. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -24,7 +24,8 @@ var private Database.DataType queryTypeResponse; delegate connect( Database.DBQueryResult result, Database.DataType type, - Database source) {} + Database source, + int requestID) {} protected function Finalizer() { @@ -40,7 +41,7 @@ public function SetDataType(Database.DataType type) protected function CompleteSelf(Database source) { - connect(GetResult(), queryTypeResponse, source); + connect(GetResult(), queryTypeResponse, source, GetRequestID()); } defaultproperties diff --git a/sources/Data/Database/Tasks/DBIncrementTask.uc b/sources/Data/Database/Tasks/DBIncrementTask.uc index 886c739..c18d53b 100644 --- a/sources/Data/Database/Tasks/DBIncrementTask.uc +++ b/sources/Data/Database/Tasks/DBIncrementTask.uc @@ -1,6 +1,6 @@ /** * Variant of `DBTask` for `IncrementData()` query. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -19,7 +19,10 @@ */ class DBIncrementTask extends DBTask; -delegate connect(Database.DBQueryResult result, Database source) {} +delegate connect( + Database.DBQueryResult result, + Database source, + int requestID) {} protected function Finalizer() { @@ -29,7 +32,7 @@ protected function Finalizer() protected function CompleteSelf(Database source) { - connect(GetResult(), source); + connect(GetResult(), source, GetRequestID()); } defaultproperties diff --git a/sources/Data/Database/Tasks/DBKeysTask.uc b/sources/Data/Database/Tasks/DBKeysTask.uc index f074609..2f3097b 100644 --- a/sources/Data/Database/Tasks/DBKeysTask.uc +++ b/sources/Data/Database/Tasks/DBKeysTask.uc @@ -1,6 +1,6 @@ /** * Variant of `DBTask` for `GetDataKeys()` query. - * Copyright 2021-2022 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -23,8 +23,9 @@ var private ArrayList queryKeysResponse; delegate connect( Database.DBQueryResult result, - ArrayList keys, - Database source) {} + /*take*/ ArrayList keys, + Database source, + int requestID) {} protected function Finalizer() { @@ -40,7 +41,7 @@ public function SetDataKeys(/* take */ ArrayList keys) protected function CompleteSelf(Database source) { - connect(GetResult(), queryKeysResponse, source); + connect(GetResult(), queryKeysResponse, source, GetRequestID()); } defaultproperties diff --git a/sources/Data/Database/Tasks/DBReadTask.uc b/sources/Data/Database/Tasks/DBReadTask.uc index 54b4925..7a62467 100644 --- a/sources/Data/Database/Tasks/DBReadTask.uc +++ b/sources/Data/Database/Tasks/DBReadTask.uc @@ -1,6 +1,6 @@ /** * Variant of `DBTask` for `ReadData()` query. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -23,8 +23,9 @@ var private AcediaObject queryDataResponse; delegate connect( Database.DBQueryResult result, - AcediaObject data, - Database source) {} + /*take*/ AcediaObject data, + Database source, + int requestID) {} protected function Finalizer() { @@ -40,7 +41,7 @@ public function SetReadData(AcediaObject data) protected function CompleteSelf(Database source) { - connect(GetResult(), queryDataResponse, source); + connect(GetResult(), queryDataResponse, source, GetRequestID()); } defaultproperties diff --git a/sources/Data/Database/Tasks/DBRemoveTask.uc b/sources/Data/Database/Tasks/DBRemoveTask.uc index 50278f7..4b74a2f 100644 --- a/sources/Data/Database/Tasks/DBRemoveTask.uc +++ b/sources/Data/Database/Tasks/DBRemoveTask.uc @@ -1,6 +1,6 @@ /** * Variant of `DBTask` for `RemoveData()` query. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -19,7 +19,10 @@ */ class DBRemoveTask extends DBTask; -delegate connect(Database.DBQueryResult result, Database source) {} +delegate connect( + Database.DBQueryResult result, + Database source, + int requestID) {} protected function Finalizer() { @@ -29,7 +32,7 @@ protected function Finalizer() protected function CompleteSelf(Database source) { - connect(GetResult(), source); + connect(GetResult(), source, GetRequestID()); } defaultproperties diff --git a/sources/Data/Database/Tasks/DBSizeTask.uc b/sources/Data/Database/Tasks/DBSizeTask.uc index 564ac9e..9178700 100644 --- a/sources/Data/Database/Tasks/DBSizeTask.uc +++ b/sources/Data/Database/Tasks/DBSizeTask.uc @@ -1,6 +1,6 @@ /** * Variant of `DBTask` for `GetDataSize()` query. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -21,7 +21,11 @@ class DBSizeTask extends DBTask; var private int querySizeResponse; -delegate connect(Database.DBQueryResult result, int size, Database source) {} +delegate connect( + Database.DBQueryResult result, + int size, + Database source, + int requestID) {} protected function Finalizer() { @@ -37,7 +41,7 @@ public function SetDataSize(int size) protected function CompleteSelf(Database source) { - connect(GetResult(), querySizeResponse, source); + connect(GetResult(), querySizeResponse, source, GetRequestID()); } defaultproperties diff --git a/sources/Data/Database/Tasks/DBWriteTask.uc b/sources/Data/Database/Tasks/DBWriteTask.uc index 5c3961d..3036920 100644 --- a/sources/Data/Database/Tasks/DBWriteTask.uc +++ b/sources/Data/Database/Tasks/DBWriteTask.uc @@ -1,6 +1,6 @@ /** * Variant of `DBTask` for `WriteData()` query. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -19,7 +19,10 @@ */ class DBWriteTask extends DBTask; -delegate connect(Database.DBQueryResult result, Database source) {} +delegate connect( + Database.DBQueryResult result, + Database source, + int requestID) {} protected function Finalizer() { @@ -29,7 +32,7 @@ protected function Finalizer() protected function CompleteSelf(Database source) { - connect(GetResult(), source); + connect(GetResult(), source, GetRequestID()); } defaultproperties diff --git a/sources/Data/Database/Tests/TEST_DatabaseCommon.uc b/sources/Data/Database/Tests/TEST_DatabaseCommon.uc index 63b7992..22f7014 100644 --- a/sources/Data/Database/Tests/TEST_DatabaseCommon.uc +++ b/sources/Data/Database/Tests/TEST_DatabaseCommon.uc @@ -25,20 +25,20 @@ protected static function TESTS() local JSONPointer pointer; Context("Testing extracting `JSONPointer` from database link."); Issue("`JSONPointer` is incorrectly extracted."); - pointer = __().db.GetPointer( + pointer = __core().db.GetPointer( __().text.FromString("[local]default:/huh/what/is/")); TEST_ExpectNotNone(pointer); TEST_ExpectTrue(pointer.ToText().ToString() == "/huh/what/is/"); - pointer = __().db.GetPointer(__().text.FromString("[remote]db:")); + pointer = __core().db.GetPointer(__().text.FromString("[remote]db:")); TEST_ExpectNotNone(pointer); TEST_ExpectTrue(pointer.ToText().ToString() == ""); - pointer = __().db.GetPointer(__().text.FromString("[remote]:")); + pointer = __core().db.GetPointer(__().text.FromString("[remote]:")); TEST_ExpectNotNone(pointer); TEST_ExpectTrue(pointer.ToText().ToString() == ""); - pointer = __().db.GetPointer(__().text.FromString("db:/just/a/pointer")); + pointer = __core().db.GetPointer(__().text.FromString("db:/just/a/pointer")); TEST_ExpectNotNone(pointer); TEST_ExpectTrue(pointer.ToText().ToString() == "/just/a/pointer"); - pointer = __().db.GetPointer(__().text.FromString(":/just/a/pointer")); + pointer = __core().db.GetPointer(__().text.FromString(":/just/a/pointer")); TEST_ExpectNotNone(pointer); TEST_ExpectTrue(pointer.ToText().ToString() == "/just/a/pointer"); } diff --git a/sources/Data/Database/Tests/TEST_LocalDatabase.uc b/sources/Data/Database/Tests/TEST_LocalDatabase.uc index fd4200a..d983167 100644 --- a/sources/Data/Database/Tests/TEST_LocalDatabase.uc +++ b/sources/Data/Database/Tests/TEST_LocalDatabase.uc @@ -27,63 +27,78 @@ var protected Database.DBQueryResult resultType; var protected Database.DataType resultDataType; var protected HashTable resultData; var protected AcediaObject resultObject; +var protected int resultRequestID; protected function DBReadingHandler( Database.DBQueryResult result, AcediaObject data, - Database source) + Database source, + int requestID) { default.resultType = result; default.resultObject = data; default.resultData = HashTable(data); + default.resultRequestID = requestID; } protected function DBKeysHandler( Database.DBQueryResult result, ArrayList keys, - Database source) + Database source, + int requestID) { - default.resultType = result; - default.resultKeys = keys; + default.resultType = result; + default.resultKeys = keys; + default.resultRequestID = requestID; } protected function DBCheckHandler( Database.DBQueryResult result, Database.DataType type, - Database source) + Database source, + int requestID) { - default.resultType = result; - default.resultDataType = type; + default.resultType = result; + default.resultDataType = type; + default.resultRequestID = requestID; } protected function DBSizeHandler( Database.DBQueryResult result, int size, - Database source) + Database source, + int requestID) { - default.resultType = result; - default.resultSize = size; + default.resultType = result; + default.resultSize = size; + default.resultRequestID = requestID; } protected function DBWritingHandler( Database.DBQueryResult result, - Database source) + Database source, + int requestID) { - default.resultType = result; + default.resultType = result; + default.resultRequestID = requestID; } protected function DBIncrementHandler( Database.DBQueryResult result, - Database source) + Database source, + int requestID) { - default.resultType = result; + default.resultType = result; + default.resultRequestID = requestID; } protected function DBRemoveHandler( Database.DBQueryResult result, - Database source) + Database source, + int requestID) { - default.resultType = result; + default.resultType = result; + default.resultRequestID = requestID; } protected static function ReadFromDB(LocalDatabaseInstance db, string pointer) @@ -117,7 +132,7 @@ local LocalDatabaseInstance db; source = GetJSONTemplateString(); parser = __().text.ParseString(source); root = HashTable(__().json.ParseWith(parser)); -db = __().db.NewLocal(P("TEST_ReadOnly")); +db = __core().db.NewLocal(P("TEST_ReadOnly")); db.WriteData(__().json.Pointer(), root); */ protected static function string GetJSONTemplateString() @@ -220,19 +235,20 @@ protected static function TESTS() Test_TaskChaining(); Test_Removal(); Test_Increment(); + Test_RequestID(); } protected static function Test_LoadingPrepared() { local LocalDatabaseInstance db; - db = __().db.LoadLocal(P("TEST_ReadOnly")); + db = __core().db.LoadLocal(P("TEST_ReadOnly")); Context("Testing reading prepared data from the local database."); Issue("Existing database reported as missing."); - TEST_ExpectTrue(__().db.ExistsLocal(P("TEST_ReadOnly"))); + TEST_ExpectTrue(__core().db.ExistsLocal(P("TEST_ReadOnly"))); Issue("Loading same database several times produces different" @ "`LocalDatabaseInstance` objects."); - TEST_ExpectTrue(__().db.LoadLocal(P("TEST_ReadOnly")) == db); + TEST_ExpectTrue(__core().db.LoadLocal(P("TEST_ReadOnly")) == db); // Groups of read-only tests SubTest_LoadingPreparedSuccessRoot(db); SubTest_LoadingPreparedSuccessSubValues(db); @@ -471,18 +487,18 @@ protected static function SubTest_LoadingPreparedGetKeysFail( protected static function Test_Writing() { local LocalDatabaseInstance db; - db = __().db.NewLocal(P("TEST_DB")); + db = __core().db.NewLocal(P("TEST_DB")); Context("Testing (re-)creating and writing into a new local database."); Issue("Cannot create a new database."); TEST_ExpectNotNone(db); - TEST_ExpectTrue(__().db.ExistsLocal(P("TEST_DB"))); + TEST_ExpectTrue(__core().db.ExistsLocal(P("TEST_DB"))); Issue("Freshly created database is not empty."); TEST_ExpectTrue(CountRecordsInPackage("TEST_DB") == 1); // 1 root object Issue("Loading just created database produces different" @ "`LocalDatabaseInstance` object."); - TEST_ExpectTrue(__().db.LoadLocal(P("TEST_DB")) == db); + TEST_ExpectTrue(__core().db.LoadLocal(P("TEST_DB")) == db); // This set of tests fills our test database with objects SubTest_WritingSuccess(db); SubTest_WritingDataCheck(db); @@ -495,33 +511,33 @@ protected static function Test_Writing() @ "local database."); __().memory.Free(db); // For `NewLocal()` call __().memory.Free(db); // For `LoadLocal()` call - TEST_ExpectTrue(__().db.DeleteLocal(P("TEST_DB"))); + TEST_ExpectTrue(__core().db.DeleteLocal(P("TEST_DB"))); Issue("Newly created database is reported to still exist after deletion."); - TEST_ExpectFalse(__().db.ExistsLocal(P("TEST_DB"))); + TEST_ExpectFalse(__core().db.ExistsLocal(P("TEST_DB"))); TEST_ExpectFalse(db.IsAllocated()); Issue("`DeleteLocal()` does not return `false` after trying to delete" @ "non-existing local database."); - TEST_ExpectFalse(__().db.DeleteLocal(P("TEST_DB"))); + TEST_ExpectFalse(__core().db.DeleteLocal(P("TEST_DB"))); } protected static function Test_Recreate() { local LocalDatabaseInstance db; Issue("Freshly created database is not empty."); - db = __().db.NewLocal(P("TEST_DB")); + db = __core().db.NewLocal(P("TEST_DB")); TEST_ExpectTrue(CountRecordsInPackage("TEST_DB") == 1); Issue("Cannot create a database after database with the same name was" @ "just deleted."); TEST_ExpectNotNone(db); - TEST_ExpectTrue(__().db.ExistsLocal(P("TEST_DB"))); + TEST_ExpectTrue(__core().db.ExistsLocal(P("TEST_DB"))); SubTest_WritingArrayIndicies(db); - __().db.DeleteLocal(P("TEST_DB")); + __core().db.DeleteLocal(P("TEST_DB")); Issue("Newly created database is reported to still exist after deletion."); __().memory.Free(db); - TEST_ExpectFalse(__().db.ExistsLocal(P("TEST_DB"))); + TEST_ExpectFalse(__core().db.ExistsLocal(P("TEST_DB"))); TEST_ExpectFalse(db.IsAllocated()); } @@ -530,15 +546,15 @@ protected static function Test_TaskChaining() local LocalDatabaseInstance db; Context("Testing (re-)creating and writing into a new local database."); Issue("Freshly created database is not empty."); - db = __().db.NewLocal(P("TEST_DB")); + db = __core().db.NewLocal(P("TEST_DB")); TEST_ExpectTrue(CountRecordsInPackage("TEST_DB") == 1); Issue("Cannot create a database after database with the same name was" @ "just deleted."); TEST_ExpectNotNone(db); - TEST_ExpectTrue(__().db.ExistsLocal(P("TEST_DB"))); + TEST_ExpectTrue(__core().db.ExistsLocal(P("TEST_DB"))); SubTest_TaskChaining(db); - __().db.DeleteLocal(P("TEST_DB")); + __core().db.DeleteLocal(P("TEST_DB")); } protected static function HashTable GetJSONSubTemplateObject() @@ -776,7 +792,7 @@ protected static function Test_Removal() local HashTable templateObject; templateObject = GetJSONSubTemplateObject(); templateArray = GetJSONSubTemplateArray(); - db = __().db.NewLocal(P("TEST_DB")); + db = __core().db.NewLocal(P("TEST_DB")); db.WriteData(__().json.Pointer(P("")), templateObject); db.WriteData(__().json.Pointer(P("/B")), templateObject); db.WriteData(__().json.Pointer(P("/B/A")), templateArray); @@ -787,7 +803,7 @@ protected static function Test_Removal() SubTest_RemovalResult(db); SubTest_RemovalCheckValuesAfter(db); SubTest_RemovalRoot(db); - __().db.DeleteLocal(P("TEST_DB")); + __core().db.DeleteLocal(P("TEST_DB")); } protected static function SubTest_RemovalResult(LocalDatabaseInstance db) @@ -861,7 +877,7 @@ protected static function Test_Increment() local HashTable templateObject; templateObject = GetJSONSubTemplateObject(); templateArray = GetJSONSubTemplateArray(); - db = __().db.NewLocal(P("TEST_DB")); + db = __core().db.NewLocal(P("TEST_DB")); db.WriteData(__().json.Pointer(P("")), templateObject); db.WriteData(__().json.Pointer(P("/B")), templateObject); db.WriteData(__().json.Pointer(P("/C")), __().box.int(-5)); @@ -904,7 +920,7 @@ protected static function Test_Increment() Issue("Incrementing database values has created garbage objects."); // 5 initial records + 1 made for a new array in `SubTest_IncrementNull()` TEST_ExpectTrue(CountRecordsInPackage("TEST_DB") == 6); - __().db.DeleteLocal(P("TEST_DB")); + __core().db.DeleteLocal(P("TEST_DB")); } protected static function SubTest_IncrementNull(LocalDatabaseInstance db) @@ -1232,8 +1248,9 @@ protected static function SubTest_IncrementRewriteArray( protected static function SubTest_IncrementMissing(LocalDatabaseInstance db) { - local DBIncrementTask task; - Issue("New values are created in database after incrementing with path" + local DBIncrementTask task; + local DBCheckTask checkTask; + Issue("New values are not created in database after incrementing with path" @ "pointing to non-existing value."); task = db.IncrementData(__().json.Pointer(P("/L")), __().box.int(345)); task.connect = DBIncrementHandler; @@ -1245,13 +1262,168 @@ protected static function SubTest_IncrementMissing(LocalDatabaseInstance db) task.connect = DBIncrementHandler; task.TryCompleting(); TEST_ExpectTrue(default.resultType == DBR_Success); - db.CheckDataType(__().json.Pointer(P("/L"))).connect = DBCheckHandler; + checkTask = db.CheckDataType(__().json.Pointer(P("/L"))); + checkTask.connect = DBCheckHandler; + checkTask.TryCompleting(); + TEST_ExpectTrue(default.resultDataType == JSON_Number); + TEST_ExpectTrue(default.resultType == DBR_Success); ReadFromDB(db, "/B/A/1/"); TEST_ExpectTrue(default.resultDataType == JSON_Number); TEST_ExpectTrue(ArrayList(default.resultObject).GetLength() == 12); TEST_ExpectTrue(ArrayList(default.resultObject).GetInt(11) == 85); } +protected static function Test_RequestID() +{ + local LocalDatabaseInstance db; + local ArrayList templateArray; + local HashTable templateObject; + templateObject = GetJSONSubTemplateObject(); + templateArray = GetJSONSubTemplateArray(); + db = __core().db.NewLocal(P("TEST_DB")); + db.WriteData(__().json.Pointer(P("")), templateObject); + db.WriteData(__().json.Pointer(P("/B")), templateObject); + db.WriteData(__().json.Pointer(P("/C")), __().box.int(-5)); + db.WriteData(__().json.Pointer(P("/D")), __().box.bool(false)); + db.WriteData(__().json.Pointer(P("/B/A")), templateArray); + db.WriteData(__().json.Pointer(P("/B/A/1")), templateObject); + db.WriteData(__().json.Pointer(P("/B/A/1/")), templateArray); + /* `db` now contains: + { + "A": "simpleValue", + "B": { + "A": [true, { + "A": "simpleValue", + "B": 11.12, + "": [true, null, "huh"] + }, "huh"], + "B": 11.12 + }, + "C": -5, + "D": false + } + */ + // Constantly recreating `db` takes time, so we make test dependent + // on each other. + // Generally speaking this is not great, but we cannot run them in + // parallel anyway. + Context("Testing whether database operations report correct request ID."); + SubTest_RequestIDForCheck(db); + SubTest_RequestIDForIncrement(db); + SubTest_RequestIDForKeys(db); + SubTest_RequestIDForRead(db); + SubTest_RequestIDForRemove(db); + SubTest_RequestIDForSize(db); + SubTest_RequestIDForWrite(db); + __core().db.DeleteLocal(P("TEST_DB")); +} + +protected static function SubTest_RequestIDForCheck(LocalDatabaseInstance db) +{ + local DBCheckTask task; + + Issue("Type checking operation isn't returning correct request ID."); + task = db.CheckDataType(__().json.Pointer(P("/L"))); + task.connect = DBCheckHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 0); + task = db.CheckDataType(__().json.Pointer(P("/L")), 29); + task.connect = DBCheckHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 29); +} + +protected static function SubTest_RequestIDForIncrement( + LocalDatabaseInstance db) +{ + local DBIncrementTask task; + + Issue("Increment operation isn't returning correct request ID."); + task = db.IncrementData(__().json.Pointer(P("/L")), __().box.int(29)); + task.connect = DBIncrementHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 0); + task = db.IncrementData(__().json.Pointer(P("/L")), __().box.int(29), -7); + task.connect = DBIncrementHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == -7); +} + +protected static function SubTest_RequestIDForKeys(LocalDatabaseInstance db) +{ + local DBKeysTask task; + + Issue("Keys list operation isn't returning correct request ID."); + task = db.GetDataKeys(__().json.Pointer(P(""))); + task.connect = DBKeysHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 0); + task = db.GetDataKeys(__().json.Pointer(P("")), 11); + task.connect = DBKeysHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 11); +} + +protected static function SubTest_RequestIDForRead(LocalDatabaseInstance db) +{ + local DBReadTask task; + + Issue("Reading operation isn't returning correct request ID."); + task = db.ReadData(__().json.Pointer(P("/L")),); + task.connect = DBReadingHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 0); + task = db.ReadData(__().json.Pointer(P("/L")),, 666); + task.connect = DBReadingHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 666); +} + +protected static function SubTest_RequestIDForRemove(LocalDatabaseInstance db) +{ + local DBRemoveTask task; + + Issue("Removing operation isn't returning correct request ID."); + task = db.RemoveData(__().json.Pointer(P("/L"))); + task.connect = DBRemoveHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 0); + task = db.RemoveData(__().json.Pointer(P("/L")), 80); + task.connect = DBRemoveHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 80); +} + +protected static function SubTest_RequestIDForSize(LocalDatabaseInstance db) +{ + local DBSizeTask task; + + Issue("Size getting operation isn't returning correct request ID."); + task = db.GetDataSize(__().json.Pointer(P("/L"))); + task.connect = DBSizeHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 0); + task = db.GetDataSize(__().json.Pointer(P("/L")), 7); + task.connect = DBSizeHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 7); +} + +protected static function SubTest_RequestIDForWrite(LocalDatabaseInstance db) +{ + local DBWriteTask task; + + Issue("Writing operation isn't returning correct request ID."); + task = db.WriteData(__().json.Pointer(P("/L")), none); + task.connect = DBWritingHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 0); + task = db.WriteData(__().json.Pointer(P("/L")), none, 42); + task.connect = DBWritingHandler; + task.TryCompleting(); + TEST_ExpectTrue(default.resultRequestID == 42); +} + defaultproperties { caseGroup = "Database"