Browse Source

Add local database support

Add local database support and DB's documentation skeleton
pull/8/head
Anton Tarasenko 3 years ago
parent
commit
9039105ebf
  1. 114
      docs/API/Databases/index.md
  2. 200
      sources/Data/Database/DBAPI.uc
  3. 189
      sources/Data/Database/DBTask.uc
  4. 304
      sources/Data/Database/Database.uc
  5. 1160
      sources/Data/Database/Local/DBRecord.uc
  6. 53
      sources/Data/Database/Local/LocalDBSettings.uc
  7. 100
      sources/Data/Database/Local/LocalDatabase.uc
  8. 356
      sources/Data/Database/Local/LocalDatabaseInstance.uc
  9. 45
      sources/Data/Database/Tasks/DBCheckTask.uc
  10. 37
      sources/Data/Database/Tasks/DBIncrementTask.uc
  11. 45
      sources/Data/Database/Tasks/DBKeysTask.uc
  12. 45
      sources/Data/Database/Tasks/DBReadTask.uc
  13. 37
      sources/Data/Database/Tasks/DBRemoveTask.uc
  14. 45
      sources/Data/Database/Tasks/DBSizeTask.uc
  15. 37
      sources/Data/Database/Tasks/DBWriteTask.uc
  16. 1238
      sources/Data/Database/Tests/TEST_LocalDatabase.uc
  17. 2
      sources/Global.uc
  18. 1
      sources/Manifest.uc

114
docs/API/Databases/index.md

@ -0,0 +1,114 @@
# Databases
!!! Do not tell about recommended db type
!!! Tell about no intention to bother with access rights
While most mods' needs for storing data can be easily covered by config files, there are still use cases that require more powerful tools like databases:
* With config files alone it is impossible to share data between several different servers, especially if they are located on different machines;
* Representing hierarchical data in config files, while not impossible, can be quite tricky (and any generic implementation can itself be called a database).
Acedia provides it's own information storage functionality in form of databases that store information in JSON format. That is, every Acedia's database is represented by a JSON object, that can be interacted with by provided database API. Two implementations are provided:
1. **Remote database** *(not yet implemented)* that provides Acedia an ability to connect to Avarice database over TCP connection, allowing such database to be used by several servers at once;
2. **Local database** that store information in server's own directory, making it only accessible from that server. While using remote databases is recommended, local ones make sure that Acedia can function even if server admin does not want to use external software.
## Using databases
To demonstrate basic of working with Acedia's databases, let's consider a simple, practice problem: creating a feature that can remember shared text notes in the database and later display all the accumulated ones.
```unrealscript
class NoteTaker extends Feature;
var private Database myDatabase;
var private JSONPointer realmPointer;
var private LoggerAPI.Definition errFailedToRead, errBadData, errHadBadNotes;
protected function Constructor()
{
local DynamicArray emptyArray;
myDatabase = _.db.Realms();
realmPointer = _.db.RealmsPointer();
realmPointer.Push(P("NoteTaker"));
emptyArray = _.collections.EmptyDynamicArray();
db.IncrementData(realmPointer, emptyArray);
emptyArray.FreeSelf();
}
public function TakeNote(Text newNote)
{
local DynamicArray wrapper;
if (newNote == none) {
return;
}
wrapper = _.collections
.EmptyDynamicArray()
.AddItem(newNote);
db.IncrementData(realmPointer, wrapper);
wrapper.FreeSelf();
}
public function PrintAllNotes()
{
db.ReadData(realmPointer).connect = DoPrint;
}
private function DoPrint(DBQueryResult result, AcediaObject data)
{
local int i;
local bool hadBadNotes;
local Text nextNote;
local DynamicArray loadedArray;
if (result != DBR_Success)
{
_.logger.Auto(errFailedToRead);
_.memory.Free(data);
return;
}
loadedArray = DynamicArray(data);
if (loadedArray == none)
{
_.logger.Auto(errBadData);
_.memory.Free(data);
return;
}
for (i = 0; i < loadedArray.GetLength(); i += 1)
{
nextNote = loadedArray.GetText(i);
if (nextNote != none) {
Log("Note" @ (i+1) $ "." @ loadedArray.GetText(i).ToPlainString());
}
else {
hadBadNotes = true;
}
}
if (hadBadNotes) {
_.logger.Auto(errHadBadNotes);
}
_.memory.Free(data);
}
defaultproperties
{
errFailedToRead = (l=LOG_Error,m="Could not read notes data from the database!")
errBadData = (l=LOG_Error,m="Notes database contained invalid data!")
errHadBadNotes = (l=LOG_Error,m="Some of the notes had wrong data format!")
}
```
....
Acedia assumes that *creating* and *deleting* databases is server admins's responsibility, since they have to make a choice of what type of database to use. So unless you are making a feature that is supposed to manage databases, **you should attempt to create or delete databases**. You need, instead, load already existing one via one of the several ways. Easiest way is using *realms*:
```unrealscript
local Database db;
local JSONPointer ptr;
db = _.db.Realm(P("MyMod")).database;
ptr = _.db.Realm(P("MyMod")).pointer;
```
### Issues: database might already contain badly formatted data - check it
### Improvements: delete notes
### Improvements: loading only one note
## Further topics

