Browse Source

Add request ID support to `DBAPI`

core_refactor
Anton Tarasenko 2 years ago
parent
commit
71dba2bac7
  1. 24
      sources/Data/Database/DBTask.uc
  2. 129
      sources/Data/Database/Database.uc
  3. 97
      sources/Data/Database/Local/LocalDatabaseInstance.uc
  4. 7
      sources/Data/Database/Tasks/DBCheckTask.uc
  5. 9
      sources/Data/Database/Tasks/DBIncrementTask.uc
  6. 9
      sources/Data/Database/Tasks/DBKeysTask.uc
  7. 9
      sources/Data/Database/Tasks/DBReadTask.uc
  8. 9
      sources/Data/Database/Tasks/DBRemoveTask.uc
  9. 10
      sources/Data/Database/Tasks/DBSizeTask.uc
  10. 9
      sources/Data/Database/Tasks/DBWriteTask.uc
  11. 10
      sources/Data/Database/Tests/TEST_DatabaseCommon.uc
  12. 252
      sources/Data/Database/Tests/TEST_LocalDatabase.uc

24
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;
}
/**

129
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;
}

97
sources/Data/Database/Local/LocalDatabaseInstance.uc

@ -149,54 +149,61 @@ private final function DBTask MakeNewTask(class<DBTask> 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();
}

7
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

9
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

9
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

9
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

9
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

10
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

9
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

10
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");
}

252
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"

Loading…
Cancel
Save