diff --git a/.vscode/launch.json b/.vscode/launch.json index a12ffb326dad1b2f113bf70239d67f6ddd1c4195..4c467e7af97849455887aad9be0005103afc24fa 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -291,6 +291,32 @@ "visualizerFile": "${env:LRS_PKG_ROOT}/src/qt-natvis/qt5.natvis.xml", "showDisplayString": true }, + { + "name": "TestEngine (kDB::SMQuery) (gdb)", + "type": "cppdbg", + "request": "launch", + "program": "${env:LRS_PKG_ROOT}/host-build-debug/kDB/kDB/SMQuery/tests/TestEngine", + "args": [], + "stopAtEntry": false, + "cwd": "${env:LRS_PKG_ROOT}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "visualizerFile": "${env:LRS_PKG_ROOT}/src/qt-natvis/qt5.natvis.xml", + "showDisplayString": true + }, ] } diff --git a/docs/doxygen/pages/kDBSMQuery.dox b/docs/doxygen/pages/kDBSMQuery.dox new file mode 100644 index 0000000000000000000000000000000000000000..b5c5821209b47be76a98fc45bb40e95113875ab7 --- /dev/null +++ b/docs/doxygen/pages/kDBSMQuery.dox @@ -0,0 +1,19 @@ +/** +\page kDB_SMQuery kDB::SMQuery Library +\defgroup kDB_SMQuery + +kDB::SMQuery is a kDB library which allows to execute Simple Management Query. + +The idea is for the engine to be able to execute query of the form + +@code +SOME KEY name1: value1 name2: value2 ... +@endcode + +For instance: + +@code +CREATE DATASETS uri: <uri_of_dataset> +@endcode + +*/ \ No newline at end of file diff --git a/docs/doxygen/pages/mainpage.dox b/docs/doxygen/pages/mainpage.dox index 49d3b8f792beae90153fe53a25c7f62fe0a41ebb..09e4d0e158bdcb6cc7b2e8c564c55a9ae6177d7f 100644 --- a/docs/doxygen/pages/mainpage.dox +++ b/docs/doxygen/pages/mainpage.dox @@ -1,7 +1,12 @@ /** \mainpage kDB: Knowledge DataBase \section kdb_overview General overview + +\section kDB_core Core Libraries + - \ref kDB_SMQuery Simple Management Query + \section kdb_extensions Extensions + - \ref kDBDatasets extension to manage datasets - \ref kDBQueries extension to handle query templates in C++/Python */ diff --git a/kDB/CMakeLists.txt b/kDB/CMakeLists.txt index df5f029158eff0ba18d2b00007b74a85f143b79b..178d975f52983a8feeb5e543b54c96e21f5a05a7 100644 --- a/kDB/CMakeLists.txt +++ b/kDB/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(RDFDB) add_subdirectory(Repository) add_subdirectory(RDFView) +add_subdirectory(SMQuery) add_subdirectory(SPARQL) add_subdirectory(Utils) diff --git a/kDB/Forward.h b/kDB/Forward.h index bd61d9b6a54bfd83753e2b616e263e294f817a19..11d1216984ed8960573891dd51e6e72b69231e46 100644 --- a/kDB/Forward.h +++ b/kDB/Forward.h @@ -69,4 +69,12 @@ namespace kDB { class Database; } + namespace SMQuery + { + class Engine; + namespace Interfaces + { + class Action; + } + } } diff --git a/kDB/SMQuery/CMakeLists.txt b/kDB/SMQuery/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..33c98b59ff7a7fd7ce49151439d790816da694f8 --- /dev/null +++ b/kDB/SMQuery/CMakeLists.txt @@ -0,0 +1,24 @@ +add_subdirectory(tests) + +# Create library + +set(KDBSMQUERY_SRCS + Engine.cpp + Parser.cpp + Interfaces/Interfaces.cpp + ) + +add_library(kDBSMQuery SHARED ${KDBSMQUERY_SRCS}) +target_link_libraries(kDBSMQuery PUBLIC knowCore ) + +# Install +install(TARGETS kDBSMQuery EXPORT kDBTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) +set(PROJECT_EXPORTED_TARGETS kDBSMQuery ${PROJECT_EXPORTED_TARGETS} CACHE INTERNAL "") + +install( FILES + Engine.h + DESTINATION ${INSTALL_INCLUDE_DIR}/kDBSMQuery ) + + install( FILES + Interfaces/Action.h + DESTINATION ${INSTALL_INCLUDE_DIR}/kDBSMQuery/Interfaces ) diff --git a/kDB/SMQuery/Engine.cpp b/kDB/SMQuery/Engine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8a9230921a3b36cf19d14e0b200372bf792fa98d --- /dev/null +++ b/kDB/SMQuery/Engine.cpp @@ -0,0 +1,46 @@ +#include "Engine.h" + +#include <QHash> + +#include "Parser.h" +#include "Interfaces/Action.h" + +using namespace kDB::SMQuery; + +struct Engine::Private +{ + QHash<QString, Interfaces::Action*> key2action; + QList<Interfaces::Action*> actions; +}; + +Engine::Engine() : d(new Private) +{ + +} + +Engine::~Engine() +{ + qDeleteAll(d->actions); + delete d; +} + +void Engine::add(const QStringList& _keys, Interfaces::Action* _action) +{ + d->actions.append(_action); + for(const QString& key : _keys) + { + d->key2action[key.toUpper()] = _action; + } +} + +knowCore::ReturnVoid Engine::execute(const QString& _text) +{ + KNOWCORE_RETURN_VALUE_TRY(query, Parser::parse(_text)); + QString key = query.key.join(" ").toUpper(); + if(d->key2action.contains(key)) + { + return d->key2action.value(key)->execute(key, query.parameters); + } else { + return kCrvError("No action for '{}'", key); + } +} diff --git a/kDB/SMQuery/Engine.h b/kDB/SMQuery/Engine.h new file mode 100644 index 0000000000000000000000000000000000000000..86c390e2070c51126905d4c3457bf331edff1229 --- /dev/null +++ b/kDB/SMQuery/Engine.h @@ -0,0 +1,31 @@ +#include <QStringList> + +#include <kDB/Forward.h> + +namespace kDB::SMQuery +{ + /** + * @ingroup kDB_SMQuery + * + * Execution engine for Simple Management Query + */ + class Engine + { + public: + Engine(); + ~Engine(); + /** + * Add an \ref _action for the list of \ref _keys. + * + * _keys take the form of "SOME ACTION" and will react to query with "SOME ACTION". + */ + void add(const QStringList& _keys, Interfaces::Action* _action); + /** + * Execute a query. + */ + knowCore::ReturnVoid execute(const QString& _text); + private: + struct Private; + Private* const d; + }; +} \ No newline at end of file diff --git a/kDB/SMQuery/Forward.h b/kDB/SMQuery/Forward.h new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/kDB/SMQuery/Interfaces/Action.h b/kDB/SMQuery/Interfaces/Action.h new file mode 100644 index 0000000000000000000000000000000000000000..e5ca21a1779f4f9f88189dfa304b166b0dbc0dbc --- /dev/null +++ b/kDB/SMQuery/Interfaces/Action.h @@ -0,0 +1,19 @@ +#include <knowCore/Forward.h> + +namespace kDB::SMQuery::Interfaces +{ + /** + * @ingroup kDB_SMQuery + * + * Define an action to be executed by the SMQuery engine + */ + class Action + { + public: + virtual ~Action(); + /** + * Execute the action with the given \ref _key and \ref _parameters + */ + virtual knowCore::ReturnVoid execute(const QString& _key, const knowCore::ValueHash& _parameters) = 0; + }; +} diff --git a/kDB/SMQuery/Interfaces/Interfaces.cpp b/kDB/SMQuery/Interfaces/Interfaces.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5f1cfda7091cb418c9673fa371c915e213a5b85 --- /dev/null +++ b/kDB/SMQuery/Interfaces/Interfaces.cpp @@ -0,0 +1,6 @@ +#include "Action.h" + +using namespace kDB::SMQuery::Interfaces; + +Action::~Action() +{} diff --git a/kDB/SMQuery/Parser.cpp b/kDB/SMQuery/Parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cdc698254b372b3b432a3f58eb3583f8873a946e --- /dev/null +++ b/kDB/SMQuery/Parser.cpp @@ -0,0 +1,167 @@ +#include "Parser.h" + +#include <knowCore/BigNumber.h> +#include <knowCore/LexerTextStream.h> + +using namespace kDB::SMQuery; + +struct Parser::Private +{ + struct Token + { + enum class Type { + END_OF_FILE, + IDENTIFIER, + COLON, + VALUE + }; + Type type; + QString text; + knowCore::Value value; + }; + QString getIdentifier(knowCore::LexerTextStream::Element lastChar); + knowCore::ReturnValue<Token> nextToken(); + + knowCore::LexerTextStream stream; +}; + +QString Parser::Private::getIdentifier(knowCore::LexerTextStream::Element lastChar) +{ + QString identifierStr; + if( lastChar.content != 0) { + identifierStr = lastChar.content; + } + while (not stream.eof() ) + { + lastChar = stream.getNextChar(); + if( lastChar.isLetter() or lastChar == '_') + { + identifierStr += lastChar.content; + } else { + stream.unget(lastChar); + break; + } + } + return identifierStr; +} + +knowCore::ReturnValue<Parser::Private::Token> Parser::Private::nextToken() +{ + using Token = Private::Token; + knowCore::LexerTextStream::Element lastChar = stream.getNextNonSeparatorChar(); + const knowCore::LexerTextStream::Element firstChar = lastChar; + if( lastChar == EOF ) return kCrvSuccess(Token{Token::Type::END_OF_FILE, QString(), knowCore::Value()}); + + KNOWCORE_ASSERT(lastChar.content.size() == 1); + switch(lastChar.content.at(0).toLatin1()) + { + case '<': + { // This an URI + auto [string, finished] = stream.getString(">"); + if(finished) + { + return kCrvSuccess(Token{Token::Type::VALUE, QString(), knowCore::Value::fromValue(knowCore::Uri(string.content))}); + } else { + return kCrvError("Unfinished uri: {}", string.content); + } + } + case '"': + { // This a String + auto [string, finished] = stream.getString("\""); + if(finished) + { + return kCrvSuccess(Token{Token::Type::VALUE, QString(), knowCore::Value::fromValue(string.content)}); + } else { + return kCrvError("Unfinished uri: {}", string.content); + } + } + case ':': + return kCrvSuccess(Token{Token::Type::COLON, QString(), knowCore::Value()}); + default: + { + if(lastChar.isLetter()) + { + return kCrvSuccess(Token{Token::Type::IDENTIFIER, getIdentifier(lastChar), knowCore::Value()}); + } else if(lastChar.isDigit()) + { + auto [number, isinteger] = stream.getDigit(lastChar); + KNOWCORE_RETURN_VALUE_TRY(bn, knowCore::BigNumber::fromString(number.content)); + return kCrvSuccess(Token{Token::Type::VALUE, QString(), bn.toValue()}); + } else { + return kCrvError("Invalid character: {}", lastChar.content); + } + } + } + + +} + +knowCore::ReturnValue<Parser::Result> Parser::parse(const QString& _string) +{ + using Token = Private::Token; + Private d; + d.stream.setString(_string); + Result r; + bool parsingKey = true; + while(parsingKey) + { + KNOWCORE_RETURN_VALUE_TRY(token, d.nextToken()); + switch(token.type) + { + case Token::Type::END_OF_FILE: + return kCrvSuccess(r); + case Token::Type::IDENTIFIER: + { + r.key.append(token.text); + break; + } + case Token::Type::COLON: + { + if(r.key.isEmpty()) + { + return kCrvError("Unexpected ':'"); + } + QString name = r.key.takeLast(); + KNOWCORE_RETURN_VALUE_TRY(token, d.nextToken()); + if(token.type != Token::Type::VALUE) + { + return kCrvError("Expected value after '{}:'", name); + } + r.parameters.add(name, token.value); + parsingKey = false; + break; + } + default: + return kCrvError("Unexpected token, expecting identifier, ':' or end of file."); + } + } + while(true) + { + KNOWCORE_RETURN_VALUE_TRY(token, d.nextToken()); + switch(token.type) + { + case Token::Type::END_OF_FILE: + return kCrvSuccess(r); + case Token::Type::IDENTIFIER: + { + QString name = token.text; + KNOWCORE_RETURN_VALUE_TRY(token_colon, d.nextToken()); + if(token_colon.type != Token::Type::COLON) + { + return kCrvError("Expected ':' after '{}'", name); + } + KNOWCORE_RETURN_VALUE_TRY(token_value, d.nextToken()); + if(token_value.type != Token::Type::VALUE) + { + return kCrvError("Expected value after '{}:'", name); + } + r.parameters.add(name, token_value.value); + break; + } + default: + return kCrvError("Unexpected token, expecting identifier or end of file."); + } + } + +} + diff --git a/kDB/SMQuery/Parser.h b/kDB/SMQuery/Parser.h new file mode 100644 index 0000000000000000000000000000000000000000..a2f04fb4bc33b4c61398a3223d782339d30b3e52 --- /dev/null +++ b/kDB/SMQuery/Parser.h @@ -0,0 +1,18 @@ +#include <knowCore/ValueHash.h> + +namespace kDB::SMQuery +{ + class Parser + { + public: + struct Result + { + QStringList key; + knowCore::ValueHash parameters; + }; + public: + static knowCore::ReturnValue<Result> parse(const QString& _query); + private: + struct Private; + }; +} diff --git a/kDB/SMQuery/tests/CMakeLists.txt b/kDB/SMQuery/tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..233d8da77da1abad5d2673bea129d6c419ebe50d --- /dev/null +++ b/kDB/SMQuery/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(TestEngine TestEngine.cpp ) +target_link_libraries(TestEngine PRIVATE kDBSMQuery Qt5::Test) +add_test(NAME TEST-SMQuery-Engine COMMAND TestEngine) diff --git a/kDB/SMQuery/tests/TestEngine.cpp b/kDB/SMQuery/tests/TestEngine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..568edae861d1508b1260486f4faa20eb7e2bb1a1 --- /dev/null +++ b/kDB/SMQuery/tests/TestEngine.cpp @@ -0,0 +1,70 @@ +#include "TestEngine.h" + +#include "../Interfaces/Action.h" +#include "../Engine.h" + +#include <knowCore/TypeDefinitions.h> +#include <knowCore/Test.h> +#include <knowCore/ValueHash.h> + +class TestFailAction : public kDB::SMQuery::Interfaces::Action +{ +public: + virtual ~TestFailAction() {} + knowCore::ReturnVoid execute(const QString&, const knowCore::ValueHash&) override + { + return kCrvError("Expected failure."); + } + +}; + +class TestAction : public kDB::SMQuery::Interfaces::Action +{ +public: + virtual ~TestAction() {} + knowCore::ReturnVoid execute(const QString& _key, const knowCore::ValueHash& _parameters) override + { + if(_key == "ALSO FAIL") + { + return kCrvError("Expected failure."); + } else if(_key == "DO SUCCEED") + { + if(_parameters.contains("uri") and _parameters.value("uri") != knowCore::Value::fromValue("hello world"_kCu)) + { + return kCrvError("Invalid uri."); + } + if(_parameters.contains("value") and _parameters.value("value") != knowCore::Value::fromValue(12)) + { + return kCrvError("Invalid value."); + } + if(_parameters.contains("text") and _parameters.value("text") != knowCore::Value::fromValue("some text"_kCs)) + { + return kCrvError("Invalid text."); + } + return kCrvSuccess(); + } else { + return kCrvError("Should not happen."); + } + } + +}; +void TestEngine::testQuery() +{ + kDB::SMQuery::Engine e; + e.add({"TRIGGER FAIL"}, new TestFailAction); + e.add({"ALSO FAIL", "DO SUCCEED"}, new TestAction); + + KNOWCORE_TEST_VERIFY_FAILURE_VOID(e.execute("RANDOM ACTION")); + KNOWCORE_TEST_VERIFY_FAILURE_VOID(e.execute("TRIGGER FAIL")); + KNOWCORE_TEST_VERIFY_FAILURE_VOID(e.execute("ALSO FAIL")); + KNOWCORE_TEST_VERIFY_SUCCESS_VOID(e.execute("DO SUCCEED uri: <hello world>")); + KNOWCORE_TEST_VERIFY_SUCCESS_VOID(e.execute("DO SUCCEED uri: <hello world> value: 12")); + KNOWCORE_TEST_VERIFY_SUCCESS_VOID(e.execute("DO SUCCEED uri: <hello world> value: 12 text: \"some text\"")); + + KNOWCORE_TEST_VERIFY_FAILURE_VOID(e.execute("DO SUCCEED uri: <hello>")); + KNOWCORE_TEST_VERIFY_FAILURE_VOID(e.execute("DO SUCCEED value: 121")); + KNOWCORE_TEST_VERIFY_FAILURE_VOID(e.execute("DO SUCCEED text: \"some wrong text\"")); +} + +QTEST_MAIN(TestEngine) + diff --git a/kDB/SMQuery/tests/TestEngine.h b/kDB/SMQuery/tests/TestEngine.h new file mode 100644 index 0000000000000000000000000000000000000000..a9d04ea664cc7b3d811ff04af8131c160871044f --- /dev/null +++ b/kDB/SMQuery/tests/TestEngine.h @@ -0,0 +1,10 @@ + +#include <QtTest/QtTest> + +class TestEngine : public QObject +{ + Q_OBJECT +private slots: + void testQuery(); +}; + diff --git a/kDBConfig.cmake.in b/kDBConfig.cmake.in index 5c75483330f7e350eac2a07baf048456db3d5620..ce09787c2c14a6764537127c1719106d5d44c0ba 100644 --- a/kDBConfig.cmake.in +++ b/kDBConfig.cmake.in @@ -59,6 +59,7 @@ macro(__kdb_add_nice_target_core_library CV KCF name) endmacro() __kdb_add_nice_target_core_library(TRUE __KDB_COMPONENTS_FINDABLE__ Repository) +__kdb_add_nice_target_core_library(TRUE __KDB_COMPONENTS_FINDABLE__ SMQuery) macro(__kdb_add_nice_target_components CV KE KCF name) if(${CV})