200
sources/Data/Database/DBAPI.uc

@ -0,0 +1,200 @@
/**
* API that provides methods for creating/destroying and managing available
* databases.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBAPI extends AcediaObject;
var private const class<Database> localDBClass;
// Store all already loaded databases to make sure we do not create two
// different `LocalDatabaseInstance` that are trying to make changes
// separately.
var private AssociativeArray loadedLocalDatabases;
private final function CreateLocalDBMapIfMissing()
{
if (loadedLocalDatabases == none) {
loadedLocalDatabases = __().collections.EmptyAssociativeArray();
}
}
/**
* Creates new local database with name `databaseName`.
*
* This method will fail if:
* 1. `databaseName` is `none` or empty;
* 2. Local database with name `databaseName` already exists.
*
* @param databaseName Name for the new database.
* @return Reference to created database. Returns `none` iff method failed.
*/
public final function LocalDatabaseInstance NewLocal(Text databaseName)
{
local DBRecord rootRecord;
local Text rootRecordName;
local LocalDatabase newConfig;
local LocalDatabaseInstance newLocalDBInstance;
CreateLocalDBMapIfMissing();
// No need to check `databaseName` for being valid,
// since `Load()` will just return `none` if it is not.
newConfig = class'LocalDatabase'.static.Load(databaseName);
if (newConfig == none) return none;
if (newConfig.HasDefinedRoot()) return none;
if (loadedLocalDatabases.HasKey(databaseName)) return none;
newLocalDBInstance = LocalDatabaseInstance(_.memory.Allocate(localDBClass));
loadedLocalDatabases.SetItem(databaseName.Copy(), newLocalDBInstance);
rootRecord = class'DBRecord'.static.NewRecord(databaseName);
rootRecordName = _.text.FromString(string(rootRecord.name));
newConfig.SetRootName(rootRecordName);
newConfig.Save();
newLocalDBInstance.Initialize(newConfig, rootRecord);
_.memory.Free(rootRecordName);
return newLocalDBInstance;
}
/**
* Loads and returns local database with the name `databaseName`.
*
* If specified database is already loaded - simply returns it's reference
* (consequent calls to `LoadLocal()` will keep returning the same reference,
* unless database is deleted).
*
* @param databaseName Name of the database to load.
* @return Loaded local database. `none` if it does not exist.
*/
public final function LocalDatabaseInstance LoadLocal(Text databaseName)
{
local DBRecord rootRecord;
local Text rootRecordName;
local LocalDatabase newConfig;
local LocalDatabaseInstance newLocalDBInstance;
CreateLocalDBMapIfMissing();
if (loadedLocalDatabases.HasKey(databaseName))
{
return LocalDatabaseInstance(loadedLocalDatabases
.GetItem(databaseName));
}
// No need to check `databaseName` for being valid,
// since `Load()` will just return `none` if it is not.
newConfig = class'LocalDatabase'.static.Load(databaseName);
if (newConfig == none) return none;
if (!newConfig.HasDefinedRoot()) return none;
newLocalDBInstance = LocalDatabaseInstance(_.memory.Allocate(localDBClass));
loadedLocalDatabases.SetItem(databaseName.Copy(), newLocalDBInstance);
rootRecordName = newConfig.GetRootName();
rootRecord = class'DBRecord'.static
.LoadRecord(rootRecordName, databaseName);
newLocalDBInstance.Initialize(newConfig, rootRecord);
_.memory.Free(rootRecordName);
return newLocalDBInstance;
}
/**
* Checks if local database with the name `databaseName` already exists.
*
* @param databaseName Name of the database to check.
* @return `true` if database with specified name exists and `false` otherwise.
*/
public final function bool ExistsLocal(Text databaseName)
{
return LoadLocal(databaseName) != none;
}
/**
* Deletes local database with name `databaseName`.
*
* @param databaseName Name of the database to delete.
* @return `true` if database with specified name existed and was deleted and
* `false` otherwise.
*/
public final function bool DeleteLocal(Text databaseName)
{
local LocalDatabase localDatabaseConfig;
local LocalDatabaseInstance localDatabase;
local AssociativeArray.Entry dbEntry;
CreateLocalDBMapIfMissing();
// To delete database we first need to load it
localDatabase = LoadLocal(databaseName);
if (localDatabase != none) {
localDatabaseConfig = localDatabase.GetConfig();
}
dbEntry = loadedLocalDatabases.TakeEntry(databaseName);
// Delete `LocalDatabaseInstance` before erasing the package,
// to allow it to clean up safely
_.memory.Free(dbEntry.key);
_.memory.Free(dbEntry.value);
if (localDatabaseConfig != none) {
EraseAllPackageData(localDatabaseConfig.GetPackageName());
localDatabaseConfig.DeleteSelf();
return true;
}
return false;
}
private function EraseAllPackageData(Text packageToErase)
{
local int i;
local string packageName;
local GameInfo game;
local DBRecord nextRecord;
local array<DBRecord> allRecords;
packageName = _.text.ToString(packageToErase);
if (packageName == "") {
return;
}
game = _.unreal.GetGameType();
game.DeletePackage(packageName);
// Delete any leftover objects. This has to be done *after*
// `DeletePackage()` call, otherwise removed garbage can reappear.
// No clear idea why it works this way.
foreach game.AllDataObjects(class'DBRecord', nextRecord, packageName) {
allRecords[allRecords.length] = nextRecord;
}
for (i = 0; i < allRecords.length; i += 1)
{
game.DeleteDataObject( class'DBRecord', string(allRecords[i].name),
packageName);
}
}
/**
* Returns array of names of all available local databases.
*
* @return List of names of all local databases.
*/
public final function array<Text> ListLocal()
{
local int i;
local array<Text> dbNames;
local array<string> dbNamesAsStrings;
dbNamesAsStrings = GetPerObjectNames( "AcediaDB",
string(class'LocalDatabase'.name),
MaxInt);
for (i = 0; i < dbNamesAsStrings.length; i += 1) {
dbNames[dbNames.length] = _.text.FromString(dbNamesAsStrings[i]);
}
return dbNames;
}
defaultproperties
{
localDBClass = class'LocalDatabaseInstance'
}

