From 0aa3ba9fb29377e2d4bbd179d5bb6fc48fec1e8c Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sun, 7 Nov 2021 04:16:00 +0700 Subject: [PATCH] Add database links support into `DBAPI` --- sources/Data/Database/DBAPI.uc | 63 +++++++++++++++++++ sources/Data/Database/Local/LocalDatabase.uc | 10 ++- .../Database/Local/LocalDatabaseInstance.uc | 38 ++++++++++- .../Database/Tests/TEST_DatabaseCommon.uc | 47 ++++++++++++++ sources/Manifest.uc | 9 +-- 5 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 sources/Data/Database/Tests/TEST_DatabaseCommon.uc diff --git a/sources/Data/Database/DBAPI.uc b/sources/Data/Database/DBAPI.uc index ced06f9..9e428ce 100644 --- a/sources/Data/Database/DBAPI.uc +++ b/sources/Data/Database/DBAPI.uc @@ -34,6 +34,69 @@ private final function CreateLocalDBMapIfMissing() } } +/** + * Loads database based on the link. + * + * Links have the form of ":", followed by the JSON pointer + * (possibly empty one) to the object inside it. "" can be either "local" + * or "remote" and "" refers to the database that we are expected + * to load. This name has to consist of numbers and latin letters only. + * + * @param databaseLink Link from which to extract database's name. + * @return Database named "" of type "" from the `databaseLink`. + */ +public final function Database Load(Text databaseLink) +{ + local Parser parser; + local Database result; + local MutableText databaseName; + if (databaseLink == none) { + return none; + } + parser = _.text.Parse(databaseLink); + // Only local DBs are supported for now! + parser.Match(P("local:")); + if (!parser.Ok()) + { + parser.FreeSelf(); + return none; + } + parser.MUntil(databaseName, _.text.GetCharacter("/")); + result = LoadLocal(databaseName); + databaseName.FreeSelf(); + return result; +} + +/** + * Extracts `JSONPointer` from the database path, given by `databaseLink`. + * + * Links have the form of ":", followed by the JSON pointer + * (possibly empty one) to the object inside it. "" can be either "local" + * or "remote" and "" refers to the database that we are to use to + * get the data, specified in the link. + * This method returns `JSONPointer` that comes after type-name pair. + * + * @param Link from which to extract `JSONPointer`. + * @return `JSONPointer` from the database link. + */ +public final function JSONPointer GetPointer(Text databaseLink) +{ + local int slashIndex; + local Text textPointer; + local JSONPointer result; + if (databaseLink == none) { + return none; + } + slashIndex = databaseLink.IndexOf(P("/")); + if (slashIndex < 0) { + return JSONPointer(_.memory.Allocate(class'JSONPointer')); + } + textPointer = databaseLink.Copy(slashIndex); + result = _.json.Pointer(textPointer); + textPointer.FreeSelf(); + return result; +} + /** * Creates new local database with name `databaseName`. * diff --git a/sources/Data/Database/Local/LocalDatabase.uc b/sources/Data/Database/Local/LocalDatabase.uc index 9595498..868a716 100644 --- a/sources/Data/Database/Local/LocalDatabase.uc +++ b/sources/Data/Database/Local/LocalDatabase.uc @@ -25,7 +25,8 @@ class LocalDatabase extends AcediaObject perobjectconfig config(AcediaDB); -var config private string root; +var config private string root; +var config private bool enforceAcediaStructure; public final function Text GetPackageName() { @@ -42,6 +43,12 @@ public final function Text GetRootName() return __().text.FromString(root); } +public final function bool RequiresAcediaStructure() +{ + return enforceAcediaStructure; +} + + /** * Changes caller's root name. * @@ -97,4 +104,5 @@ public final function DeleteSelf() defaultproperties { + enforceAcediaStructure = false } \ No newline at end of file diff --git a/sources/Data/Database/Local/LocalDatabaseInstance.uc b/sources/Data/Database/Local/LocalDatabaseInstance.uc index b184eb8..bc8b704 100644 --- a/sources/Data/Database/Local/LocalDatabaseInstance.uc +++ b/sources/Data/Database/Local/LocalDatabaseInstance.uc @@ -86,6 +86,8 @@ var private DBTask lastTask; // Remember task's life version to make sure we still have the correct copy var private int lastTaskLifeVersion; +var private LoggerAPI.Definition warnDataErasedForAcediaStructure; + protected function Constructor() { _.unreal.OnTick(self).connect = CompleteAllTasks; @@ -334,11 +336,40 @@ public function DBIncrementTask IncrementData( */ public final function Initialize(LocalDatabase config, DBRecord root) { - if (configEntry != none) { - return; - } + if (configEntry != none) return; + if (config == none) return; + configEntry = config; rootRecord = root; + if (config.RequiresAcediaStructure()) { + CreateAcediaStructure(config); + } +} + +// Create JSON object at "/users" if something else (or nothing) +// is stored there +private final function CreateAcediaStructure(LocalDatabase config) +{ + local DataType usersType; + local JSONPointer usersPointer; + local AssociativeArray emptyObject; + if (config == none) return; + if (rootRecord == none) return; + + usersPointer = _.json.Pointer(P("/users")); + usersType = rootRecord.GetObjectType(usersPointer); + if (usersType != JSON_Object) + { + emptyObject = _.collections.EmptyAssociativeArray(); + rootRecord.SaveObject(usersPointer, emptyObject); + emptyObject.FreeSelf(); + if (usersType != JSON_Undefined) + { + _.logger.Auto(warnDataErasedForAcediaStructure) + .Arg(config.GetPackageName()); + } + } + usersPointer.FreeSelf(); } /** @@ -356,4 +387,5 @@ public final function LocalDatabase GetConfig() defaultproperties { usesObjectPool = false + warnDataErasedForAcediaStructure = (l=LOG_Warning,m="Data was erased at \"/users\" in database \"%1\" to create Acedia's database structure.") } \ No newline at end of file diff --git a/sources/Data/Database/Tests/TEST_DatabaseCommon.uc b/sources/Data/Database/Tests/TEST_DatabaseCommon.uc new file mode 100644 index 0000000..1049e4e --- /dev/null +++ b/sources/Data/Database/Tests/TEST_DatabaseCommon.uc @@ -0,0 +1,47 @@ +/** + * Set of tests for `DBRecord` class. + * 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 . + */ +class TEST_DatabaseCommon extends TestCase + abstract; + +protected static function TESTS() +{ + local JSONPointer pointer; + Context("Testing extracting `JSONPointer` from database link."); + Issue("`JSONPointer` is incorrectly extracted."); + pointer = + __().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")); + TEST_ExpectNotNone(pointer); + TEST_ExpectTrue(pointer.ToText().ToString() == ""); + pointer = __().db.GetPointer(__().text.FromString("remote:")); + TEST_ExpectNotNone(pointer); + TEST_ExpectTrue(pointer.ToText().ToString() == ""); + pointer = __().db.GetPointer(__().text.FromString("/just/a/pointer")); + TEST_ExpectNotNone(pointer); + TEST_ExpectTrue(pointer.ToText().ToString() == "/just/a/pointer"); +} + +defaultproperties +{ + caseGroup = "Database" + caseName = "Common database tests" +} \ No newline at end of file diff --git a/sources/Manifest.uc b/sources/Manifest.uc index 90b6c3f..19d8a41 100644 --- a/sources/Manifest.uc +++ b/sources/Manifest.uc @@ -55,8 +55,9 @@ defaultproperties testCases(19) = class'TEST_Command' testCases(20) = class'TEST_CommandDataBuilder' testCases(21) = class'TEST_LogMessage' - testCases(22) = class'TEST_LocalDatabase' - testCases(23) = class'TEST_AcediaConfig' - testCases(24) = class'TEST_UTF8EncoderDecoder' - testCases(25) = class'TEST_AvariceStreamReader' + testCases(22) = class'TEST_DatabaseCommon' + testCases(23) = class'TEST_LocalDatabase' + testCases(24) = class'TEST_AcediaConfig' + testCases(25) = class'TEST_UTF8EncoderDecoder' + testCases(26) = class'TEST_AvariceStreamReader' } \ No newline at end of file