189
sources/Data/Database/DBTask.uc

@ -0,0 +1,189 @@
/**
* This should be considered an internal class and a detail of
* implementation.
* An object that is created when user tries to query database.
* It contains a delegate `connect()` that will be called when query is
* 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
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBTask extends AcediaObject
dependson(Database)
abstract;
/**
* Life of instances of this class is supposed to go like so:
* 1. Get created and returned to the user that made database query so
* that he can setup a delegate that will receive the result;
* 2. Wait until database query result is ready AND all previous tasks
* have completed;
* 3. Call it's `connect()` delegate with query results;
* 4. Deallocate itself.
*
* Task is determined ready when it's `DBQueryResult` variable was set.
*
* This class IS NOT supposed to be accessed by user at all - this is simply
* an auxiliary construction that allows us to make calls to the database
* like so: `db.ReadData(...).connect = handler;`.
*
* Since every query can have it's own set of returning parameters -
* signature of `connect()` method can vary from task to task.
* For this reason we define it in child classes of `BDTask` that specialize in
* particular query.
*/
var private DBTask previousTask;
// These allows us to detect when previous task got completed (deallocated)
var private int previousTaskLifeVersion;
var private Database.DBQueryResult taskResult;
var private bool isReadyToComplete;
var private LoggerAPI.Definition errLoopInTaskChain;
protected function Finalizer()
{
if (previousTask != none) {
previousTask.FreeSelf(previousTaskLifeVersion);
}
previousTask = none;
previousTaskLifeVersion = -1;
isReadyToComplete = false;
}
/**
* Sets `DBQueryResult` for the caller task.
*
* Having previous task assigned is not required for the caller task to
* be completed, since it can be the first task.
*
* @param task Task that has to be completed before this one can.
*/
public final function SetPreviousTask(DBTask task)
{
previousTask = task;
if (previousTask != none) {
previousTaskLifeVersion = previousTask.GetLifeVersion();
}
}
/**
* Returns `DBQueryResult` assigned to the caller `DBTask`.
*
* This method should only be called after `SetResult()`, otherwise it's
* behavior and return result should be considered undefined.
*
* @return `DBQueryResult` assigned to the caller `DBTask`.
*/
public final function Database.DBQueryResult GetResult()
{
return taskResult;
}
/**
* Assigns `DBQueryResult` for the caller task.
*
* Every single task has to be assigned one and cannot be completed before
* it does.
*
* 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.
*/
public final function SetResult(Database.DBQueryResult result)
{
taskResult = result;
isReadyToComplete = true;
}
/**
* Override this to call `connect()` delegate declared in child classes.
* Since this base class does not itself have `connect()` delegate declared -
* this method cannot be implemented here.
*/
protected function CompleteSelf() {}
/**
* Attempts to complete this task.
* Can only succeed iff caller task both has necessary data to complete it's
* query and all previous tasks have completed.
*/
public final function TryCompleting()
{
local int i;
local array<DBTask> tasksQueue;
tasksQueue = BuildRequiredTasksQueue();
// Queue is built backwards: tasks that have to be completed first are
// at the end of the array
for (i = tasksQueue.length - 1; i >= 0; i -= 1)
{
if (tasksQueue[i].isReadyToComplete)
{
tasksQueue[i].CompleteSelf();
_.memory.Free(tasksQueue[i]);
}
else {
break;
}
}
}
// We do not know how deep `previousTask`-based chain will go, so we
// will store tasks that have to complete last earlier in the array.
private final function array<DBTask> BuildRequiredTasksQueue()
{
local int i;
local int expectedLifeVersion;
local bool loopDetected;
local DBTask nextRequiredTask;
local array<DBTask> tasksQueue;
nextRequiredTask = self;
tasksQueue[0] = nextRequiredTask;
while (nextRequiredTask.previousTask != none)
{
expectedLifeVersion = nextRequiredTask.previousTaskLifeVersion;
nextRequiredTask = nextRequiredTask.previousTask;
if (nextRequiredTask.GetLifeVersion() != expectedLifeVersion) {
break;
}
for (i = 0; i < tasksQueue.length; i += 1)
{
if (nextRequiredTask == tasksQueue[i])
{
loopDetected = true;
break;
}
}
if (!loopDetected) {
tasksQueue[tasksQueue.length] = nextRequiredTask;
}
else
{
_.logger.Auto(errLoopInTaskChain).ArgClass(nextRequiredTask.class);
break;
}
}
return tasksQueue;
}
defaultproperties
{
errLoopInTaskChain = (l=LOG_Error,m="`DBTask` of class `%1` required itself to complete. This might cause database to get damaged unexpectedly. Please report this to the developer.")
}

304
sources/Data/Database/Database.uc

@ -0,0 +1,304 @@
/**
* Interface database class that provides all Acedia's functionality for
* querying databases. For most of the cases, this is a class you are expected
* to work with and providing appropriate implementation is Acedia's `DBAPI`
* responsibility. Choice of the implementation is done based on user's
* config files.
* 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 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class Database extends AcediaObject
abstract;
/**
* Describes possible data types that can be stored in Acedia's databases.
* Lists consists of all possible JSON values types (with self-explanatory
* names) plus technical `JSON_Undefined` type that is used to indicate that
* a particular value does not exist.
*/
enum DataType
{
JSON_Undefined,
JSON_Null,
JSON_Boolean,
JSON_Number,
JSON_String,
JSON_Array,
JSON_Object
};
/**
* Possible outcomes of any query: success (only `DBR_Success`) or
* some kind of failure (any other value).
* This type is common for all queries, however reasons as to why
* a particular result value was obtained can differ from one to another.
*/
enum DBQueryResult
{
// Means query has succeeded;
DBR_Success,
// Query was provided with an invalid JSON pointer
// (`none` or somehow otherwise unfit to be used with a particular query);
DBR_InvalidPointer,
// Operation could not finish because database is damaged and unusable;
DBR_InvalidDatabase,
// Means that data (provided for the query) is somehow invalid.
DBR_InvalidData
};
/**
* Schedules reading data, located at the given `pointer` in
* the caller database.
*
* @param pointerToData JSON pointer to the value in database to read.
* `none` is always treated as an invalid JSON pointer.
* @param makeMutable Setting this to `false` (default) will force method
* 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.
* @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)`;
* * 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.
* * Possible `DBQueryResult` types are `DBR_Success`,
* `DBR_InvalidPointer` and `DBR_InvalidDatabase`;
* * `data != none` iff `result == DBR_Success`;//TODO: JSON null???
* * `DBR_InvalidPointer` can be produced if either `pointer == none` or
* it does not point at any existing value inside the caller database.
*/
public function DBReadTask ReadData(
JSONPointer pointer,
optional bool makeMutable)
{
return none;
}
/**
* Schedules writing `data` at the location inside the caller database,
* given by the `pointer`.
*
* Only `AssociativeArray` (that represents JSON object) can be recorded as
* a database's root value (referred to by an empty JSON pointer "").
*
* @param pointer JSON pointer to the location in the database, where `data`
* should be written (as a JSON value).
* `none` is always treated as an invalid JSON pointer.
* @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).
* @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)`;
* * Possible `DBQueryResult` types are `DBR_Success`,
* `DBR_InvalidPointer`, `DBR_InvalidDatabase` and `DBR_InvalidData`;
* * Data is actually written inside the database iff
* `result == DBR_Success`;
* * `result == DBR_InvalidData` iff either given `data`'s type is not
* JSON-compatible or a non-`AssociativeArray` was attempted to be
* recorded as caller database's root value;
* * `DBR_InvalidPointer` can be produced if either `pointer == none` or
* container of the value `pointer` points at does not exist.
* Example: writing data at "/sub-object/valueA" will always fail if
* "sub-object" does not exist.
*/
public function DBWriteTask WriteData(JSONPointer pointer, AcediaObject data)
{
return none;
}
/**
* Schedules removing data at the location inside the caller database,
* given by the `pointer`.
*
* "Removing" root object results in simply erasing all of it's stored data.
*
* @param pointer JSON pointer to the location of the data to remove from
* database. `none` is always treated as an invalid JSON pointer.
* @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)`.
* * Possible `DBQueryResult` types are `DBR_Success`,
* `DBR_InvalidPointer` and `DBR_InvalidDatabase`;
* * Data is actually removed from the database iff
* `result == DBR_Success`.
* * `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)
{
return none;
}
/**
* Schedules checking type of data at the location inside the caller database,
* given by the `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.
* @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)`;
* * Possible `DBQueryResult` types are `DBR_Success`,
* `DBR_InvalidPointer` and `DBR_InvalidDatabase`;
* * This task can only fail if either caller database is broken
* (task will produce `DBR_InvalidDatabase` result) or given `pointer`
* is `none` (task will produce `DBR_InvalidPointer` result).
* Otherwise the result will be `DBR_Success`.
* * Data is actually removed from the database iff
* `result == DBR_Success`.
*/
public function DBCheckTask CheckDataType(JSONPointer pointer)
{
return none;
}
/**
* Schedules obtaining "size": amount of elements stored inside
* either JSON object or JSON array, which location inside the caller database
* is given by provided `pointer`.
*
* For every JSON value that is neither object or array size is
* defined as `-1`.
*
* @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.
* @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)`.
* * Possible `DBQueryResult` types are `DBR_Success`,
* `DBR_InvalidPointer` and `DBR_InvalidDatabase`;
* * Returned `size` value is actually a size of referred
* JSON object/array inside the database iff `result == DBR_Success`;
* * `DBR_InvalidPointer` can be produced if either `pointer == none` or
* it does not point at a JSON object or array inside the
* caller database.
*/
public function DBSizeTask GetDataSize(JSONPointer pointer)
{
return none;
}
/**
* Schedules obtaining set of keys inside the JSON object, which location in
* the caller database is given by provided `pointer`.
*
* Only JSON objects have (and will return) keys (names of their sub-values).
*
* @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.
* @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, DynamicArray keys)`.
* * 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.
* * 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
* JSON object inside the database iff `result == DBR_Success`;
* * `DBR_InvalidPointer` can be produced iff `pointer == none`;
* * `result == DBR_InvalidData` iff `pointer != none`, but does not
* 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)
{
return none;
}
/**
* Schedules "incrementing" data, located at the given `pointer` in
* the caller database.
*
* "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);
* 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 -
* performs logical "or" operation. Otherwise fails;
* 3. JSON number: if combines with stored JSON numeric value -
* adds values together. Otherwise fails.
* 4. JSON string: if combines with stored JSON string value -
* concatenates itself at the end. Otherwise fails.
* 5. JSON array: if combines with stored JSON array value -
* concatenates itself at the end. Otherwise fails.
* 6. JSON object: if combines 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.
*
* @param pointer JSON pointer to the location in the database, where
* data should be incremented (by `increment`).
* `none` is always treated as an invalid JSON pointer.
* @param increment JSON-compatible value to be used as an increment for
* the data at the specified location inside the database.
* @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)`.
* * Possible `DBQueryResult` types are `DBR_Success`,
* `DBR_InvalidPointer`, `DBR_InvalidData` and `DBR_InvalidDatabase`;
* * Data is actually incremented iff `result == DBR_Success`;
* * `DBR_InvalidPointer` can be produced if either `pointer == none` or
* container of the value `pointer` points at does not exist.
* Example: incrementing data at "/sub-object/valueA" will always fail
* if "sub-object" does not exist.
* * `result == DBR_InvalidData` iff `pointer != none`, but does not
* point at a JSON value compatible (in the sense of "increment"
* operation) with `increment` parameter.
*/
public function DBIncrementTask IncrementData(
JSONPointer pointer,
AcediaObject increment)
{
return none;
}
defaultproperties
{
}

1160
sources/Data/Database/Local/DBRecord.uc

File diff suppressed because it is too large Load Diff

53
sources/Data/Database/Local/LocalDBSettings.uc

@ -0,0 +1,53 @@
/**
* Object for storing settings for the local databases. It is useless to
* allocate it's instances.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class LocalDBSettings extends AcediaObject
config(AcediaSystem);
// Acedia's local database stores it's JSON objects and arrays as
// named data objects inside a it's package file.
// Every object in a package must have a unique name, but neither
// JSON object/array's own name or it's path can be used since they can contain
// characters unusable for data object's name.
// That's why Acedia generates a random name for every object that consists
// of a sequence of latin letters. This value defines how many letters this
// sequence must contain. With default value of 20 letters it provides database
// with an ability to store up to
// 26^20 ~= 19,928,148,895,209,409,152,340,197,376
// different names, while also reducing probability of name collision for
// newly created objects to zero.
// There is really no need to modify this value and reducing it might
// lead to issues with database, so do not do it unless there is a really good
// reason to it.
var config public const int randomNameLength;
// Delay (in seconds) between consecutive writings of the database's
// content on the disk.
// Setting this value too low can cause loss of performance, while setting
// it too high might cause some of the data not being recorded and getting lost
// on crash.
// This delay is ignored in special circumstances when database object is
// forcefully destroyed (and upon level end).
var config public const float writeToDiskDelay;
defaultproperties
{
randomNameLength = 20
writeToDiskDelay = 10.0
}

100
sources/Data/Database/Local/LocalDatabase.uc

@ -0,0 +1,100 @@
/**
* This class IS NOT an implementation for `Database` interface and
* simply exists to store config information about some local database.
* Name is chosen to make user configs more readable.
* This class is considered an internal object and should only be referred
* to inside AcediaCore package.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class LocalDatabase extends AcediaObject
perobjectconfig
config(AcediaDB);
var config private string root;
public final function Text GetPackageName()
{
return __().text.FromString(string(name));
}
public final function bool HasDefinedRoot()
{
return root != "";
}
public final function Text GetRootName()
{
return __().text.FromString(root);
}
/**
* Changes caller's root name.
*
* Only makes changes if root is not already defined.
*/
public final function SetRootName(Text rootName)
{
if (HasDefinedRoot()) {
return;
}
if (rootName != none) {
root = rootName.ToPlainString();
}
else {
root = "";
}
}
public final static function LocalDatabase Load(Text databaseName)
{
if (!__().text.IsEmpty(databaseName)) {
return new(none, databaseName.ToPlainString()) class'LocalDatabase';
}
return none;
}
/**
* Updates `LocalDatabase` record inside it's config file. If caller
* `LocalDatabase` does not have defined root `HasDefinedRoot() == none`,
* then this method will erase it's record from the config.
*/
public final function Save()
{
if (HasDefinedRoot()) {
SaveConfig();
}
else {
ClearConfig();
}
}
/**
* Forgets all information stored in the caller `LocalDatabase` and erases it
* from the config files. After this call, creating `LocalDatabase` object
* with the same name will produce an object that can be treated as "blank":
* one will be able to use it to store information about new database.
*/
public final function DeleteSelf()
{
root = "";
ClearConfig();
}
defaultproperties
{
}

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

@ -0,0 +1,356 @@
/**
* Implementation of Acedia's `Database` interface for locally stored
* databases.
* This class SHOULD NOT be deallocated manually.
* This name was chosen so that more readable `LocalDatabase` could be
* used in config for defining local databases through per-object-config.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class LocalDatabaseInstance extends Database;
/**
* `LocalDatabaseInstance` implements `Database` interface for
* local databases, however most of the work (everything related to actually
* performing operations) is handled by `DBRecord` class.
* This class' purpose is to:
* 1. Managing updating information stored on the disk: it has to make
* sure that saving is (eventually) done after every update, but not
* too often, since it is an expensive operation;
* 2. Making sure handlers for database queries are called (eventually).
* First point is done via starting a "cooldown" timer after every disk
* update that will count time until the next one. Second is done by storing
* `DBTask`, generated by last database query and making it call it's handler
* at the start of next tick.
*
* Why do we wait until the next tick?
* Factually, every `LocalDatabaseInstance`'s query is completed immediately.
* However, `Database`'s interface is designed to be used like so:
* `db.ReadData(...).connect = handler;` where `handler` for query is assigned
* AFTER it was filed to the database. Therefore, we cannot call `handler`
* inside `ReadData()` and wait until next tick instead.
* We could have allowed for immediate query response if we either
* requested that handler was somehow set before the query or by providing
* a method to immediately call handlers for queries users have made so far.
* We avoided these solutions because we intend Acedia's `Database` interface
* to be used in the same way regardless of whether server admins have chosen
* to use local or remote databases. And neither of these solutions would have
* worked with inherently asynchronous remote databases. That is why we instead
* opted to use a more convenient interface
* `db.ReadData(...).connect = handler;` and have both databases behave
* the same way - with somewhat delayed response from the database.
* If you absolutely must force your local database to have an immediate
* response, then you can do it like so:
* ```unrealscript
* local DBTask task;
* ...
* task = db.ReadData(...);
* task.connect = handler;
* task.TryCompleting();
* ```
* However this method is not recommended and will never be a part of
* a stable interface.
*/
// Reference to the `LocalDatabase` config object, corresponding to
// this database
var private LocalDatabase configEntry;
// Reference to the `DBRecord` that stores root object of this database
var private DBRecord rootRecord;
// As long as this `Timer` runs - we are in the "cooldown" period where no disk
// updates can be done (except special cases like this object getting
// deallocated).
var private Timer diskUpdateTimer;
// Only relevant when `diskUpdateTimer` is running. `false` would mean there is
// nothing to new to write and the timer will be discarded, but `true` means
// that we have to write database on disk and restart the update timer again.
var private bool needsDiskUpdate;
// Last to-be-completed task added to this database
var private DBTask lastTask;
// Remember task's life version to make sure we still have the correct copy
var private int lastTaskLifeVersion;
protected function Constructor()
{
_.unreal.OnTick(self).connect = CompleteAllTasks;
}
protected function Finalizer()
{
// Defaulting variables is not necessary, since this class does not
// use object pool.
CompleteAllTasks();
_.unreal.OnTick(self).Disconnect();
_.memory.Free(diskUpdateTimer);
}
// It only has parameters so that it can be used as a `Tick()` event handler.
private final function CompleteAllTasks(
optional float delta,
optional float dilationCoefficient)
{
if (lastTask != none && lastTask.GetLifeVersion() == lastTaskLifeVersion) {
lastTask.TryCompleting();
}
lastTask = none;
lastTaskLifeVersion = -1;
}
private final function LocalDatabaseInstance ScheduleDiskUpdate()
{
if (diskUpdateTimer != none)
{
needsDiskUpdate = true;
return self;
}
WriteToDisk();
needsDiskUpdate = false;
diskUpdateTimer = _.time.StartTimer(
class'LocalDBSettings'.default.writeToDiskDelay);
diskUpdateTimer.OnElapsed(self).connect = DoDiskUpdate;
return self;
}
private final function DoDiskUpdate(Timer source)
{
if (needsDiskUpdate)
{
WriteToDisk();
needsDiskUpdate = false;
diskUpdateTimer.Start();
}
else
{
_.memory.Free(diskUpdateTimer);
diskUpdateTimer = none;
}
}
private final function WriteToDisk()
{
local string packageName;
if (configEntry != none) {
packageName = _.text.ToString(configEntry.GetPackageName());
}
if (packageName != "") {
_.unreal.GetGameType().SavePackage(packageName);
}
}
private final function DBTask MakeNewTask(class<DBTask> newTaskClass)
{
local DBTask newTask;
if (lastTask != none && lastTask.GetLifeVersion() != lastTaskLifeVersion)
{
lastTask = none;
lastTaskLifeVersion = -1;
}
newTask = DBTask(_.memory.Allocate(newTaskClass));
newTask.SetPreviousTask(lastTask);
lastTask = newTask;
lastTaskLifeVersion = lastTask.GetLifeVersion();
return newTask;
}
private function bool ValidatePointer(JSONPointer pointer, DBTask relevantTask)
{
if (pointer != none) {
return true;
}
relevantTask.SetResult(DBR_InvalidPointer);
return false;
}
private function bool ValidateRootRecord(DBTask relevantTask)
{
if (rootRecord != none) {
return true;
}
relevantTask.SetResult(DBR_InvalidDatabase);
return false;
}
public function DBReadTask ReadData(
JSONPointer pointer,
optional bool makeMutable)
{
local AcediaObject queryResult;
local DBReadTask readTask;
readTask = DBReadTask(MakeNewTask(class'DBReadTask'));
if (!ValidatePointer(pointer, readTask)) return readTask;
if (!ValidateRootRecord(readTask)) return readTask;
if (rootRecord.LoadObject(pointer, queryResult, makeMutable))
{
readTask.SetReadData(queryResult);
readTask.SetResult(DBR_Success);
}
else
{
readTask.SetResult(DBR_InvalidPointer);
_.memory.Free(queryResult); // just in case
}
return readTask;
}
public function DBWriteTask WriteData(JSONPointer pointer, AcediaObject data)
{
local bool isDataStorable;
local DBWriteTask writeTask;
writeTask = DBWriteTask(MakeNewTask(class'DBWriteTask'));
if (!ValidatePointer(pointer, writeTask)) return writeTask;
if (!ValidateRootRecord(writeTask)) return writeTask;
if (pointer.GetLength() <= 0) {
isDataStorable = (data.class == class'AssociativeArray');
}
else {
isDataStorable = _.json.IsCompatible(data);
}
if (!isDataStorable)
{
writeTask.SetResult(DBR_InvalidData);
return writeTask;
}
if (rootRecord.SaveObject(pointer, data))
{
writeTask.SetResult(DBR_Success);
ScheduleDiskUpdate();
}
else {
writeTask.SetResult(DBR_InvalidPointer);
}
return writeTask;
}
public function DBRemoveTask RemoveData(JSONPointer pointer)
{
local DBRemoveTask removeTask;
removeTask = DBRemoveTask(MakeNewTask(class'DBRemoveTask'));
if (!ValidatePointer(pointer, removeTask)) return removeTask;
if (!ValidateRootRecord(removeTask)) return removeTask;
if (pointer.GetLength() == 0)
{
rootRecord.EmptySelf();
removeTask.SetResult(DBR_Success);
return removeTask;
}
if (rootRecord.RemoveObject(pointer))
{
removeTask.SetResult(DBR_Success);
ScheduleDiskUpdate();
}
else {
removeTask.SetResult(DBR_InvalidPointer);
}
return removeTask;
}
public function DBCheckTask CheckDataType(JSONPointer pointer)
{
local DBCheckTask checkTask;
checkTask = DBCheckTask(MakeNewTask(class'DBCheckTask'));
if (!ValidatePointer(pointer, checkTask)) return checkTask;
if (!ValidateRootRecord(checkTask)) return checkTask;
checkTask.SetDataType(rootRecord.GetObjectType(pointer));
checkTask.SetResult(DBR_Success);
return checkTask;
}
public function DBSizeTask GetDataSize(JSONPointer pointer)
{
local DBSizeTask sizeTask;
sizeTask = DBSizeTask(MakeNewTask(class'DBSizeTask'));
if (!ValidatePointer(pointer, sizeTask)) return sizeTask;
if (!ValidateRootRecord(sizeTask)) return sizeTask;
sizeTask.SetDataSize(rootRecord.GetObjectSize(pointer));
sizeTask.SetResult(DBR_Success);
return sizeTask;
}
public function DBKeysTask GetDataKeys(JSONPointer pointer)
{
local DynamicArray keys;
local DBKeysTask keysTask;
keysTask = DBKeysTask(MakeNewTask(class'DBKeysTask'));
if (!ValidatePointer(pointer, keysTask)) return keysTask;
if (!ValidateRootRecord(keysTask)) return keysTask;
keys = rootRecord.GetObjectKeys(pointer);
keysTask.SetDataKeys(keys);
if (keys == none) {
keysTask.SetResult(DBR_InvalidData);
}
else {
keysTask.SetResult(DBR_Success);
}
return keysTask;
}
public function DBIncrementTask IncrementData(
JSONPointer pointer,
AcediaObject increment)
{
local DBQueryResult queryResult;
local DBIncrementTask incrementTask;
incrementTask = DBIncrementTask(MakeNewTask(class'DBIncrementTask'));
if (!ValidatePointer(pointer, incrementTask)) return incrementTask;
if (!ValidateRootRecord(incrementTask)) return incrementTask;
queryResult = rootRecord.IncrementObject(pointer, increment);
incrementTask.SetResult(queryResult);
if (queryResult == DBR_Success) {
ScheduleDiskUpdate();
}
return incrementTask;
}
/**
* Initializes caller database with prepared config and root objects.
*
* This is internal method and should not be called outside of `DBAPI`.
*/
public final function Initialize(LocalDatabase config, DBRecord root)
{
if (configEntry != none) {
return;
}
configEntry = config;
rootRecord = root;
}
/**
* Returns config object that describes caller database.
*
* @return Config object that describes caller database.
* returned value is the same value caller database uses,
* it IS NOT a copy and SHOULD NOT be deallocated or deleted.
*/
public final function LocalDatabase GetConfig()
{
return configEntry;
}
defaultproperties
{
usesObjectPool = false
}

45
sources/Data/Database/Tasks/DBCheckTask.uc

@ -0,0 +1,45 @@
/**
* Variant of `DBTask` for `CheckDataType()` query.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBCheckTask extends DBTask;
var private Database.DataType queryTypeResponse;
delegate connect(Database.DBQueryResult result, Database.DataType type) {}
protected function Finalizer()
{
super.Finalizer();
queryTypeResponse = JSON_Undefined;
connect = none;
}
public function SetDataType(Database.DataType type)
{
queryTypeResponse = type;
}
protected function CompleteSelf()
{
connect(GetResult(), queryTypeResponse);
}
defaultproperties
{
}

37
sources/Data/Database/Tasks/DBIncrementTask.uc

@ -0,0 +1,37 @@
/**
* Variant of `DBTask` for `IncrementData()` query.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBIncrementTask extends DBTask;
delegate connect(Database.DBQueryResult result) {}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
protected function CompleteSelf()
{
connect(GetResult());
}
defaultproperties
{
}

45
sources/Data/Database/Tasks/DBKeysTask.uc

@ -0,0 +1,45 @@
/**
* Variant of `DBTask` for `GetDataKeys()` query.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBKeysTask extends DBTask;
var private DynamicArray queryKeysResponse;
delegate connect(Database.DBQueryResult result, DynamicArray keys) {}
protected function Finalizer()
{
super.Finalizer();
queryKeysResponse = none;
connect = none;
}
public function SetDataKeys(DynamicArray keys)
{
queryKeysResponse = keys;
}
protected function CompleteSelf()
{
connect(GetResult(), queryKeysResponse);
}
defaultproperties
{
}

45
sources/Data/Database/Tasks/DBReadTask.uc

@ -0,0 +1,45 @@
/**
* Variant of `DBTask` for `ReadData()` query.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBReadTask extends DBTask;
var private AcediaObject queryDataResponse;
delegate connect(Database.DBQueryResult result, AcediaObject data) {}
protected function Finalizer()
{
super.Finalizer();
queryDataResponse = none;
connect = none;
}
public function SetReadData(AcediaObject data)
{
queryDataResponse = data;
}
protected function CompleteSelf()
{
connect(GetResult(), queryDataResponse);
}
defaultproperties
{
}

37
sources/Data/Database/Tasks/DBRemoveTask.uc

@ -0,0 +1,37 @@
/**
* Variant of `DBTask` for `RemoveData()` query.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBRemoveTask extends DBTask;
delegate connect(Database.DBQueryResult result) {}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
protected function CompleteSelf()
{
connect(GetResult());
}
defaultproperties
{
}

45
sources/Data/Database/Tasks/DBSizeTask.uc

@ -0,0 +1,45 @@
/**
* Variant of `DBTask` for `GetDataSize()` query.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBSizeTask extends DBTask;
var private int querySizeResponse;
delegate connect(Database.DBQueryResult result, int size) {}
protected function Finalizer()
{
super.Finalizer();
querySizeResponse = 0;
connect = none;
}
public function SetDataSize(int size)
{
querySizeResponse = size;
}
protected function CompleteSelf()
{
connect(GetResult(), querySizeResponse);
}
defaultproperties
{
}

37
sources/Data/Database/Tasks/DBWriteTask.uc

@ -0,0 +1,37 @@
/**
* Variant of `DBTask` for `WriteData()` query.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class DBWriteTask extends DBTask;
delegate connect(Database.DBQueryResult result) {}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
protected function CompleteSelf()
{
connect(GetResult());
}
defaultproperties
{
}

1238
sources/Data/Database/Tests/TEST_LocalDatabase.uc

File diff suppressed because it is too large Load Diff

2
sources/Global.uc

@ -39,6 +39,7 @@ var public ColorAPI color;
var public UserAPI users;
var public PlayersAPI players;
var public JSONAPI json;
var public DBAPI db;
var public KFFrontend kf;
@ -72,6 +73,7 @@ protected function Initialize()
users = UserAPI(memory.Allocate(class'UserAPI'));
players = PlayersAPI(memory.Allocate(class'PlayersAPI'));
json = JSONAPI(memory.Allocate(class'JSONAPI'));
db = DBAPI(memory.Allocate(class'DBAPI'));
kf = KFFrontend(memory.Allocate(class'KF1_Frontend'));
json.StaticConstructor();
}

1
sources/Manifest.uc

@ -54,4 +54,5 @@ defaultproperties
testCases(19) = class'TEST_Command'
testCases(20) = class'TEST_CommandDataBuilder'
testCases(21) = class'TEST_LogMessage'
testCases(22) = class'TEST_LocalDatabase'
}
Loading…
Cancel
Save