diff --git a/ActiveRecord/CMakeLists.txt b/ActiveRecord/CMakeLists.txt new file mode 100644 index 000000000..9c2e67f2a --- /dev/null +++ b/ActiveRecord/CMakeLists.txt @@ -0,0 +1,37 @@ +# Sources +file(GLOB SRCS_G "src/*.cpp") +POCO_SOURCES_AUTO(SRCS ${SRCS_G}) + +# Headers +file(GLOB_RECURSE HDRS_G "include/*.h") +POCO_HEADERS_AUTO(SRCS ${HDRS_G}) + +# Version Resource +if(MSVC AND BUILD_SHARED_LIBS) + source_group("Resources" FILES ${PROJECT_SOURCE_DIR}/DLLVersion.rc) + list(APPEND SRCS ${PROJECT_SOURCE_DIR}/DLLVersion.rc) +endif() + +add_library(ActiveRecord ${SRCS}) +add_library(Poco::ActiveRecord ALIAS ActiveRecord) +set_target_properties(ActiveRecord + PROPERTIES + VERSION ${SHARED_LIBRARY_VERSION} SOVERSION ${SHARED_LIBRARY_VERSION} + OUTPUT_NAME PocoActiveRecord + DEFINE_SYMBOL ActiveRecord_EXPORTS +) + +target_link_libraries(ActiveRecord PUBLIC Poco::Data Poco::Foundation) +target_include_directories(ActiveRecord + PUBLIC + $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +POCO_INSTALL(ActiveRecord) +POCO_GENERATE_PACKAGE(ActiveRecord) + +if(ENABLE_TESTS) + add_subdirectory(testsuite) +endif() diff --git a/ActiveRecord/Compiler/CMakeLists.txt b/ActiveRecord/Compiler/CMakeLists.txt new file mode 100644 index 000000000..6f31df489 --- /dev/null +++ b/ActiveRecord/Compiler/CMakeLists.txt @@ -0,0 +1,19 @@ +# Sources +file(GLOB SRCS_G "src/*.cpp") +POCO_SOURCES_AUTO(SRCS ${SRCS_G}) + +add_executable(ActiveRecordCompiler ${SRCS}) +set_target_properties(ActiveRecordCompiler + PROPERTIES + OUTPUT_NAME arc +) + +target_link_libraries(ActiveRecordCompiler PUBLIC Poco::Foundation Poco::Util) + +install( + TARGETS ActiveRecordCompiler EXPORT "ActiveRecordCompiler" + LIBRARY DESTINATION lib${LIB_SUFFIX} + ARCHIVE DESTINATION lib${LIB_SUFFIX} + RUNTIME DESTINATION bin + INCLUDES DESTINATION include +) diff --git a/ActiveRecord/Compiler/Makefile b/ActiveRecord/Compiler/Makefile new file mode 100644 index 000000000..1c083ea11 --- /dev/null +++ b/ActiveRecord/Compiler/Makefile @@ -0,0 +1,14 @@ +# +# Makefile +# +# Makefile for Poco ActiveRecord Compiler +# + +include $(POCO_BASE)/build/rules/global + +objects = Parser CodeGenerator HeaderGenerator ImplGenerator Compiler + +target = arc +target_libs = PocoUtil PocoJSON PocoXML PocoFoundation + +include $(POCO_BASE)/build/rules/exec diff --git a/ActiveRecord/Compiler/src/CodeGenerator.cpp b/ActiveRecord/Compiler/src/CodeGenerator.cpp new file mode 100644 index 000000000..79f0145d9 --- /dev/null +++ b/ActiveRecord/Compiler/src/CodeGenerator.cpp @@ -0,0 +1,166 @@ +// +// CodeGenerator.cpp +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "CodeGenerator.h" +#include "Poco/StringTokenizer.h" +#include + + +using namespace std::string_literals; + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +CodeGenerator::CodeGenerator(const std::string& source, std::ostream& stream): + _source(source), + _stream(stream) +{ +} + + +void CodeGenerator::writeBeginNameSpace(const std::string& nameSpace) const +{ + if (!nameSpace.empty()) + { + auto ns = splitNameSpace(nameSpace); + for (const auto& s: ns) + { + _stream << "namespace " << s << " {\n"; + } + } +} + + +void CodeGenerator::writeEndNameSpace(const std::string& nameSpace) const +{ + if (!nameSpace.empty()) + { + auto ns = splitNameSpace(nameSpace); + for (const auto& s: ns) + { + _stream << "} "; + } + _stream << "// namespace " << nameSpace << "\n"; + } +} + + +void CodeGenerator::writeHeaderComment(const std::string& fileName) const +{ + _stream + << "//\n" + << "// " << fileName << "\n" + << "//\n" + << "// This file has been generated from " << _source << ". Do not edit.\n" + << "//\n\n\n"; +} + + +void CodeGenerator::writeInclude(const std::string& nameSpace, const std::string& name) const +{ + _stream << "#include \""; + auto ns = splitNameSpace(nameSpace); + for (const auto& s: ns) + { + _stream << s << '/'; + } + _stream << name << ".h\"\n"; +} + + +std::vector CodeGenerator::splitNameSpace(const std::string& nameSpace) +{ + std::vector result; + Poco::StringTokenizer tok(nameSpace, ":"s, Poco::StringTokenizer::TOK_TRIM | Poco::StringTokenizer::TOK_IGNORE_EMPTY); + result.insert(result.end(), tok.begin(), tok.end()); + return result; +} + + +std::string CodeGenerator::propertyType(const Property& prop) const +{ + std::string type; + if (prop.nullable) type += "Poco::Nullable<"; + type += prop.type; + if (prop.nullable) type += ">"; + return type; +} + + +std::string CodeGenerator::paramType(const Property& prop) const +{ + std::string type; + if (!prop.nullable && isSimpleType(prop.type)) + { + type = propertyType(prop); + } + else + { + type += "const "; + type += propertyType(prop); + type += "&"; + } + return type; +} + + +std::string CodeGenerator::keyType(const Class& clazz) const +{ + for (const auto& p: clazz.properties) + { + if (p.name == clazz.key) + { + return propertyType(p); + } + } + return ""s; +} + + +bool CodeGenerator::isSimpleType(const std::string& type) +{ + static const std::set simpleTypes = + { + "bool"s, + "char"s, + "Poco::UInt8"s, + "Poco::Int8"s, + "Poco::UInt16"s, + "Poco::Int16"s, + "Poco::UInt32"s, + "Poco::Int32"s, + "Poco::UInt64"s, + "Poco::Int64"s, + "float"s, + "double"s + }; + + return simpleTypes.find(type) != simpleTypes.end(); +} + + +std::string CodeGenerator::fullClassName(const Class& clazz) const +{ + std::string fullName; + auto ns = splitNameSpace(clazz.nameSpace); + for (const auto& n: ns) + { + fullName += n; + fullName += "::"; + } + fullName += clazz.name; + return fullName; +} + + +} } } // namespace Poco::ActiveRecord::Compiler diff --git a/ActiveRecord/Compiler/src/CodeGenerator.h b/ActiveRecord/Compiler/src/CodeGenerator.h new file mode 100644 index 000000000..043bd22c1 --- /dev/null +++ b/ActiveRecord/Compiler/src/CodeGenerator.h @@ -0,0 +1,71 @@ +// +// CodeGenerator.h +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecordCompiler_CodeGenerator_INCLUDED +#define ActiveRecordCompiler_CodeGenerator_INCLUDED + + +#include "Types.h" +#include +#include + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +class CodeGenerator +{ +public: + CodeGenerator(const std::string& source, std::ostream& stream); + + static std::vector splitNameSpace(const std::string& nameSpace); + +protected: + const std::string& source() const; + std::ostream& stream() const; + void writeBeginNameSpace(const std::string& nameSpace) const; + void writeEndNameSpace(const std::string& nameSpace) const; + void writeHeaderComment(const std::string& fileName) const; + void writeInclude(const std::string& nameSpace, const std::string& name) const; + std::string propertyType(const Property& prop) const; + std::string paramType(const Property& prop) const; + std::string keyType(const Class& clazz) const; + std::string fullClassName(const Class& clazz) const; + static bool isSimpleType(const std::string& type); + +private: + std::string _source; + std::ostream& _stream; +}; + + +// +// inlines +// + + +inline std::ostream& CodeGenerator::stream() const +{ + return _stream; +} + + +inline const std::string& CodeGenerator::source() const +{ + return _source; +} + + +} } } // namespace Poco::ActiveRecord::Compiler + + +#endif // ActiveRecordCompiler_CodeGenerator_INCLUDED diff --git a/ActiveRecord/Compiler/src/Compiler.cpp b/ActiveRecord/Compiler/src/Compiler.cpp new file mode 100644 index 000000000..282b0e8e3 --- /dev/null +++ b/ActiveRecord/Compiler/src/Compiler.cpp @@ -0,0 +1,220 @@ +// +// CompilerApp.cpp +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/Util/Application.h" +#include "Poco/Util/Option.h" +#include "Poco/Util/OptionSet.h" +#include "Poco/Util/HelpFormatter.h" +#include "Poco/FileStream.h" +#include "Poco/Path.h" +#include "Poco/File.h" +#include "Parser.h" +#include "HeaderGenerator.h" +#include "ImplGenerator.h" +#include +#include + + +using namespace std::string_literals; + + +using Poco::Util::Application; +using Poco::Util::Option; +using Poco::Util::OptionSet; +using Poco::Util::HelpFormatter; +using Poco::Util::OptionCallback; + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +class CompilerApp: public Application +{ +public: + CompilerApp() + { + } + +protected: + void initialize(Application& self) + { + loadConfiguration(); // load default configuration files, if present + Application::initialize(self); + } + + void defineOptions(OptionSet& options) + { + Application::defineOptions(options); + + options.addOption( + Option("help", "h", "Display help information on command line arguments."s) + .required(false) + .repeatable(false) + .callback(OptionCallback(this, &CompilerApp::handleHelp))); + } + + void handleHelp(const std::string& name, const std::string& value) + { + _helpRequested = true; + displayHelp(); + stopOptionsProcessing(); + } + + void displayHelp() + { + HelpFormatter helpFormatter(options()); + helpFormatter.setCommand(commandName()); + helpFormatter.setUsage("OPTIONS"); + helpFormatter.setHeader("POCO C++ Libraries ORM Compiler"); + helpFormatter.format(std::cout); + } + + void resolveReferences(const Class& clazz, const ClassMap& classes) + { + for (const auto& r: clazz.references) + { + auto it = classes.find(r); + if (it == classes.end()) + { + throw Poco::NotFoundException(Poco::format("class %s has a reference to unknown class %s"s, clazz.name, r)); + } + else + { + const Class& refClass = it->second; + for (const auto& rr: refClass.references) + { + if (rr == clazz.name) + { + throw Poco::CircularReferenceException(Poco::format("class %s has a circular reference to class %s"s, clazz.name, refClass.name)); + } + } + } + } + } + + void resolveForeignKeys(const Class& clazz, const ClassMap& classes) + { + for (const auto& p: clazz.properties) + { + if (!p.foreignKey.empty()) + { + auto it = classes.find(p.referencedClass); + if (it == classes.end()) + { + throw Poco::NotFoundException(Poco::format("class %s has a reference to unknown class %s"s, clazz.name, p.referencedClass)); + } + else + { + const Class& refClass = it->second; + bool found = false; + for (const auto& rp: refClass.properties) + { + if (rp.column == p.foreignKey) + { + found = true; + break; + } + } + if (!found) throw Poco::NotFoundException(Poco::format("class %s has a reference to unknown foreign key column %s in table %s"s, clazz.name, p.foreignKey, refClass.table)); + } + } + } + } + + void resolve(const ClassMap& classes) + { + for (const auto& p: classes) + { + const Class& clazz = p.second; + resolveReferences(clazz, classes); + resolveForeignKeys(clazz, classes); + } + } + + void compile(const std::string& path) + { + Parser parser; + Poco::FileInputStream xmlStream(path); + ClassMap classes = parser.parse(path, xmlStream); + resolve(classes); + for (const auto& p: classes) + { + Poco::Path headerPath; + headerPath.pushDirectory("include"s); + auto ns = CodeGenerator::splitNameSpace(p.second.nameSpace); + for (const auto& n: ns) + { + headerPath.pushDirectory(n); + } + + Poco::File headerDir(headerPath.toString()); + headerDir.createDirectories(); + + headerPath.setFileName(p.first); + headerPath.setExtension("h"); + + Poco::FileOutputStream headerStream(headerPath.toString()); + HeaderGenerator hg(path, headerStream, p.second, classes); + hg.generate(); + headerStream.close(); + + Poco::Path implPath; + implPath.pushDirectory("src"s); + + Poco::File implDir(implPath.toString()); + implDir.createDirectories(); + + implPath.setFileName(p.first); + implPath.setExtension("cpp"); + + Poco::FileOutputStream implStream(implPath.toString()); + ImplGenerator ig(path, implStream, p.second, classes); + ig.generate(); + implStream.close(); + } + } + + int main(const ArgVec& args) + { + if (!_helpRequested) + { + if (args.empty()) + { + displayHelp(); + } + else + { + try + { + for (const auto& a: args) + { + compile(a); + } + } + catch (Poco::Exception& exc) + { + std::cout << exc.displayText() << std::endl; + } + } + } + return Application::EXIT_OK; + } + +private: + bool _helpRequested = false; +}; + + +} } } // namespace Poco::ActiveRecord::Compiler + + +POCO_APP_MAIN(Poco::ActiveRecord::Compiler::CompilerApp) diff --git a/ActiveRecord/Compiler/src/HeaderGenerator.cpp b/ActiveRecord/Compiler/src/HeaderGenerator.cpp new file mode 100644 index 000000000..915741bff --- /dev/null +++ b/ActiveRecord/Compiler/src/HeaderGenerator.cpp @@ -0,0 +1,351 @@ +// +// HeaderGenerator.cpp +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "HeaderGenerator.h" +#include "Poco/Exception.h" + + +using namespace std::string_literals; + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +HeaderGenerator::HeaderGenerator(const std::string& source, std::ostream& stream, const Class& clazz, const ClassMap& classes): + CodeGenerator(source, stream), + _class(clazz), + _classes(classes) +{ +} + + +void HeaderGenerator::generate() const +{ + writeHeaderComment(_class.name + ".h"); + std::string guard = includeGuard(_class.nameSpace, _class.name); + stream() + << "#ifndef " << guard << "\n" + << "#define " << guard << "\n" + << "\n\n"; + stream() << "#include \"Poco/ActiveRecord/ActiveRecord.h\"\n"; + for (const auto& ref: _class.references) + { + writeInclude(_class.nameSpace, ref); + } + stream() << "\n\n"; + writeBeginNameSpace(_class.nameSpace); + stream() << "\n\n"; + writeClass(); + writeEndNameSpace(_class.nameSpace); + stream() << "\n\n"; + writeTypeHandler(); + stream() + << "\n\n" + << "#endif // " << guard << "\n"; +} + + +std::string HeaderGenerator::includeGuard(const std::string& nameSpace, const std::string& name) const +{ + std::string guard; + auto ns = splitNameSpace(nameSpace); + for (const auto& s: ns) + { + guard += s; + guard += '_'; + } + guard += name; + guard += "_INCLUDED"; + return guard; +} + + +void HeaderGenerator::writeClass() const +{ + stream() << "class " << _class.name << ": public "; + if (_class.key.empty()) + stream() << "Poco::ActiveRecord::KeylessActiveRecord"; + else + stream() << "Poco::ActiveRecord::ActiveRecord<" << keyType(_class) << ">"; + stream() << "\n{\npublic:\n"; + stream() << "\tusing Ptr = Poco::AutoPtr<" << _class.name << ">;\n\n"; + if (!_class.key.empty()) + { + stream() << "\texplicit " << _class.name << "(ID id);\n"; + } + stream() + << "\t" << _class.name << "() = default;\n" + << "\t" << _class.name << "(const " << _class.name << "& other);\n" + << "\t~" << _class.name << "() = default;\n" + << "\n"; + + writeSimpleAccessors(); + writeReferencingAccessors(); + + if (!_class.key.empty()) + { + stream() << "\tstatic Ptr find(Poco::ActiveRecord::Context::Ptr pContext, const ID& id);\n\n"; + } + + stream() + << "\tvoid insert();\n" + << "\tvoid update();\n" + << "\tvoid remove();\n" + << "\n" + << "\tstatic const std::vector& columns();\n" + << "\tstatic const std::string& table();\n"; + + stream() << "\nprivate:\n"; + writeVariables(); + stream() << "\n\tfriend class Poco::Data::TypeHandler<" << _class.name << ">;\n"; + stream() << "};\n"; + stream() << "\n\n"; + writeInlineAccessorImpls(); + writeInlineReferencingAccessorImpls(); +} + + +void HeaderGenerator::writeTypeHandler() const +{ + stream() + << "namespace Poco {\n" + << "namespace Data {\n" + << "\n\n" + << "template <>\n" + << "class TypeHandler<" << fullClassName(_class) << ">\n" + << "{\n" + << "public:\n" + << "\tstatic std::size_t size()\n" + << "\t{\n" + << "\t\treturn " << _class.properties.size() - (_class.key.empty() ? 0 : 1) << ";\n" + << "\t}\n" + << "\n" + << "\tstatic void bind(std::size_t pos, const " << fullClassName(_class) << "& ar, AbstractBinder::Ptr pBinder, AbstractBinder::Direction dir)\n" + << "\t{\n"; + + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + stream() << "\t\tTypeHandler<" << propertyType(p) << ">::bind(pos++, ar._" << p.name << ", pBinder, dir);\n"; + } + } + + stream() + << "}\n" + << "\n" + << "\tstatic void extract(std::size_t pos, " << fullClassName(_class) << "& ar, const " << fullClassName(_class) << "& deflt, AbstractExtractor::Ptr pExtr)\n" + << "\t{\n"; + + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + stream() << "\t\tTypeHandler<" << propertyType(p) << ">::extract(pos++, ar._" << p.name << ", deflt._" << p.name << ", pExtr);\n"; + } + } + + stream() + << "}\n" + << "\n" + << "\tstatic void prepare(std::size_t pos, const " << fullClassName(_class) << "& ar, AbstractPreparator::Ptr pPrep)\n" + << "\t{\n"; + + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + stream() << "\t\tTypeHandler<" << propertyType(p) << ">::prepare(pos++, ar._" << p.name << ", pPrep);\n"; + } + } + + stream() + << "\t}\n" + << "};\n" + << "\n\n" + << "} } // namespace Poco::Data\n"; +} + + +void HeaderGenerator::writeSimpleAccessors() const +{ + for (const auto& p: _class.properties) + { + if (p.referencedClass.empty() && p.name != _class.key) + { + writeGetter(p); + writeSetter(p); + stream() << "\n"; + } + } +} + + +void HeaderGenerator::writeReferencingAccessors() const +{ + for (const auto& p: _class.properties) + { + if (!p.referencedClass.empty() && (p.cardinality == Property::CARD_ZERO_OR_ONE || p.cardinality == Property::CARD_ONE)) + { + writeReferenceGetter(p); + writeReferenceSetter(p); + stream() << "\n"; + } + } +} + + +void HeaderGenerator::writeVariables() const +{ + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + stream() << "\t" << propertyType(p) << " _" << p.name; + if (isSimpleType(p.type) && !p.nullable) + { + if (p.referencedClass.empty()) + { + if (p.type == "bool") + stream() << " = false"; + else + stream() << " = 0"; + } + else + { + const Class& refClass = referencedClass(p); + stream() << " = " << refClass.name << "::INVALID_ID"; + } + } + stream() << ";\n"; + } + } +} + + +void HeaderGenerator::writeGetter(const Property& property) const +{ + stream() << "\t" << paramType(property) << " " << property.name << "() const;\n"; + +} + + +void HeaderGenerator::writeSetter(const Property& property) const +{ + stream() << "\t" << _class.name << "& " << property.name << "(" << paramType(property) << " value);\n"; +} + + +void HeaderGenerator::writeReferenceGetter(const Property& property) const +{ + const Class& refClass = referencedClass(property); + stream() + << "\t" << refClass.name << "::Ptr " << property.name << "() const;\n" + << "\t" << paramType(property) << " " << property.name << "ID() const;\n"; + +} + + +void HeaderGenerator::writeReferenceSetter(const Property& property) const +{ + const Class& refClass = referencedClass(property); + stream() + << "\t" << _class.name << "& " << property.name << "(" << refClass.name << "::Ptr pObject);\n" + << "\t" << _class.name << "& " << property.name << "ID(" << paramType(property) << " id);\n"; +} + + +void HeaderGenerator::writeInlineAccessorImpls() const +{ + for (const auto& p: _class.properties) + { + if (p.referencedClass.empty() && p.name != _class.key) + { + writeInlineGetterImpl(p); + stream() << "\n\n"; + writeInlineSetterImpl(p); + stream() << "\n\n"; + } + } +} + + +void HeaderGenerator::writeInlineReferencingAccessorImpls() const +{ + for (const auto& p: _class.properties) + { + if (!p.referencedClass.empty() && (p.cardinality == Property::CARD_ZERO_OR_ONE || p.cardinality == Property::CARD_ONE)) + { + writeInlineReferencingGetterImpl(p); + stream() << "\n\n"; + writeInlineReferencingSetterImpl(p); + stream() << "\n\n"; + } + } +} + + +void HeaderGenerator::writeInlineGetterImpl(const Property& property) const +{ + stream() + << "inline " << paramType(property) << " " << _class.name << "::" << property.name << "() const\n" + << "{\n" + << "\treturn _" << property.name << ";\n" + << "}\n"; +} + + +void HeaderGenerator::writeInlineSetterImpl(const Property& property) const +{ + stream() + << "inline " << _class.name << "& " << _class.name << "::" << property.name << "(" << paramType(property) << " value)\n" + << "{\n" + << "\t_" << property.name << " = value;\n" + << "\treturn *this;\n" + << "}\n"; +} + + +void HeaderGenerator::writeInlineReferencingGetterImpl(const Property& property) const +{ + stream() + << "inline " << paramType(property) << " " << _class.name << "::" << property.name << "ID() const\n" + << "{\n" + << "\treturn _" << property.name << ";\n" + << "}\n"; +} + + +void HeaderGenerator::writeInlineReferencingSetterImpl(const Property& property) const +{ + stream() + << "inline " << _class.name << "& " << _class.name << "::" << property.name << "ID(" << paramType(property) << " value)\n" + << "{\n" + << "\t_" << property.name << " = value;\n" + << "\treturn *this;\n" + << "}\n"; +} + + +const Class& HeaderGenerator::referencedClass(const Property& property) const +{ + poco_assert (!property.referencedClass.empty()); + + auto it = _classes.find(property.referencedClass); + if (it != _classes.end()) + return it->second; + else + throw Poco::NotFoundException("referenced class"s, property.referencedClass); +} + + +} } } // namespace Poco::ActiveRecord::Compiler diff --git a/ActiveRecord/Compiler/src/HeaderGenerator.h b/ActiveRecord/Compiler/src/HeaderGenerator.h new file mode 100644 index 000000000..e91e012ac --- /dev/null +++ b/ActiveRecord/Compiler/src/HeaderGenerator.h @@ -0,0 +1,59 @@ +// +// HeaderGenerator.h +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecordCompiler_HeaderGenerator_INCLUDED +#define ActiveRecordCompiler_HeaderGenerator_INCLUDED + + +#include "CodeGenerator.h" +#include "Types.h" + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +class HeaderGenerator: public CodeGenerator +{ +public: + HeaderGenerator(const std::string& source, std::ostream& stream, const Class& clazz, const ClassMap& classes); + + void generate() const; + void writeClass() const; + void writeTypeHandler() const; + void writeSimpleAccessors() const; + void writeReferencingAccessors() const; + void writeVariables() const; + void writeGetter(const Property& property) const; + void writeSetter(const Property& property) const; + void writeReferenceGetter(const Property& property) const; + void writeReferenceSetter(const Property& property) const; + void writeInlineAccessorImpls() const; + void writeInlineReferencingAccessorImpls() const; + void writeInlineGetterImpl(const Property& property) const; + void writeInlineSetterImpl(const Property& property) const; + void writeInlineReferencingGetterImpl(const Property& property) const; + void writeInlineReferencingSetterImpl(const Property& property) const; + const Class& referencedClass(const Property& property) const; + +protected: + std::string includeGuard(const std::string& nameSpace, const std::string& name) const; + +private: + Class _class; + const ClassMap& _classes; +}; + + +} } } // namespace Poco::ActiveRecord::Compiler + + +#endif // ActiveRecordCompiler_HeaderGenerator_INCLUDED diff --git a/ActiveRecord/Compiler/src/ImplGenerator.cpp b/ActiveRecord/Compiler/src/ImplGenerator.cpp new file mode 100644 index 000000000..3a550cba9 --- /dev/null +++ b/ActiveRecord/Compiler/src/ImplGenerator.cpp @@ -0,0 +1,418 @@ +// +// ImplGenerator.cpp +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "ImplGenerator.h" +#include "Poco/Exception.h" + + +using namespace std::string_literals; + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +ImplGenerator::ImplGenerator(const std::string& source, std::ostream& stream, const Class& clazz, const ClassMap& classes): + CodeGenerator(source, stream), + _class(clazz), + _classes(classes) +{ +} + + +void ImplGenerator::generate() const +{ + writeHeaderComment(_class.name + ".cpp"); + writeInclude(_class.nameSpace, _class.name); + + if (!_class.key.empty()) + { + if (keyType(_class) == "Poco::UUID") + { + stream() << "#include \"Poco/UUIDGenerator.h\"\n"; + } + } + + stream() << "\n\n"; + stream() << "using namespace std::string_literals;\n"; + stream() << "using namespace Poco::Data::Keywords;\n"; + stream() << "\n\n"; + writeBeginNameSpace(_class.nameSpace); + stream() << "\n\n"; + writeClassMembers(); + writeEndNameSpace(_class.nameSpace); +} + + +void ImplGenerator::writeClassMembers() const +{ + if (!_class.key.empty()) + { + stream() + << _class.name << "::" << _class.name << "(ID id):\n" + << "\tPoco::ActiveRecord::ActiveRecord<" << keyType(_class) << ">(id)\n" + << "{\n" + << "}\n" + << "\n\n"; + } + writeCopyConstructor(); + stream() << "\n\n"; + writeReferencingAccessorsImpl(); + if (!_class.key.empty()) + { + writeFind(); + stream() << "\n\n"; + } + writeInsert(); + stream() << "\n\n"; + writeUpdate(); + stream() << "\n\n"; + writeRemove(); + stream() << "\n\n"; + writeColumns(); + stream() << "\n\n"; + writeTable(); + stream() << "\n\n"; +} + + +void ImplGenerator::writeCopyConstructor() const +{ + stream() << _class.name << "::" << _class.name << "(const " << _class.name << "& other):\n"; + bool needComma = false; + if (!_class.key.empty()) + { + stream() << "\tPoco::ActiveRecord::ActiveRecord<" << keyType(_class) << ">(other)"; + needComma = true; + } + + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + if (needComma) stream() << ",\n"; + stream() << "\t_" << p.name << "(other._" << p.name << ")"; + needComma = true; + } + } + + stream() + << "\n" + << "{\n" + << "}\n"; +} + + +void ImplGenerator::writeReferencingAccessorsImpl() const +{ + for (const auto& p: _class.properties) + { + if (!p.referencedClass.empty() && (p.cardinality == Property::CARD_ZERO_OR_ONE || p.cardinality == Property::CARD_ONE)) + { + writeReferencingGetterImpl(p); + stream() << "\n\n"; + writeReferencingSetterImpl(p); + stream() << "\n\n"; + } + } +} + + +void ImplGenerator::writeReferencingGetterImpl(const Property& property) const +{ + const Class& refClass = referencedClass(property); + stream() + << refClass.name << "::Ptr " << _class.name << "::" << property.name << "() const\n" + << "{\n"; + + if (property.nullable) + { + stream() + << "\tif (!_" << property.name << ".isNull())\n" + << "\t\treturn " << refClass.name << "::find(context(), _" << property.name << ".value());\n" + << "\telse\n" + << "\t\treturn nullptr;\n"; + } + else + { + stream() << "\treturn " << refClass.name << "::find(context(), _" << property.name << ");\n"; + } + stream() << "}\n"; +} + + +void ImplGenerator::writeReferencingSetterImpl(const Property& property) const +{ + const Class& refClass = referencedClass(property); + stream() + << _class.name << "& " << _class.name << "::" << property.name << "(" << refClass.name << "::Ptr pObject)\n" + << "{\n" + << "\tif (pObject)\n" + << "\t\t_" << property.name << " = pObject->id();\n" + << "\telse\n" + << "\t\t_" << property.name << " = " << refClass.name << "::INVALID_ID;\n" + << "\treturn *this;\n" + << "}\n"; +} + + +void ImplGenerator::writeFind() const +{ + stream() + << _class.name << "::Ptr " << _class.name << "::find(Poco::ActiveRecord::Context::Ptr pContext, const ID& id)\n" + << "{\n" + << "\tPoco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(pContext->statementPlaceholderProvider());\n" + << "\t" << _class.name << "::Ptr pObject(new " << _class.name << ");\n" + << "\n" + << "\tpContext->session()\n" + << "\t\t<< \"SELECT " + << keyProperty(_class).column; + + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + stream() << ", " << p.column; + } + } + + stream() + << "\"\n" + << "\t\t<< \" FROM " << _class.table << "\"\n" + << "\t\t<< \" WHERE " << keyProperty(_class).column << " = \" << pSPP->next(),\n" + << "\t\tinto(pObject->mutableID()),\n" + << "\t\tinto(*pObject),\n" + << "\t\tbind(id),\n" + << "\t\tnow;\n" + << "\n" + << "\treturn withContext(pObject, pContext);\n" + << "}\n"; +} + + +void ImplGenerator::writeInsert() const +{ + stream() + << "void " << _class.name << "::insert()\n" + << "{\n" + << "\tPoco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider());\n" + << "\n"; + + if (!_class.key.empty()) + { + if (keyType(_class) == "Poco::UUID") + { + stream() + << "\tif (id().isNull())\n" + << "\t{\n" + << "\t\tmutableID() = Poco::UUIDGenerator().createRandom();\n" + << "\t}\n" + << "\n"; + } + } + + stream() + << "\tcontext()->session()\n" + << "\t\t<< \"INSERT INTO " << _class.table << " ("; + + bool needComma = false; + if (!_class.key.empty()) + { + stream() << keyProperty(_class).column; + needComma = true; + } + + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + if (needComma) stream() << ", "; + stream() << p.column; + needComma = true; + } + } + + stream() + << ")\"\n" + << "\t\t<< \" VALUES ("; + + needComma = false; + if (!_class.key.empty()) + { + if (_class.autoIncrementID) + stream() << "NULL"; + else + stream() << "\" << pSPP->next() << \""; + needComma = true; + } + + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + if (needComma) stream() << ", "; + stream() << "\" << pSPP->next() << \""; + needComma = true; + } + } + + stream() << ")\",\n"; + + if (!_class.key.empty() && !_class.autoIncrementID) + { + stream() << "\t\tbind(id()),\n"; + } + stream() + << "\t\tuse(*this),\n" + << "\t\tnow;\n"; + + if (_class.autoIncrementID) + { + stream() << "\tupdateID(context()->session());\n"; + } + + stream() << "}\n"; +} + + +void ImplGenerator::writeUpdate() const +{ + stream() + << "void " << _class.name << "::update()\n" + << "{\n"; + + if (_class.key.empty()) + { + stream() << "\tthrow Poco::NotImplementedException(\"update not implemented for keyless class\", \"" << _class.name << "\");\n"; + } + else + { + stream() + << "\tPoco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider());\n" + << "\n" + << "\tcontext()->session()\n" + << "\t\t<< \"UPDATE " << _class.table << "\"\n" + << "\t\t<< \" SET "; + + bool needComma = false; + for (const auto& p: _class.properties) + { + if (p.name != _class.key) + { + if (needComma) stream() << " << \", "; + stream() << p.column << " = \" << pSPP->next()"; + needComma = true; + } + } + + stream() + << "\n" + << "\t\t<< \" WHERE " << keyProperty(_class).column << " = \" << pSPP->next(),\n" + << "\t\tuse(*this),\n" + << "\t\tbind(id()),\n" + << "\t\tnow;\n"; + } + stream() << "}\n"; +} + + +void ImplGenerator::writeRemove() const +{ + stream() + << "void " << _class.name << "::remove()\n" + << "{\n" + << "\tPoco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider());\n" + << "\n" + << "\tcontext()->session()\n" + << "\t\t<< \"DELETE FROM " << _class.table << "\"\n" + << "\t\t<< \" WHERE "; + + if (_class.key.empty()) + { + bool needAnd = false; + for (const auto& p: _class.properties) + { + if (needAnd) stream() << " << \" AND "; + stream() << p.column << " = \" << pSPP->next()"; + needAnd = true; + } + stream() + << ",\n" + << "\t\tuse(*this),\n" + << "\t\tnow;\n"; + } + else + { + stream() + << keyProperty(_class).column << " = \" << pSPP->next(),\n" + << "\t\tbind(id()),\n" + << "\t\tnow;\n"; + } + stream() << "}\n"; +} + + +void ImplGenerator::writeColumns() const +{ + stream() + << "const std::vector& " << _class.name << "::columns()\n" + << "{\n" + << "\tstatic const std::vector cols =\n" + << "\t{\n"; + + for (const auto& p: _class.properties) + { + stream() << "\t\t\"" << p.column << "\"s,\n"; + } + + stream() + << "\t};\n" + << "\n" + << "\treturn cols;\n" + << "}\n"; +} + + +void ImplGenerator::writeTable() const +{ + stream() + << "const std::string& " << _class.name << "::table()\n" + << "{\n" + << "\tstatic const std::string t = \"" << _class.table << "\";\n" + << "\treturn t;\n" + << "}\n"; +} + + +const Property& ImplGenerator::keyProperty(const Class& clazz) const +{ + for (const auto& p: clazz.properties) + { + if (p.name == clazz.key) + return p; + } + throw Poco::NotFoundException("key property"s, clazz.key); +} + + +const Class& ImplGenerator::referencedClass(const Property& property) const +{ + poco_assert (!property.referencedClass.empty()); + + auto it = _classes.find(property.referencedClass); + if (it != _classes.end()) + return it->second; + else + throw Poco::NotFoundException("referenced class"s, property.referencedClass); +} + + +} } } // namespace Poco::ActiveRecord::Compiler diff --git a/ActiveRecord/Compiler/src/ImplGenerator.h b/ActiveRecord/Compiler/src/ImplGenerator.h new file mode 100644 index 000000000..63aea23ce --- /dev/null +++ b/ActiveRecord/Compiler/src/ImplGenerator.h @@ -0,0 +1,56 @@ +// +// ImplGenerator.h +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecordCompiler_ImplGenerator_INCLUDED +#define ActiveRecordCompiler_ImplGenerator_INCLUDED + + +#include "CodeGenerator.h" +#include "Types.h" + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +class ImplGenerator: public CodeGenerator +{ +public: + ImplGenerator(const std::string& source, std::ostream& stream, const Class& clazz, const ClassMap& classes); + + void generate() const; + void writeClassMembers() const; + void writeCopyConstructor() const; + void writeReferencingAccessorsImpl() const; + void writeReferencingGetterImpl(const Property& property) const; + void writeReferencingSetterImpl(const Property& property) const; + void writeFind() const; + void writeInsert() const; + void writeUpdate() const; + void writeRemove() const; + void writeColumns() const; + void writeTable() const; + const Class& referencedClass(const Property& property) const; + +protected: + const Property& keyProperty(const Class& clazz) const; + std::string includeGuard(const std::string& nameSpace, const std::string& name) const; + +private: + Class _class; + const ClassMap& _classes; +}; + + +} } } // namespace Poco::ActiveRecord::Compiler + + +#endif // ActiveRecordCompiler_ImplGenerator_INCLUDED diff --git a/ActiveRecord/Compiler/src/Parser.cpp b/ActiveRecord/Compiler/src/Parser.cpp new file mode 100644 index 000000000..94401f995 --- /dev/null +++ b/ActiveRecord/Compiler/src/Parser.cpp @@ -0,0 +1,277 @@ +// +// Parser.cpp +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Parser.h" +#include "Poco/SAX/Attributes.h" +#include "Poco/SAX/Locator.h" +#include "Poco/SAX/InputSource.h" +#include "Poco/SAX/SAXParser.h" +#include "Poco/Format.h" +#include "Poco/Ascii.h" +#include "Poco/Exception.h" + + +using namespace std::string_literals; + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +Parser::Parser() +{ +} + + +ClassMap Parser::parse(const std::string& systemId, std::istream& stream) +{ + Poco::XML::SAXParser parser; + parser.setFeature(Poco::XML::XMLReader::FEATURE_NAMESPACES, false); + parser.setFeature(Poco::XML::XMLReader::FEATURE_NAMESPACE_PREFIXES, false); + parser.setContentHandler(this); + + Poco::XML::InputSource inputSource(stream); + inputSource.setSystemId(systemId); + + parser.parse(&inputSource); + + poco_assert (_elemStack.empty()); + + return _classes; +} + + +void Parser::setDocumentLocator(const Poco::XML::Locator* pLocator) +{ + _pLocator = pLocator; +} + + +void Parser::startElement(const Poco::XML::XMLString& uri, const Poco::XML::XMLString& localName, const Poco::XML::XMLString& qname, const Poco::XML::Attributes& attributes) +{ + if (qname == "project") + { + if (_elemStack.empty()) + { + _elemStack.push_back(qname); + handleProject(attributes); + } + else throw Poco::SyntaxException(Poco::format("%s: project element must be at document root"s, where())); + } + else if (qname == "class") + { + if (_elemStack.size() == 1 && _elemStack.back() == "project") + { + _elemStack.push_back(qname); + handleClass(attributes); + } + else throw Poco::SyntaxException(Poco::format("%s: class element must be within project element"s, where())); + } + else if (qname == "property") + { + if (_elemStack.size() == 2 && _elemStack.back() == "class") + { + _elemStack.push_back(qname); + handleProperty(attributes); + } + else throw Poco::SyntaxException(Poco::format("%s: property element must be within class element"s, where())); + } + else throw Poco::SyntaxException(Poco::format("%s: invalid element: %s"s, where(), qname)); +} + + +void Parser::endElement(const Poco::XML::XMLString& uri, const Poco::XML::XMLString& localName, const Poco::XML::XMLString& qname) +{ + poco_assert (_elemStack.size() > 0); + + if (qname == "class") + { + _classes[_class.name] = _class; + } + _elemStack.pop_back(); +} + + +void Parser::handleProject(const Poco::XML::Attributes& attributes) +{ + _nameSpace = attributes.getValue("namespace"s); + _convertCamelCase = parseBool("convertCamelCase"s, attributes.getValue("convertCamelCase"s)); +} + + +void Parser::handleClass(const Poco::XML::Attributes& attributes) +{ + _class.name = attributes.getValue("name"s); + _class.nameSpace = _nameSpace; + _class.table = attributes.getValue("table"s); + if (_class.table.empty()) _class.table = toDatabaseName(_class.name); + _class.key = attributes.getValue("key"s); + _class.autoIncrementID = parseBool("autoIncrementID"s, attributes.getValue("autoIncrementID"s), false); + _class.properties.clear(); + _class.references.clear(); +} + + +void Parser::handleProperty(const Poco::XML::Attributes& attributes) +{ + Property prop; + prop.name = attributes.getValue("name"s); + prop.column = attributes.getValue("column"s); + if (prop.column.empty()) prop.column = toDatabaseName(prop.name); + prop.type = parseType(attributes.getValue("type"s)); + prop.referencedClass = attributes.getValue("references"s); + prop.foreignKey = attributes.getValue("foreignKey"s); + prop.cardinality = parseCardinality(attributes.getValue("cardinality")); + prop.nullable = parseBool("nullable"s, attributes.getValue("nullable"s), false); + _class.properties.push_back(prop); + + if (!prop.referencedClass.empty()) + { + if (prop.cardinality == Property::CARD_ZERO_OR_ONE) + { + prop.nullable = true; + } + } + + if (prop.referencedClass.empty() && !prop.foreignKey.empty()) + { + throw Poco::InvalidArgumentException(Poco::format("%s: foreign key specified, but no referenced class"s, where())); + } + + if (!prop.referencedClass.empty() && prop.referencedClass != _class.name && std::find(_class.references.begin(), _class.references.end(), prop.referencedClass) == _class.references.end()) + { + _class.references.push_back(prop.referencedClass); + } + + if (_class.key.empty() && prop.name == "id") + { + _class.key = prop.name; + } +} + + +std::string Parser::where() const +{ + if (_pLocator) + { + if (_pLocator->getSystemId().empty()) + return Poco::format("Line %d, column %d"s, + _pLocator->getLineNumber(), + _pLocator->getColumnNumber()); + else + return Poco::format("File '%s', line %d, column %d"s, + _pLocator->getSystemId(), + _pLocator->getLineNumber(), + _pLocator->getColumnNumber()); + } + else return ""; +} + + +std::string Parser::parseType(const std::string& type) const +{ + static const std::map typeMap = + { + {"bool"s, "bool"s}, + {"char"s, "char"s}, + {"int8"s, "Poco::Int8"s}, + {"uint8"s, "Poco::UInt8"s}, + {"int16"s, "Poco::Int16"s}, + {"uint16"s, "Poco::UInt16"s}, + {"int32"s, "Poco::Int32"s}, + {"uint32"s, "Poco::UInt32"s}, + {"int64"s, "Poco::Int64"s}, + {"uint64"s, "Poco::UInt64"s}, + {"float"s, "float"s}, + {"double"s, "double"s}, + {"dateTime"s, "Poco::DateTime"s}, + {"timestamp"s, "Poco::Timestamp"s}, + {"time"s, "Poco::Data::Time"s}, + {"date"s, "Poco::Data::Date"s}, + {"uuid"s, "Poco::UUID"s}, + {"string"s, "std::string"s} + }; + + auto it = typeMap.find(type); + if (it == typeMap.end()) + throw Poco::InvalidArgumentException(Poco::format("%s: invalid type: %s", where(), type)); + else + return it->second; +} + + +char Parser::parseCardinality(const std::string& cardinality) const +{ + if (cardinality.empty()) + { + return Property::CARD_ONE; + } + else if (cardinality.size() == 1) + { + switch (cardinality[0]) + { + case Property::CARD_ZERO_OR_ONE: + case Property::CARD_ONE: + case Property::CARD_ZERO_OR_MORE: + case Property::CARD_ONE_OR_MORE: + return cardinality[0]; + default: + break; + } + } + throw Poco::InvalidArgumentException(Poco::format("%s: cardinality must be one of ?, 1, *, +", where())); +} + + +bool Parser::parseBool(const std::string& name, const std::string& value, bool deflt) const +{ + if (value == "true") + return true; + else if (value == "false") + return false; + else if (value.empty()) + return deflt; + else throw Poco::InvalidArgumentException(Poco::format("%s: %s value must be 'true' or 'false'", name, where())); +} + + +std::string Parser::convertCamelCase(const std::string& name) +{ + std::string result; + for (const char c: name) + { + if (Poco::Ascii::isUpper(c)) + { + if (!(result.empty() || result.back() == '_')) + { + result += '_'; + } + result += Poco::Ascii::toLower(c); + } + else + { + result += c; + } + } + return result; +} + + +std::string Parser::toDatabaseName(const std::string& name) +{ + if (_convertCamelCase) + return convertCamelCase(name); + else + return name; +} + + +} } } // namespace Poco::ActiveRecord::Compiler diff --git a/ActiveRecord/Compiler/src/Parser.h b/ActiveRecord/Compiler/src/Parser.h new file mode 100644 index 000000000..9b639a773 --- /dev/null +++ b/ActiveRecord/Compiler/src/Parser.h @@ -0,0 +1,63 @@ +// +// Parser.h +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecordCompiler_Parser_INCLUDED +#define ActiveRecordCompiler_Parser_INCLUDED + + +#include "Types.h" +#include "Poco/SAX/DefaultHandler.h" +#include + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +class Parser: protected Poco::XML::DefaultHandler + /// A parser for the XML ORM (project/class/property) class specification file. +{ +public: + Parser(); + /// Creates the Parser. + + ClassMap parse(const std::string& systemId, std::istream& stream); + /// Parses the XML file. + +protected: + // ContentHandler + void setDocumentLocator(const Poco::XML::Locator* pLocator); + void startElement(const Poco::XML::XMLString& uri, const Poco::XML::XMLString& localName, const Poco::XML::XMLString& qname, const Poco::XML::Attributes& attributes); + void endElement(const Poco::XML::XMLString& uri, const Poco::XML::XMLString& localName, const Poco::XML::XMLString& qname); + void handleProject(const Poco::XML::Attributes& attributes); + void handleClass(const Poco::XML::Attributes& attributes); + void handleProperty(const Poco::XML::Attributes& attributes); + std::string where() const; + std::string parseType(const std::string& type) const; + char parseCardinality(const std::string& cardinality) const; + bool parseBool(const std::string& name, const std::string& value, bool deflt = false) const; + std::string convertCamelCase(const std::string& name); + std::string toDatabaseName(const std::string& name); + +private: + const Poco::XML::Locator* _pLocator = nullptr; + bool _convertCamelCase = false; + std::string _nameSpace; + Class _class; + ClassMap _classes; + std::vector _elemStack; +}; + + +} } } // namespace Poco::ActiveRecord::Compiler + + +#endif // ActiveRecordCompiler_Parser_INCLUDED diff --git a/ActiveRecord/Compiler/src/Types.h b/ActiveRecord/Compiler/src/Types.h new file mode 100644 index 000000000..6afb71524 --- /dev/null +++ b/ActiveRecord/Compiler/src/Types.h @@ -0,0 +1,60 @@ +// +// Types.h +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecordCompiler_Types_INCLUDED +#define ActiveRecordCompiler_Types_INCLUDED + + +#include "Poco/Types.h" +#include +#include + + +namespace Poco { +namespace ActiveRecord { +namespace Compiler { + + +struct Property +{ + static const char CARD_ZERO_OR_ONE = '?'; + static const char CARD_ONE = '1'; + static const char CARD_ZERO_OR_MORE = '*'; + static const char CARD_ONE_OR_MORE = '+'; + + std::string name; + std::string column; + std::string type; + std::string referencedClass; + std::string foreignKey; + char cardinality = CARD_ONE; + bool nullable = false; +}; + + +struct Class +{ + std::string name; + std::string nameSpace; + std::string table; + std::string key; + bool autoIncrementID = false; + std::vector properties; + std::vector references; +}; + + +using ClassMap = std::map; + + +} } } // namespace Poco::ActiveRecord::Compiler + + +#endif // ActiveRecordCompiler_Types_INCLUDED diff --git a/ActiveRecord/Makefile b/ActiveRecord/Makefile new file mode 100644 index 000000000..3ba7de777 --- /dev/null +++ b/ActiveRecord/Makefile @@ -0,0 +1,15 @@ +# +# Makefile +# +# Makefile for Poco ActiveRecord Library +# + +include $(POCO_BASE)/build/rules/global + +objects = Context ActiveRecord IDTraits StatementPlaceholderProvider + +target = PocoActiveRecord +target_version = 1 +target_libs = PocoData PocoFoundation + +include $(POCO_BASE)/build/rules/lib diff --git a/ActiveRecord/cmake/PocoActiveRecordConfig.cmake b/ActiveRecord/cmake/PocoActiveRecordConfig.cmake new file mode 100644 index 000000000..a2e66669c --- /dev/null +++ b/ActiveRecord/cmake/PocoActiveRecordConfig.cmake @@ -0,0 +1,4 @@ +include(CMakeFindDependencyMacro) +find_dependency(PocoFoundation) +find_dependency(PocoData) +include("${CMAKE_CURRENT_LIST_DIR}/PocoActiveRecordTargets.cmake") diff --git a/ActiveRecord/include/Poco/ActiveRecord/ActiveRecord.h b/ActiveRecord/include/Poco/ActiveRecord/ActiveRecord.h new file mode 100644 index 000000000..29047717e --- /dev/null +++ b/ActiveRecord/include/Poco/ActiveRecord/ActiveRecord.h @@ -0,0 +1,278 @@ +// +// ActiveRecord.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: ActiveRecord +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecord_ActiveRecord_INCLUDED +#define ActiveRecord_ActiveRecord_INCLUDED + + +#include "Poco/ActiveRecord/ActiveRecordLib.h" +#include "Poco/ActiveRecord/Context.h" +#include "Poco/ActiveRecord/IDTraits.h" +#include "Poco/ActiveRecord/TypeHandler.h" +#include "Poco/DateTime.h" +#include "Poco/RefCountedObject.h" +#include "Poco/AutoPtr.h" +#include "Poco/Types.h" +#include + + +namespace Poco { +namespace ActiveRecord { + + +class ActiveRecordLib_API ActiveRecordBase: public Poco::RefCountedObject + /// The base class for the ActiveRecord class template. +{ +public: + using Ptr = Poco::AutoPtr; + + virtual std::string toString() const = 0; + /// Returns a string representation of the object for + /// debugging purposes. The default implementation returns the ID. + + void create(Context::Ptr pContext); + /// Attaches the given Context and calls insert(). + + virtual void insert() = 0; + /// Inserts a new row in the database with the content of this object. + /// The ID must be 0, and after successful insert, a unique ID + /// is assigned. + + virtual void update() = 0; + /// Updates the row in the database with the content of this object. + + virtual void remove() = 0; + /// Deletes the corresponding row in the database. + + void attach(Context::Ptr pContext); + /// Attaches the object to a Context. + + void detach(); + /// Detaches the object from its Context. + + Context::Ptr context() const; + /// Returns the Context this object is attached to, + /// or a null pointer if the object has not been + /// attached to a Context. + + virtual bool isValid() const; + /// Returns true iff the object is valid ID, otherwise false. + + bool isAttached() const; + /// Returns true iff the object has been attached to a Context, otherwise false. + +protected: + ActiveRecordBase() = default; + ~ActiveRecordBase() = default; + + template + static Poco::AutoPtr withContext(Poco::AutoPtr pObj, Context::Ptr pContext) + { + if (pObj && pObj->isValid()) + { + pObj->attach(pContext); + return pObj; + } + else return nullptr; + } + +private: + ActiveRecordBase(const ActiveRecordBase&) = delete; + ActiveRecordBase& operator = (const ActiveRecordBase&) = delete; + + Context::Ptr _pContext; +}; + + +template +class ActiveRecord: public ActiveRecordBase + /// The base class for all database objects that + /// implement the ActiveRecord pattern, with a + /// single key column. +{ +public: + using Ptr = Poco::AutoPtr; + using ID = IDType; + + static const IDType INVALID_ID; + + ID id() const; + /// Returns the unique ID of the object. + + // ActiveRecordBase + std::string toString() const; + bool isValid() const; + +protected: + ActiveRecord() = default; + ActiveRecord(ID id): _id(id) {}; + ~ActiveRecord() = default; + + ActiveRecord(const ActiveRecord& other): + _id(other._id) + { + } + + ID& mutableID(); + + void updateID(Poco::Data::Session& session); + /// Updates the ID using lastInsertID(). + + static ID lastInsertID(Poco::Data::Session& session); + /// Returns the last inserted ID from the database session. + /// Used for automatically incrementing keys. + + template + static Poco::AutoPtr withContext(Poco::AutoPtr pObj, Context::Ptr pContext) + { + if (pObj->isValid()) + { + pObj->attach(pContext); + return pObj; + } + else return nullptr; + } + + template + static void queryInto(Poco::Data::Statement& statement, AR& ar) + { + statement, Poco::Data::Keywords::into(ar._id), Poco::Data::Keywords::into(ar); + } + +private: + ActiveRecord& operator = (const ActiveRecord&) = delete; + + ID _id = INVALID_ID; + + template friend class Query; +}; + + +template +const IDType ActiveRecord::INVALID_ID = IDTraits::INVALID_ID; + + +class KeylessActiveRecord: public ActiveRecordBase + /// The base class for all database objects that + /// implement the ActiveRecord pattern, without + /// a key column. +{ +public: + using Ptr = Poco::AutoPtr; + + // ActiveRecordBase + std::string toString() const; + +protected: + template + static void queryInto(Poco::Data::Statement& statement, AR& ar) + { + statement, Poco::Data::Keywords::into(ar); + } + + template friend class Query; +}; + + +// +// inlines +// + + +inline Context::Ptr ActiveRecordBase::context() const +{ + return _pContext; +} + + +inline bool ActiveRecordBase::isAttached() const +{ + return !_pContext.isNull(); +} + + +template +inline IDType ActiveRecord::id() const +{ + return _id; +} + + +template +inline IDType& ActiveRecord::mutableID() +{ + return _id; +} + + +template +inline bool ActiveRecord::isValid() const +{ + return IDTraits::isValid(_id); +} + + +template +inline std::string ActiveRecord::toString() const +{ + return IDTraits::toString(_id); +} + + +template +void ActiveRecord::updateID(Poco::Data::Session& session) +{ + _id = lastInsertID(session); +} + + +template +IDType ActiveRecord::lastInsertID(Poco::Data::Session& session) +{ + using namespace Poco::Data::Keywords; + + IDType id; + if (session.connector() == "sqlite") + { + session + << "SELECT last_insert_rowid()", + into(id), + now; + } + else if (session.connector() == "PostgreSQL") + { + session + << "SELECT currval('id_seq')", + into(id), + now; + } + else if (session.connector() == "MySQL") + { + session + << "SELECT LAST_INSERT_ID()", + into(id), + now; + } + else + { + throw Poco::NotImplementedException("lastInsertID not implemented for connector", session.connector()); + } + return id; +} + + +} } // namespace Poco::ActiveRecord + + +#endif // ActiveRecord_ActiveRecord_INCLUDED diff --git a/ActiveRecord/include/Poco/ActiveRecord/ActiveRecordLib.h b/ActiveRecord/include/Poco/ActiveRecord/ActiveRecordLib.h new file mode 100644 index 000000000..c737d9241 --- /dev/null +++ b/ActiveRecord/include/Poco/ActiveRecord/ActiveRecordLib.h @@ -0,0 +1,58 @@ +// +// ActiveRecordLib.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: ActiveRecordLib +// +// Basic definitions for the ActiveRecord library. +// This file must be the first file included by every other ActiveRecordLib +// header file. +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecord_ActiveRecordLib_INCLUDED +#define ActiveRecord_ActiveRecordLib_INCLUDED + + +#include "Poco/Poco.h" + + +// +// The following block is the standard way of creating macros which make exporting +// from a DLL simpler. All files within this DLL are compiled with the ActiveRecordLib_EXPORTS +// symbol defined on the command line. this symbol should not be defined on any project +// that uses this DLL. This way any other project whose source files include this file see +// ActiveRecordLib_API functions as being imported from a DLL, wheras this DLL sees symbols +// defined with this macro as being exported. +// +#if defined(_WIN32) && defined(POCO_DLL) + #if defined(ActiveRecordLib_EXPORTS) + #define ActiveRecordLib_API __declspec(dllexport) + #else + #define ActiveRecordLib_API __declspec(dllimport) + #endif +#endif + + +#if !defined(ActiveRecordLib_API) + #define ActiveRecordLib_API +#endif + + +// +// Automatically link ActiveRecordLib library. +// +#if defined(_MSC_VER) + #if !defined(POCO_NO_AUTOMATIC_LIBS) && !defined(ActiveRecordLib_EXPORTS) + #pragma comment(lib, "PocoActiveRecord" POCO_LIB_SUFFIX) + #endif +#endif + + +#endif // ActiveRecord_ActiveRecordLib_INCLUDED diff --git a/ActiveRecord/include/Poco/ActiveRecord/Context.h b/ActiveRecord/include/Poco/ActiveRecord/Context.h new file mode 100644 index 000000000..bb5cda0a3 --- /dev/null +++ b/ActiveRecord/include/Poco/ActiveRecord/Context.h @@ -0,0 +1,74 @@ +// +// Context.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: Context +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecord_Context_INCLUDED +#define ActiveRecord_Context_INCLUDED + + +#include "Poco/ActiveRecord/ActiveRecordLib.h" +#include "Poco/ActiveRecord/StatementPlaceholderProvider.h" +#include "Poco/Data/Session.h" +#include "Poco/RefCountedObject.h" +#include "Poco/AutoPtr.h" + + +namespace Poco { +namespace ActiveRecord { + + +class ActiveRecordLib_API Context: public Poco::RefCountedObject + /// Context information for ActiveRecord objects. +{ +public: + using Ptr = Poco::AutoPtr; + + explicit Context(const Poco::Data::Session& session); + /// Creates the Context from an existing Poco::Data::Session. + + Context(const std::string& connector, const std::string& connectionString); + /// Creates the Context from a connector name and connection string. + + ~Context() = default; + /// Destroys the Context. + + Poco::Data::Session& session(); + /// Returns the database session. + + StatementPlaceholderProvider::Ptr statementPlaceholderProvider() const; + /// Returns a new StatementPlaceholderProvider. + +private: + Context() = delete; + Context(const Context&) = delete; + Context& operator = (const Context&) = delete; + + Poco::Data::Session _session; +}; + + +// +// inlines +// + + +inline Poco::Data::Session& Context::session() +{ + return _session; +} + + +} } // namespace Poco::ActiveRecord + + +#endif // ActiveRecord_Context_INCLUDED diff --git a/ActiveRecord/include/Poco/ActiveRecord/IDTraits.h b/ActiveRecord/include/Poco/ActiveRecord/IDTraits.h new file mode 100644 index 000000000..5a681bbd9 --- /dev/null +++ b/ActiveRecord/include/Poco/ActiveRecord/IDTraits.h @@ -0,0 +1,183 @@ +// +// IDTraits.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: IDTraits +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecord_IDTraits_INCLUDED +#define ActiveRecord_IDTraits_INCLUDED + + +#include "Poco/Types.h" +#include "Poco/UUID.h" +#include "Poco/NumberFormatter.h" + + +namespace Poco { +namespace ActiveRecord { + + +template +class IDTraits + /// Traits for ID/index types. + /// See specializations for details. +{ +}; + + +template <> +class IDTraits +{ +public: + static const Poco::UInt64 INVALID_ID; + + static bool isValid(Poco::UInt64 id) + { + return id != INVALID_ID; + } + + static std::string toString(Poco::UInt64 id) + { + return Poco::NumberFormatter::format(id); + } +}; + + +template <> +class IDTraits +{ +public: + static const Poco::Int64 INVALID_ID; + + static bool isValid(Poco::Int64 id) + { + return id != INVALID_ID; + } + + static std::string toString(Poco::Int64 id) + { + return Poco::NumberFormatter::format(id); + } +}; + + +template <> +class IDTraits +{ +public: + static const Poco::UInt32 INVALID_ID; + + static bool isValid(Poco::UInt32 id) + { + return id != INVALID_ID; + } + + static std::string toString(Poco::UInt32 id) + { + return Poco::NumberFormatter::format(id); + } +}; + + +template <> +class IDTraits +{ +public: + static const Poco::Int32 INVALID_ID; + + static bool isValid(Poco::Int32 id) + { + return id != INVALID_ID; + } + + static std::string toString(Poco::Int32 id) + { + return Poco::NumberFormatter::format(id); + } +}; + + +template <> +class IDTraits +{ +public: + static const Poco::UInt16 INVALID_ID; + + static bool isValid(Poco::UInt16 id) + { + return id != INVALID_ID; + } + + static std::string toString(Poco::UInt16 id) + { + return Poco::NumberFormatter::format(id); + } +}; + + +template <> +class IDTraits +{ +public: + static const Poco::Int16 INVALID_ID; + + static bool isValid(Poco::Int16 id) + { + return id != INVALID_ID; + } + + static std::string toString(Poco::Int16 id) + { + return Poco::NumberFormatter::format(id); + } +}; + + +template <> +class IDTraits +{ +public: + static const std::string INVALID_ID; + + static bool isValid(const std::string& id) + { + return !id.empty(); + } + + static std::string toString(const std::string& id) + { + return id; + } +}; + + +template <> +class IDTraits +{ +public: + static const Poco::UUID INVALID_ID; + + static bool isValid(const Poco::UUID& id) + { + return !id.isNull(); + } + + static std::string toString(const Poco::UUID& id) + { + return id.toString(); + } +}; + + +} } // namespace Poco::ActiveRecord + + +#endif // ActiveRecord_IDTraits_INCLUDED diff --git a/ActiveRecord/include/Poco/ActiveRecord/Query.h b/ActiveRecord/include/Poco/ActiveRecord/Query.h new file mode 100644 index 000000000..b49b2abe8 --- /dev/null +++ b/ActiveRecord/include/Poco/ActiveRecord/Query.h @@ -0,0 +1,197 @@ +// +// Query.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: Query +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecord_select_INCLUDED +#define ActiveRecord_select_INCLUDED + + +#include "Poco/ActiveRecord/ActiveRecord.h" +#include "Poco/ActiveRecord/Context.h" +#include "Poco/Data/Session.h" +#include "Poco/Data/Statement.h" + + +namespace Poco { +namespace ActiveRecord { + + +template +class Query +{ +public: + explicit Query(Context::Ptr pContext): + _pContext(pContext), + _select(pContext->session()) + { + select(); + } + + Query() = delete; + Query(const Query&) = delete; + ~Query() = default; + Query& operator = (const Query&) = delete; + + Query& where(const std::string& clause) + { + _select << " WHERE " << fixPlaceholders(clause); + return *this; + } + + Query& orderBy(const std::string& order) + { + _select << " ORDER BY " << order; + return *this; + } + + template + Query& bind(const T& value) + { + _select, Poco::Data::Keywords::bind(value); + return *this; + } + + Query& offset(std::size_t offset) + { + _offset = offset; + return *this; + } + + Query& limit(std::size_t limit) + { + _limit = limit; + return *this; + } + + Query& filter(const std::function& fn) + { + _filter = fn; + return *this; + } + + Query& filter(std::function&& fn) + { + _filter = std::move(fn); + return *this; + } + + std::vector execute() + { + std::vector result; + + typename ActRec::Ptr pObject = new ActRec; + ActRec::queryInto(_select, *pObject); + _select, Poco::Data::Keywords::limit(1); + + std::size_t index = 0; + while (!_select.done()) + { + if (_select.execute()) + { + if (!_filter || _filter(*pObject)) + { + if (index >= _offset && (_limit == 0 || result.size() < _limit)) + { + typename ActRec::Ptr pClone = new ActRec(*pObject); + result.push_back(ActiveRecord::withContext(pClone, _pContext)); + } + index++; + } + } + } + _totalResults = index; + + return result; + } + + std::size_t totalResults() const + { + return _totalResults; + } + + void reset() + { + _offset = 0; + _limit = 0; + _totalResults = 0; + std::function emptyFilter; + _filter.swap(emptyFilter); + _select = Poco::Data::Statement(_pContext->session()); + select(); + } + +protected: + void select() + { + _select << "SELECT "; + const auto& columns = ActRec::columns(); + bool needComma = false; + for (const auto& c: columns) + { + if (needComma) _select << ", "; + _select << c; + needComma = true; + } + _select << " FROM " << ActRec::table(); + } + + std::string fixPlaceholders(const std::string& clause) + { + auto pSPP = _pContext->statementPlaceholderProvider(); + + std::string result; + auto it = clause.begin(); + auto end = clause.end(); + while (it != end) + { + switch (*it) + { + case '"': + result += *it++; + while (it != end && *it != '"') result += *it++; + if (it != end) result += *it++; + break; + + case '\'': + result += *it++; + while (it != end && *it != '\'') result += *it++; + if (it != end) result += *it++; + break; + + case '?': + result += pSPP->next(); + ++it; + break; + + default: + result += *it++; + break; + } + } + return result; + } + +private: + Context::Ptr _pContext; + Poco::Data::Statement _select; + std::function _filter; + std::size_t _offset = 0; + std::size_t _limit = 0; + std::size_t _totalResults = 0; +}; + + +} } // namespace Poco::ActiveRecord + + +#endif // ActiveRecord_select_INCLUDED diff --git a/ActiveRecord/include/Poco/ActiveRecord/StatementPlaceholderProvider.h b/ActiveRecord/include/Poco/ActiveRecord/StatementPlaceholderProvider.h new file mode 100644 index 000000000..5273f4b67 --- /dev/null +++ b/ActiveRecord/include/Poco/ActiveRecord/StatementPlaceholderProvider.h @@ -0,0 +1,62 @@ +// +// StatementPlaceholderProvider.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: StatementPlaceholderProvider +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecord_StatementPlaceholderProvider_INCLUDED +#define ActiveRecord_StatementPlaceholderProvider_INCLUDED + + +#include "Poco/ActiveRecord/ActiveRecordLib.h" +#include +#include + + +namespace Poco { +namespace ActiveRecord { + + +class ActiveRecordLib_API StatementPlaceholderProvider +{ +public: + using Ptr = std::unique_ptr; + + virtual void reset() = 0; + virtual std::string next() = 0; + + virtual ~StatementPlaceholderProvider(); +}; + + +class ActiveRecordLib_API DefaultStatementPlaceholderProvider: public StatementPlaceholderProvider +{ +public: + void reset(); + std::string next(); +}; + + +class ActiveRecordLib_API PostgresStatementPlaceholderProvider: public StatementPlaceholderProvider +{ +public: + void reset(); + std::string next(); + +private: + int _n = 1; +}; + + +} } // namespace Poco::ActiveRecord + + +#endif // ActiveRecord_StatementPlaceholderProvider_INCLUDED diff --git a/ActiveRecord/include/Poco/ActiveRecord/TypeHandler.h b/ActiveRecord/include/Poco/ActiveRecord/TypeHandler.h new file mode 100644 index 000000000..f65c0606e --- /dev/null +++ b/ActiveRecord/include/Poco/ActiveRecord/TypeHandler.h @@ -0,0 +1,70 @@ +// +// TypeHandler.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: TypeHandler +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecord_TypeHandler_INCLUDED +#define ActiveRecord_TypeHandler_INCLUDED + + +#include "Poco/Data/TypeHandler.h" +#include "Poco/ThreadLocal.h" +#include "Poco/UUID.h" +#include + + +namespace Poco { +namespace Data { + + +template <> +class TypeHandler +{ +public: + using UUIDMap = std::map; + + static std::size_t size() + { + return 1; + } + + static void bind(std::size_t pos, const Poco::UUID& uuid, AbstractBinder::Ptr pBinder, AbstractBinder::Direction dir) + { + static Poco::ThreadLocal uuidMap; + std::string& uuidString = (*uuidMap)[pos]; + uuidString = uuid.toString(); + TypeHandler::bind(pos++, uuidString, pBinder, dir); + } + + static void extract(std::size_t pos, Poco::UUID& uuid, const Poco::UUID& deflt, AbstractExtractor::Ptr pExtr) + { + std::string defltString = deflt.toString(); + std::string uuidString; + TypeHandler::extract(pos++, uuidString, defltString, pExtr); + uuid.parse(uuidString); + } + + static void prepare(std::size_t pos, const Poco::UUID& uuid, AbstractPreparator::Ptr pPrep) + { + static Poco::ThreadLocal uuidMap; + std::string& uuidString = (*uuidMap)[pos]; + uuidString = uuid.toString(); + TypeHandler::prepare(pos++, uuidString, pPrep); + } +}; + + +} } // namespace Poco::Data + + +#endif // ActiveRecord_TypeHandler_INCLUDED + diff --git a/ActiveRecord/src/ActiveRecord.cpp b/ActiveRecord/src/ActiveRecord.cpp new file mode 100644 index 000000000..3fb359e37 --- /dev/null +++ b/ActiveRecord/src/ActiveRecord.cpp @@ -0,0 +1,62 @@ +// +// ActiveRecord.h +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: ActiveRecord +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/ActiveRecord/ActiveRecord.h" +#include "Poco/NumberFormatter.h" +#include "Poco/Exception.h" + + +using namespace Poco::Data::Keywords; +using namespace std::string_literals; + + +namespace Poco { +namespace ActiveRecord { + + +void ActiveRecordBase::attach(Context::Ptr pContext) +{ + if (_pContext) throw Poco::IllegalStateException("ActiveRecord already has a Context"); + if (!pContext) throw Poco::InvalidArgumentException("Cannot attach to a null Context"); + + _pContext = pContext; +} + + +void ActiveRecordBase::detach() +{ + _pContext.reset(); +} + + +void ActiveRecordBase::create(Context::Ptr pContext) +{ + attach(pContext); + insert(); +} + + +bool ActiveRecordBase::isValid() const +{ + return true; +} + + +std::string KeylessActiveRecord::toString() const +{ + return ""s; +} + + +} } // namespace Poco::ActiveRecord diff --git a/ActiveRecord/src/Context.cpp b/ActiveRecord/src/Context.cpp new file mode 100644 index 000000000..499ba090e --- /dev/null +++ b/ActiveRecord/src/Context.cpp @@ -0,0 +1,43 @@ +// +// Context.cpp +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: Context +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/ActiveRecord/Context.h" + + +namespace Poco { +namespace ActiveRecord { + + +Context::Context(const Poco::Data::Session& session): + _session(session) +{ +} + + +Context::Context(const std::string& connector, const std::string& connectionString): + _session(connector, connectionString) +{ +} + + +StatementPlaceholderProvider::Ptr Context::statementPlaceholderProvider() const +{ + if (_session.connector() == "postgresql") + return std::make_unique(); + else + return std::make_unique(); +} + + +} } // namespace Poco::ActiveRecord diff --git a/ActiveRecord/src/IDTraits.cpp b/ActiveRecord/src/IDTraits.cpp new file mode 100644 index 000000000..7488051d8 --- /dev/null +++ b/ActiveRecord/src/IDTraits.cpp @@ -0,0 +1,32 @@ +// +// IDTraits.cpp +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: IDTraits +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/ActiveRecord/IDTraits.h" + + +namespace Poco { +namespace ActiveRecord { + + +const Poco::UInt64 IDTraits::INVALID_ID = std::numeric_limits::max(); +const Poco::Int64 IDTraits::INVALID_ID = std::numeric_limits::max(); +const Poco::UInt32 IDTraits::INVALID_ID = std::numeric_limits::max(); +const Poco::Int32 IDTraits::INVALID_ID = std::numeric_limits::max(); +const Poco::UInt16 IDTraits::INVALID_ID = std::numeric_limits::max(); +const Poco::Int16 IDTraits::INVALID_ID = std::numeric_limits::max(); +const std::string IDTraits::INVALID_ID; +const Poco::UUID IDTraits::INVALID_ID; + + +} } // namespace Poco::ActiveRecord diff --git a/ActiveRecord/src/StatementPlaceholderProvider.cpp b/ActiveRecord/src/StatementPlaceholderProvider.cpp new file mode 100644 index 000000000..85e6140c2 --- /dev/null +++ b/ActiveRecord/src/StatementPlaceholderProvider.cpp @@ -0,0 +1,54 @@ +// +// StatementPlaceholderProvider.cpp +// +// Library: ActiveRecord +// Package: ActiveRecord +// Module: StatementPlaceholderProvider +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/ActiveRecord/StatementPlaceholderProvider.h" +#include "Poco/Format.h" + + +using namespace std::string_literals; + + +namespace Poco { +namespace ActiveRecord { + + +StatementPlaceholderProvider::~StatementPlaceholderProvider() +{ +} + + +void DefaultStatementPlaceholderProvider::reset() +{ +} + + +std::string DefaultStatementPlaceholderProvider::next() +{ + return "?"s; +} + + +void PostgresStatementPlaceholderProvider::reset() +{ + _n = 1; +} + + +std::string PostgresStatementPlaceholderProvider::next() +{ + return Poco::format("$%d"s, _n++); +} + + +} } // namespace Poco::ActiveRecord diff --git a/ActiveRecord/testsuite/CMakeLists.txt b/ActiveRecord/testsuite/CMakeLists.txt new file mode 100644 index 000000000..bfbe34d48 --- /dev/null +++ b/ActiveRecord/testsuite/CMakeLists.txt @@ -0,0 +1,36 @@ +# Sources +file(GLOB SRCS_G "src/*.cpp") +POCO_SOURCES_AUTO(TEST_SRCS ${SRCS_G}) + +# Headers +file(GLOB_RECURSE HDRS_G "src/*.h") +POCO_HEADERS_AUTO(TEST_SRCS ${HDRS_G}) + +POCO_SOURCES_AUTO_PLAT(TEST_SRCS OFF + src/WinDriver.cpp +) + +POCO_SOURCES_AUTO_PLAT(TEST_SRCS WINCE + src/WinCEDriver.cpp +) + +add_executable(ActiveRecord-testrunner ${TEST_SRCS}) +if(ANDROID) + add_test( + NAME ActiveRecord + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -DANDROID_NDK=${ANDROID_NDK} -DLIBRARY_DIR=${CMAKE_BINARY_DIR}/lib -DUNITTEST=${CMAKE_BINARY_DIR}/bin/ActiveRecord-testrunner -DTEST_PARAMETER=-all -P ${CMAKE_SOURCE_DIR}/cmake/ExecuteOnAndroid.cmake + ) +else() + add_test( + NAME ActiveRecord + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ActiveRecord-testrunner -ignore ${CMAKE_SOURCE_DIR}/cppignore.lnx -all + ) + set_tests_properties(ActiveRecord PROPERTIES ENVIRONMENT POCO_BASE=${CMAKE_SOURCE_DIR}) +endif() + +target_link_libraries(ActiveRecord-testrunner PUBLIC Poco::DataSQLite Poco::Data Poco::ActiveRecord CppUnit) +target_include_directories(ActiveRecord-testrunner + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include +) diff --git a/ActiveRecord/testsuite/Makefile b/ActiveRecord/testsuite/Makefile new file mode 100644 index 000000000..45073e7a8 --- /dev/null +++ b/ActiveRecord/testsuite/Makefile @@ -0,0 +1,16 @@ +# +# Makefile +# +# Makefile for Poco ActiveRecord testsuite +# + +include $(POCO_BASE)/build/rules/global + +objects = ActiveRecordTestSuite ActiveRecordTest Driver \ + Employee Role + +target = testrunner +target_version = 1 +target_libs = PocoActiveRecord PocoDataSQLite PocoData PocoFoundation CppUnit + +include $(POCO_BASE)/build/rules/exec diff --git a/ActiveRecord/testsuite/ORM.xml b/ActiveRecord/testsuite/ORM.xml new file mode 100644 index 000000000..fc862917f --- /dev/null +++ b/ActiveRecord/testsuite/ORM.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/ActiveRecord/testsuite/include/ORM/Employee.h b/ActiveRecord/testsuite/include/ORM/Employee.h new file mode 100644 index 000000000..2310dbb0e --- /dev/null +++ b/ActiveRecord/testsuite/include/ORM/Employee.h @@ -0,0 +1,161 @@ +// +// Employee.h +// +// This file has been generated from ORM.xml. Do not edit. +// + + +#ifndef ORM_Employee_INCLUDED +#define ORM_Employee_INCLUDED + + +#include "Poco/ActiveRecord/ActiveRecord.h" +#include "ORM/Role.h" + + +namespace ORM { + + +class Employee: public Poco::ActiveRecord::ActiveRecord +{ +public: + using Ptr = Poco::AutoPtr; + + explicit Employee(ID id); + Employee() = default; + Employee(const Employee& other); + ~Employee() = default; + + const std::string& name() const; + Employee& name(const std::string& value); + + const std::string& ssn() const; + Employee& ssn(const std::string& value); + + Role::Ptr role() const; + Poco::Int16 roleID() const; + Employee& role(Role::Ptr pObject); + Employee& roleID(Poco::Int16 id); + + Employee::Ptr manager() const; + const Poco::UUID& managerID() const; + Employee& manager(Employee::Ptr pObject); + Employee& managerID(const Poco::UUID& id); + + static Ptr find(Poco::ActiveRecord::Context::Ptr pContext, const ID& id); + + void insert(); + void update(); + void remove(); + + static const std::vector& columns(); + static const std::string& table(); + +private: + std::string _name; + std::string _ssn; + Poco::Int16 _role = Role::INVALID_ID; + Poco::UUID _manager; + + friend class Poco::Data::TypeHandler; +}; + + +inline const std::string& Employee::name() const +{ + return _name; +} + + +inline Employee& Employee::name(const std::string& value) +{ + _name = value; + return *this; +} + + +inline const std::string& Employee::ssn() const +{ + return _ssn; +} + + +inline Employee& Employee::ssn(const std::string& value) +{ + _ssn = value; + return *this; +} + + +inline Poco::Int16 Employee::roleID() const +{ + return _role; +} + + +inline Employee& Employee::roleID(Poco::Int16 value) +{ + _role = value; + return *this; +} + + +inline const Poco::UUID& Employee::managerID() const +{ + return _manager; +} + + +inline Employee& Employee::managerID(const Poco::UUID& value) +{ + _manager = value; + return *this; +} + + +} // namespace ORM + + +namespace Poco { +namespace Data { + + +template <> +class TypeHandler +{ +public: + static std::size_t size() + { + return 4; + } + + static void bind(std::size_t pos, const ORM::Employee& ar, AbstractBinder::Ptr pBinder, AbstractBinder::Direction dir) + { + TypeHandler::bind(pos++, ar._name, pBinder, dir); + TypeHandler::bind(pos++, ar._ssn, pBinder, dir); + TypeHandler::bind(pos++, ar._role, pBinder, dir); + TypeHandler::bind(pos++, ar._manager, pBinder, dir); +} + + static void extract(std::size_t pos, ORM::Employee& ar, const ORM::Employee& deflt, AbstractExtractor::Ptr pExtr) + { + TypeHandler::extract(pos++, ar._name, deflt._name, pExtr); + TypeHandler::extract(pos++, ar._ssn, deflt._ssn, pExtr); + TypeHandler::extract(pos++, ar._role, deflt._role, pExtr); + TypeHandler::extract(pos++, ar._manager, deflt._manager, pExtr); +} + + static void prepare(std::size_t pos, const ORM::Employee& ar, AbstractPreparator::Ptr pPrep) + { + TypeHandler::prepare(pos++, ar._name, pPrep); + TypeHandler::prepare(pos++, ar._ssn, pPrep); + TypeHandler::prepare(pos++, ar._role, pPrep); + TypeHandler::prepare(pos++, ar._manager, pPrep); + } +}; + + +} } // namespace Poco::Data + + +#endif // ORM_Employee_INCLUDED diff --git a/ActiveRecord/testsuite/include/ORM/Role.h b/ActiveRecord/testsuite/include/ORM/Role.h new file mode 100644 index 000000000..eb8658325 --- /dev/null +++ b/ActiveRecord/testsuite/include/ORM/Role.h @@ -0,0 +1,116 @@ +// +// Role.h +// +// This file has been generated from ORM.xml. Do not edit. +// + + +#ifndef ORM_Role_INCLUDED +#define ORM_Role_INCLUDED + + +#include "Poco/ActiveRecord/ActiveRecord.h" + + +namespace ORM { + + +class Role: public Poco::ActiveRecord::ActiveRecord +{ +public: + using Ptr = Poco::AutoPtr; + + explicit Role(ID id); + Role() = default; + Role(const Role& other); + ~Role() = default; + + const std::string& name() const; + Role& name(const std::string& value); + + const std::string& description() const; + Role& description(const std::string& value); + + static Ptr find(Poco::ActiveRecord::Context::Ptr pContext, const ID& id); + + void insert(); + void update(); + void remove(); + + static const std::vector& columns(); + static const std::string& table(); + +private: + std::string _name; + std::string _description; + + friend class Poco::Data::TypeHandler; +}; + + +inline const std::string& Role::name() const +{ + return _name; +} + + +inline Role& Role::name(const std::string& value) +{ + _name = value; + return *this; +} + + +inline const std::string& Role::description() const +{ + return _description; +} + + +inline Role& Role::description(const std::string& value) +{ + _description = value; + return *this; +} + + +} // namespace ORM + + +namespace Poco { +namespace Data { + + +template <> +class TypeHandler +{ +public: + static std::size_t size() + { + return 2; + } + + static void bind(std::size_t pos, const ORM::Role& ar, AbstractBinder::Ptr pBinder, AbstractBinder::Direction dir) + { + TypeHandler::bind(pos++, ar._name, pBinder, dir); + TypeHandler::bind(pos++, ar._description, pBinder, dir); +} + + static void extract(std::size_t pos, ORM::Role& ar, const ORM::Role& deflt, AbstractExtractor::Ptr pExtr) + { + TypeHandler::extract(pos++, ar._name, deflt._name, pExtr); + TypeHandler::extract(pos++, ar._description, deflt._description, pExtr); +} + + static void prepare(std::size_t pos, const ORM::Role& ar, AbstractPreparator::Ptr pPrep) + { + TypeHandler::prepare(pos++, ar._name, pPrep); + TypeHandler::prepare(pos++, ar._description, pPrep); + } +}; + + +} } // namespace Poco::Data + + +#endif // ORM_Role_INCLUDED diff --git a/ActiveRecord/testsuite/src/ActiveRecordTest.cpp b/ActiveRecord/testsuite/src/ActiveRecordTest.cpp new file mode 100644 index 000000000..da63dfe4d --- /dev/null +++ b/ActiveRecord/testsuite/src/ActiveRecordTest.cpp @@ -0,0 +1,333 @@ +// +// ActiveRecordTest.cpp +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "ActiveRecordTest.h" +#include "CppUnit/TestCaller.h" +#include "CppUnit/TestSuite.h" +#include "Poco/ActiveRecord/Context.h" +#include "Poco/ActiveRecord/Query.h" +#include "Poco/Data/SQLite/Connector.h" +#include "Poco/Data/Statement.h" +#include "Poco/UUIDGenerator.h" +#include "ORM/Employee.h" +#include "ORM/Role.h" + + +using namespace std::string_literals; +using namespace Poco::Data::Keywords; +using Poco::ActiveRecord::Context; +using Poco::ActiveRecord::Query; +using ORM::Employee; +using ORM::Role; + + +const std::string ActiveRecordTest::CONNECTOR("SQLite"); +const std::string ActiveRecordTest::CONNECTION_STRING("ORM.sqlite"); + + +ActiveRecordTest::ActiveRecordTest(const std::string& name): CppUnit::TestCase(name) +{ +} + + +ActiveRecordTest::~ActiveRecordTest() +{ +} + + +void ActiveRecordTest::testInsert() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + Role::Ptr pDeveloper = new Role; + pDeveloper->name("Developer").description("Developer role"); + + assertTrue (!pDeveloper->isValid()); + + pDeveloper->create(pContext); + + assertTrue (pDeveloper->isValid()); + + int n = 0; + session << "SELECT COUNT(*) FROM roles", into(n), now; + assertTrue (n == 1); + + assertTrue (pDeveloper->id() == 1); + + Role::Ptr pSeniorDeveloper = new Role; + pSeniorDeveloper->name("Senior Developer").description("Senior developer role"); + + pSeniorDeveloper->create(pContext); + + session << "SELECT COUNT(*) FROM roles", into(n), now; + assertTrue (n == 2); + + assertTrue (pSeniorDeveloper->id() == 2); +} + + +void ActiveRecordTest::testFind() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Role::Ptr pRole = Role::find(pContext, 1); + assertTrue (!pRole.isNull()); + assertTrue (pRole->name() == "Developer"); + assertTrue (pRole->description() == "Developer role"); + + pRole = Role::find(pContext, 2); + assertTrue (!pRole.isNull()); + assertTrue (pRole->name() == "Senior Developer"); + assertTrue (pRole->description() == "Senior developer role"); + + pRole = Role::find(pContext, 3); + assertTrue (!pRole.isNull()); + assertTrue (pRole->name() == "Manager"); + assertTrue (pRole->description() == "Manager role"); + + pRole = Role::find(pContext, 4); + assertTrue (pRole.isNull()); +} + + +void ActiveRecordTest::testUpdate() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Role::Ptr pRole = Role::find(pContext, 1); + assertTrue (!pRole.isNull()); + pRole->name("Junior Developer").description("Junior developer role"); + pRole->update(); + + pRole = Role::find(pContext, 1); + assertTrue (!pRole.isNull()); + assertTrue (pRole->name() == "Junior Developer"); + assertTrue (pRole->description() == "Junior developer role"); +} + + +void ActiveRecordTest::testDelete() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Role::Ptr pRole = Role::find(pContext, 1); + assertTrue (!pRole.isNull()); + + pRole->remove(); + + pRole = Role::find(pContext, 1); + assertTrue (pRole.isNull()); +} + + +void ActiveRecordTest::testRelations() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Employee::Ptr pManager = new Employee(Poco::UUIDGenerator().createOne()); + pManager->name("Bill Lumbergh").ssn("23452343").roleID(3); + pManager->create(pContext); + + Role::Ptr pManagerRole = pManager->role(); + assertFalse (pManagerRole.isNull()); + assertTrue (pManagerRole->id() == 3); + + Employee::Ptr pEmployee = new Employee(Poco::UUIDGenerator().createOne()); + pEmployee->name("Michael Bolton").ssn("123987123").roleID(2).manager(pManager); + pEmployee->create(pContext); + + assertTrue (pEmployee->managerID() == pManager->id()); +} + + +void ActiveRecordTest::testQuery() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Query query(pContext); + + auto result = query.execute(); + assertTrue (result.size() == 3); +} + + +void ActiveRecordTest::testQueryWhere() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Query query(pContext); + query.where("name = 'Senior Developer'"); + + auto result = query.execute(); + assertTrue (result.size() == 1); + assertTrue (result[0]->name() == "Senior Developer"); +} + + +void ActiveRecordTest::testQueryWhereBind() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Query query(pContext); + query.where("name = ?").bind("Senior Developer"s); + + auto result = query.execute(); + assertTrue (result.size() == 1); + assertTrue (result[0]->name() == "Senior Developer"); +} + + +void ActiveRecordTest::testQueryFilter() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Query query(pContext); + query.filter( + [](const Role& role) + { + return role.name() == "Senior Developer"; + } + ); + + auto result = query.execute(); + assertTrue (result.size() == 1); + assertTrue (result[0]->name() == "Senior Developer"); +} + + +void ActiveRecordTest::testQueryOrderBy() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Query query(pContext); + query.orderBy("id DESC"); + + auto result = query.execute(); + assertTrue (result.size() == 3); + assertTrue (result[0]->name() == "Manager"); +} + + +void ActiveRecordTest::testQueryPaging() +{ + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + Context::Ptr pContext = new Context(session); + + createRoles(pContext); + + Query query(pContext); + auto result = query.orderBy("id").offset(0).limit(2).execute(); + assertTrue (result.size() == 2); + assertTrue (result[0]->name() == "Developer"); + assertTrue (result[1]->name() == "Senior Developer"); + + query.reset(); + result = query.orderBy("id").offset(1).limit(2).execute(); + assertTrue (result.size() == 2); + assertTrue (result[0]->name() == "Senior Developer"); + assertTrue (result[1]->name() == "Manager"); +} + + +void ActiveRecordTest::setUp() +{ + Poco::Data::SQLite::Connector::registerConnector(); + + Poco::Data::Session session(CONNECTOR, CONNECTION_STRING); + + session << "DROP TABLE IF EXISTS employees", now; + session << "DROP TABLE IF EXISTS roles", now; + session + << "CREATE TABLE employees (" + << " id CHAR(36)," + << " name VARCHAR(64)," + << " ssn VARCHAR(32)," + << " role INTEGER," + << " manager CHAR(36)" + << ")", + now; + session + << "CREATE TABLE roles (" + << " id INTEGER PRIMARY KEY AUTOINCREMENT," + << " name VARCHAR(64)," + << " description VARCHAR(256)" + << ")", + now; +} + + +void ActiveRecordTest::tearDown() +{ + Poco::Data::SQLite::Connector::unregisterConnector(); +} + + +void ActiveRecordTest::createRoles(Poco::ActiveRecord::Context::Ptr pContext) +{ + Role::Ptr pDeveloper = new Role; + pDeveloper->name("Developer").description("Developer role"); + pDeveloper->create(pContext); + + Role::Ptr pSeniorDeveloper = new Role; + pSeniorDeveloper->name("Senior Developer").description("Senior developer role"); + pSeniorDeveloper->create(pContext); + + Role::Ptr pManager = new Role; + pManager->name("Manager").description("Manager role"); + pManager->create(pContext); +} + + +CppUnit::Test* ActiveRecordTest::suite() +{ + CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ActiveRecordTest"); + + CppUnit_addTest(pSuite, ActiveRecordTest, testInsert); + CppUnit_addTest(pSuite, ActiveRecordTest, testFind); + CppUnit_addTest(pSuite, ActiveRecordTest, testUpdate); + CppUnit_addTest(pSuite, ActiveRecordTest, testDelete); + CppUnit_addTest(pSuite, ActiveRecordTest, testRelations); + CppUnit_addTest(pSuite, ActiveRecordTest, testQuery); + CppUnit_addTest(pSuite, ActiveRecordTest, testQueryWhere); + CppUnit_addTest(pSuite, ActiveRecordTest, testQueryWhereBind); + CppUnit_addTest(pSuite, ActiveRecordTest, testQueryOrderBy); + CppUnit_addTest(pSuite, ActiveRecordTest, testQueryFilter); + CppUnit_addTest(pSuite, ActiveRecordTest, testQueryPaging); + + return pSuite; +} diff --git a/ActiveRecord/testsuite/src/ActiveRecordTest.h b/ActiveRecord/testsuite/src/ActiveRecordTest.h new file mode 100644 index 000000000..00f818c7a --- /dev/null +++ b/ActiveRecord/testsuite/src/ActiveRecordTest.h @@ -0,0 +1,51 @@ +// +// ActiveRecordTest.h +// +// Definition of the ActiveRecordTest class. +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecordTest_INCLUDED +#define ActiveRecordTest_INCLUDED + + +#include "Poco/ActiveRecord/Context.h" +#include "CppUnit/TestCase.h" + + +class ActiveRecordTest: public CppUnit::TestCase +{ +public: + ActiveRecordTest(const std::string& name); + ~ActiveRecordTest(); + + void testInsert(); + void testFind(); + void testUpdate(); + void testDelete(); + void testRelations(); + void testQuery(); + void testQueryWhere(); + void testQueryWhereBind(); + void testQueryOrderBy(); + void testQueryFilter(); + void testQueryPaging(); + + void setUp(); + void tearDown(); + + void createRoles(Poco::ActiveRecord::Context::Ptr pContext); + + static CppUnit::Test* suite(); + + static const std::string CONNECTOR; + static const std::string CONNECTION_STRING; +}; + + +#endif // ActiveRecordTest_INCLUDED diff --git a/ActiveRecord/testsuite/src/ActiveRecordTestSuite.cpp b/ActiveRecord/testsuite/src/ActiveRecordTestSuite.cpp new file mode 100644 index 000000000..2b602ed2b --- /dev/null +++ b/ActiveRecord/testsuite/src/ActiveRecordTestSuite.cpp @@ -0,0 +1,22 @@ +// +// ActiveRecordTestSuite.cpp +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "ActiveRecordTestSuite.h" +#include "ActiveRecordTest.h" + + +CppUnit::Test* ActiveRecordTestSuite::suite() +{ + CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ActiveRecordTestSuite"); + + pSuite->addTest(ActiveRecordTest::suite()); + + return pSuite; +} diff --git a/ActiveRecord/testsuite/src/ActiveRecordTestSuite.h b/ActiveRecord/testsuite/src/ActiveRecordTestSuite.h new file mode 100644 index 000000000..c11708502 --- /dev/null +++ b/ActiveRecord/testsuite/src/ActiveRecordTestSuite.h @@ -0,0 +1,27 @@ +// +// ActiveRecordTestSuite.h +// +// Definition of the ActiveRecordTestSuite class. +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef ActiveRecordTestSuite_INCLUDED +#define ActiveRecordTestSuite_INCLUDED + + +#include "CppUnit/TestSuite.h" + + +class ActiveRecordTestSuite +{ +public: + static CppUnit::Test* suite(); +}; + + +#endif // ActiveRecordTestSuite_INCLUDED diff --git a/ActiveRecord/testsuite/src/Driver.cpp b/ActiveRecord/testsuite/src/Driver.cpp new file mode 100644 index 000000000..6cfcc6021 --- /dev/null +++ b/ActiveRecord/testsuite/src/Driver.cpp @@ -0,0 +1,17 @@ +// +// Driver.cpp +// +// Console-based test driver for Poco ActiveRecord. +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "CppUnit/TestRunner.h" +#include "ActiveRecordTestSuite.h" + + +CppUnitMain(ActiveRecordTestSuite) diff --git a/ActiveRecord/testsuite/src/Employee.cpp b/ActiveRecord/testsuite/src/Employee.cpp new file mode 100644 index 000000000..928f849d8 --- /dev/null +++ b/ActiveRecord/testsuite/src/Employee.cpp @@ -0,0 +1,151 @@ +// +// Employee.cpp +// +// This file has been generated from ORM.xml. Do not edit. +// + + +#include "ORM/Employee.h" +#include "Poco/UUIDGenerator.h" + + +using namespace std::string_literals; +using namespace Poco::Data::Keywords; + + +namespace ORM { + + +Employee::Employee(ID id): + Poco::ActiveRecord::ActiveRecord(id) +{ +} + + +Employee::Employee(const Employee& other): + Poco::ActiveRecord::ActiveRecord(other), + _name(other._name), + _ssn(other._ssn), + _role(other._role), + _manager(other._manager) +{ +} + + +Role::Ptr Employee::role() const +{ + return Role::find(context(), _role); +} + + +Employee& Employee::role(Role::Ptr pObject) +{ + if (pObject) + _role = pObject->id(); + else + _role = Role::INVALID_ID; + return *this; +} + + +Employee::Ptr Employee::manager() const +{ + return Employee::find(context(), _manager); +} + + +Employee& Employee::manager(Employee::Ptr pObject) +{ + if (pObject) + _manager = pObject->id(); + else + _manager = Employee::INVALID_ID; + return *this; +} + + +Employee::Ptr Employee::find(Poco::ActiveRecord::Context::Ptr pContext, const ID& id) +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(pContext->statementPlaceholderProvider()); + Employee::Ptr pObject(new Employee); + + pContext->session() + << "SELECT id, name, ssn, role, manager" + << " FROM employees" + << " WHERE id = " << pSPP->next(), + into(pObject->mutableID()), + into(*pObject), + bind(id), + now; + + return withContext(pObject, pContext); +} + + +void Employee::insert() +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider()); + + if (id().isNull()) + { + mutableID() = Poco::UUIDGenerator().createRandom(); + } + + context()->session() + << "INSERT INTO employees (id, name, ssn, role, manager)" + << " VALUES (" << pSPP->next() << ", " << pSPP->next() << ", " << pSPP->next() << ", " << pSPP->next() << ", " << pSPP->next() << ")", + bind(id()), + use(*this), + now; +} + + +void Employee::update() +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider()); + + context()->session() + << "UPDATE employees" + << " SET name = " << pSPP->next() << ", ssn = " << pSPP->next() << ", role = " << pSPP->next() << ", manager = " << pSPP->next() + << " WHERE id = " << pSPP->next(), + use(*this), + bind(id()), + now; +} + + +void Employee::remove() +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider()); + + context()->session() + << "DELETE FROM employees" + << " WHERE id = " << pSPP->next(), + bind(id()), + now; +} + + +const std::vector& Employee::columns() +{ + static const std::vector cols = + { + "id"s, + "name"s, + "ssn"s, + "role"s, + "manager"s, + }; + + return cols; +} + + +const std::string& Employee::table() +{ + static const std::string t = "employees"; + return t; +} + + +} // namespace ORM diff --git a/ActiveRecord/testsuite/src/Role.cpp b/ActiveRecord/testsuite/src/Role.cpp new file mode 100644 index 000000000..95b6768cb --- /dev/null +++ b/ActiveRecord/testsuite/src/Role.cpp @@ -0,0 +1,109 @@ +// +// Role.cpp +// +// This file has been generated from ORM.xml. Do not edit. +// + + +#include "ORM/Role.h" + + +using namespace std::string_literals; +using namespace Poco::Data::Keywords; + + +namespace ORM { + + +Role::Role(ID id): + Poco::ActiveRecord::ActiveRecord(id) +{ +} + + +Role::Role(const Role& other): + Poco::ActiveRecord::ActiveRecord(other), + _name(other._name), + _description(other._description) +{ +} + + +Role::Ptr Role::find(Poco::ActiveRecord::Context::Ptr pContext, const ID& id) +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(pContext->statementPlaceholderProvider()); + Role::Ptr pObject(new Role); + + pContext->session() + << "SELECT id, name, description" + << " FROM roles" + << " WHERE id = " << pSPP->next(), + into(pObject->mutableID()), + into(*pObject), + bind(id), + now; + + return withContext(pObject, pContext); +} + + +void Role::insert() +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider()); + + context()->session() + << "INSERT INTO roles (id, name, description)" + << " VALUES (NULL, " << pSPP->next() << ", " << pSPP->next() << ")", + use(*this), + now; + updateID(context()->session()); +} + + +void Role::update() +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider()); + + context()->session() + << "UPDATE roles" + << " SET name = " << pSPP->next() << ", description = " << pSPP->next() + << " WHERE id = " << pSPP->next(), + use(*this), + bind(id()), + now; +} + + +void Role::remove() +{ + Poco::ActiveRecord::StatementPlaceholderProvider::Ptr pSPP(context()->statementPlaceholderProvider()); + + context()->session() + << "DELETE FROM roles" + << " WHERE id = " << pSPP->next(), + bind(id()), + now; +} + + +const std::vector& Role::columns() +{ + static const std::vector cols = + { + "id"s, + "name"s, + "description"s, + }; + + return cols; +} + + +const std::string& Role::table() +{ + static const std::string t = "roles"; + return t; +} + + +} // namespace ORM diff --git a/ActiveRecord/testsuite/src/WinCEDriver.cpp b/ActiveRecord/testsuite/src/WinCEDriver.cpp new file mode 100644 index 000000000..54d2eaa95 --- /dev/null +++ b/ActiveRecord/testsuite/src/WinCEDriver.cpp @@ -0,0 +1,30 @@ +// +// WinCEDriver.cpp +// +// Console-based test driver for Windows CE. +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "CppUnit/TestRunner.h" +#include "ActiveRecordTestSuite.h" +#include + + +int wmain(int argc, wchar_t* argv[]) +{ + std::vector args; + for (int i = 0; i < argc; ++i) + { + char buffer[1024]; + std::wcstombs(buffer, argv[i], sizeof(buffer)); + args.push_back(std::string(buffer)); + } + CppUnit::TestRunner runner; + runner.addTest("ActiveRecordTestSuite", ActiveRecordTestSuite::suite()); + return runner.run(args) ? 0 : 1; +} diff --git a/ActiveRecord/testsuite/src/WinDriver.cpp b/ActiveRecord/testsuite/src/WinDriver.cpp new file mode 100644 index 000000000..6dc7eec05 --- /dev/null +++ b/ActiveRecord/testsuite/src/WinDriver.cpp @@ -0,0 +1,28 @@ +// +// WinDriver.cpp +// +// Windows test driver for Poco ActiveRecord. +// +// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: Apache-2.0 +// + + +#include "WinTestRunner/WinTestRunner.h" +#include "ActiveRecordTestSuite.h" + + +class TestDriver: public CppUnit::WinTestRunnerApp +{ + void TestMain() + { + CppUnit::WinTestRunner runner; + runner.addTest(ActiveRecordTestSuite::suite()); + runner.run(); + } +}; + + +TestDriver theDriver; diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b3c3f813..198df464c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,14 @@ option(ENABLE_POCODOC "Enable Poco Documentation Generator" OFF) option(ENABLE_PAGECOMPILER "Enable PageCompiler" ON) option(ENABLE_PAGECOMPILER_FILE2PAGE "Enable File2Page" ON) +if(ENABLE_DATA) + option(ENABLE_ACTIVERECORD "Enable ActiveRecord" ON) + option(ENABLE_ACTIVERECORD_COMPILER "Enable ActiveRecord Compiler" ON) +else() + option(ENABLE_ACTIVERECORD "Enable ActiveRecord" OFF) + option(ENABLE_ACTIVERECORD_COMPILER "Enable ActiveRecord Compiler" OFF) +endif() + option(ENABLE_TESTS "Set to OFF|ON (default is OFF) to control build of POCO tests & samples" OFF) @@ -327,6 +335,16 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/Redis AND ENABLE_REDIS) list(APPEND Poco_COMPONENTS "Redis") endif() +if(EXISTS ${PROJECT_SOURCE_DIR}/ActiveRecord AND ENABLE_ACTIVERECORD) + add_subdirectory(ActiveRecord) + list(APPEND Poco_COMPONENTS "ActiveRecord") +endif() + +if(EXISTS ${PROJECT_SOURCE_DIR}/ActiveRecord/Compiler AND ENABLE_ACTIVERECORD_COMPILER) + add_subdirectory(ActiveRecord/Compiler) + list(APPEND Poco_COMPONENTS "ActiveRecordCompiler") +endif() + if(EXISTS ${PROJECT_SOURCE_DIR}/PDF AND ENABLE_PDF) add_subdirectory(PDF) list(APPEND Poco_COMPONENTS "PDF") diff --git a/Makefile b/Makefile index c184e90c6..37fb2f417 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ poco: libexecs $(if $(TESTS),tests) $(if $(SAMPLES),samples) all: libexecs tests samples INSTALLDIR = $(DESTDIR)$(POCO_PREFIX) -COMPONENTS = Foundation Encodings XML JSON Util Net Crypto NetSSL_OpenSSL Data Data/SQLite Data/ODBC Data/MySQL Zip PageCompiler PageCompiler/File2Page JWT CppParser PDF MongoDB Redis +COMPONENTS = Foundation Encodings XML JSON Util Net Crypto NetSSL_OpenSSL Data Data/SQLite Data/ODBC Data/MySQL Zip PageCompiler PageCompiler/File2Page JWT CppParser PDF MongoDB Redis ActiveRecord ActiveRecord/Compiler cppunit: $(MAKE) -C $(POCO_BASE)/CppUnit @@ -115,10 +115,10 @@ endif find $(INSTALLDIR)/lib -name "libPoco*" -type f -exec rm -f {} \; find $(INSTALLDIR)/lib -name "libPoco*" -type l -exec rm -f {} \; -libexecs = Foundation-libexec Encodings-libexec XML-libexec JSON-libexec Util-libexec Net-libexec Crypto-libexec NetSSL_OpenSSL-libexec Data-libexec Data/SQLite-libexec Data/ODBC-libexec Data/MySQL-libexec Zip-libexec JWT-libexec PageCompiler-libexec PageCompiler/File2Page-libexec CppParser-libexec PDF-libexec MongoDB-libexec Redis-libexec -tests = Foundation-tests Encodings-tests XML-tests JSON-tests Util-tests Net-tests Crypto-tests NetSSL_OpenSSL-tests Data-tests Data/SQLite-tests Data/ODBC-tests Data/MySQL-tests JWT-tests Zip-tests CppParser-tests PDF-tests MongoDB-tests Redis-tests +libexecs = Foundation-libexec Encodings-libexec XML-libexec JSON-libexec Util-libexec Net-libexec Crypto-libexec NetSSL_OpenSSL-libexec Data-libexec Data/SQLite-libexec Data/ODBC-libexec Data/MySQL-libexec Zip-libexec JWT-libexec PageCompiler-libexec PageCompiler/File2Page-libexec CppParser-libexec PDF-libexec MongoDB-libexec Redis-libexec ActiveRecord-libexec ActiveRecord/Compiler-libexec +tests = Foundation-tests Encodings-tests XML-tests JSON-tests Util-tests Net-tests Crypto-tests NetSSL_OpenSSL-tests Data-tests Data/SQLite-tests Data/ODBC-tests Data/MySQL-tests JWT-tests Zip-tests CppParser-tests PDF-tests MongoDB-tests Redis-tests ActiveRecord-tests samples = Foundation-samples Encodings-samples XML-samples JSON-samples Util-samples Net-samples Crypto-samples NetSSL_OpenSSL-samples Data-samples MongoDB-samples Zip-samples PageCompiler-samples PDF-samples -cleans = Foundation-clean Encodings-clean XML-clean JSON-clean Util-clean Net-clean Crypto-clean NetSSL_OpenSSL-clean Data-clean Data/SQLite-clean Data/ODBC-clean Data/MySQL-clean JWT-clean Zip-clean PageCompiler-clean PageCompiler/File2Page-clean CppParser-clean PDF-clean MongoDB-clean Redis-clean +cleans = Foundation-clean Encodings-clean XML-clean JSON-clean Util-clean Net-clean Crypto-clean NetSSL_OpenSSL-clean Data-clean Data/SQLite-clean Data/ODBC-clean Data/MySQL-clean JWT-clean Zip-clean PageCompiler-clean PageCompiler/File2Page-clean CppParser-clean PDF-clean MongoDB-clean Redis-clean ActiveRecord-clean ActiveRecord/Compiler-clean .PHONY: $(libexecs) .PHONY: $(tests) @@ -144,7 +144,7 @@ Foundation-clean: $(MAKE) -C $(POCO_BASE)/Foundation/testsuite clean $(MAKE) -C $(POCO_BASE)/Foundation/samples clean -Encodings-libexec: Foundation-libexec +Encodings-libexec: Foundation-libexec $(MAKE) -C $(POCO_BASE)/Encodings Encodings-tests: Encodings-libexec cppunit @@ -158,7 +158,7 @@ Encodings-clean: $(MAKE) -C $(POCO_BASE)/Encodings/testsuite clean $(MAKE) -C $(POCO_BASE)/Encodings/samples clean -XML-libexec: Foundation-libexec +XML-libexec: Foundation-libexec $(MAKE) -C $(POCO_BASE)/XML XML-tests: XML-libexec cppunit @@ -172,7 +172,7 @@ XML-clean: $(MAKE) -C $(POCO_BASE)/XML/testsuite clean $(MAKE) -C $(POCO_BASE)/XML/samples clean -JSON-libexec: Foundation-libexec +JSON-libexec: Foundation-libexec $(MAKE) -C $(POCO_BASE)/JSON JSON-tests: JSON-libexec cppunit @@ -186,7 +186,7 @@ JSON-clean: $(MAKE) -C $(POCO_BASE)/JSON/testsuite clean $(MAKE) -C $(POCO_BASE)/JSON/samples clean -Util-libexec: Foundation-libexec XML-libexec JSON-libexec +Util-libexec: Foundation-libexec XML-libexec JSON-libexec $(MAKE) -C $(POCO_BASE)/Util Util-tests: Util-libexec cppunit @@ -200,7 +200,7 @@ Util-clean: $(MAKE) -C $(POCO_BASE)/Util/testsuite clean $(MAKE) -C $(POCO_BASE)/Util/samples clean -Net-libexec: Foundation-libexec +Net-libexec: Foundation-libexec $(MAKE) -C $(POCO_BASE)/Net Net-tests: Net-libexec cppunit @@ -214,7 +214,7 @@ Net-clean: $(MAKE) -C $(POCO_BASE)/Net/testsuite clean $(MAKE) -C $(POCO_BASE)/Net/samples clean -Crypto-libexec: Foundation-libexec +Crypto-libexec: Foundation-libexec $(MAKE) -C $(POCO_BASE)/Crypto Crypto-tests: Crypto-libexec cppunit @@ -228,7 +228,7 @@ Crypto-clean: $(MAKE) -C $(POCO_BASE)/Crypto/testsuite clean $(MAKE) -C $(POCO_BASE)/Crypto/samples clean -NetSSL_OpenSSL-libexec: Foundation-libexec Net-libexec Util-libexec Crypto-libexec +NetSSL_OpenSSL-libexec: Foundation-libexec Net-libexec Util-libexec Crypto-libexec $(MAKE) -C $(POCO_BASE)/NetSSL_OpenSSL NetSSL_OpenSSL-tests: NetSSL_OpenSSL-libexec cppunit @@ -242,7 +242,7 @@ NetSSL_OpenSSL-clean: $(MAKE) -C $(POCO_BASE)/NetSSL_OpenSSL/testsuite clean $(MAKE) -C $(POCO_BASE)/NetSSL_OpenSSL/samples clean -Data-libexec: Foundation-libexec +Data-libexec: Foundation-libexec $(MAKE) -C $(POCO_BASE)/Data Data-tests: Data-libexec cppunit @@ -256,7 +256,7 @@ Data-clean: $(MAKE) -C $(POCO_BASE)/Data/testsuite clean $(MAKE) -C $(POCO_BASE)/Data/samples clean -Data/SQLite-libexec: Foundation-libexec Data-libexec +Data/SQLite-libexec: Foundation-libexec Data-libexec $(MAKE) -C $(POCO_BASE)/Data/SQLite Data/SQLite-tests: Data/SQLite-libexec cppunit @@ -266,7 +266,7 @@ Data/SQLite-clean: $(MAKE) -C $(POCO_BASE)/Data/SQLite clean $(MAKE) -C $(POCO_BASE)/Data/SQLite/testsuite clean -Data/ODBC-libexec: Foundation-libexec Data-libexec +Data/ODBC-libexec: Foundation-libexec Data-libexec $(MAKE) -C $(POCO_BASE)/Data/ODBC Data/ODBC-tests: Data/ODBC-libexec cppunit @@ -276,7 +276,7 @@ Data/ODBC-clean: $(MAKE) -C $(POCO_BASE)/Data/ODBC clean $(MAKE) -C $(POCO_BASE)/Data/ODBC/testsuite clean -Data/MySQL-libexec: Foundation-libexec Data-libexec +Data/MySQL-libexec: Foundation-libexec Data-libexec $(MAKE) -C $(POCO_BASE)/Data/MySQL Data/MySQL-tests: Data/MySQL-libexec cppunit @@ -286,7 +286,7 @@ Data/MySQL-clean: $(MAKE) -C $(POCO_BASE)/Data/MySQL clean $(MAKE) -C $(POCO_BASE)/Data/MySQL/testsuite clean -Zip-libexec: Foundation-libexec Net-libexec Util-libexec XML-libexec +Zip-libexec: Foundation-libexec Net-libexec Util-libexec XML-libexec $(MAKE) -C $(POCO_BASE)/Zip Zip-tests: Zip-libexec cppunit @@ -300,7 +300,7 @@ Zip-clean: $(MAKE) -C $(POCO_BASE)/Zip/testsuite clean $(MAKE) -C $(POCO_BASE)/Zip/samples clean -PageCompiler-libexec: Net-libexec Util-libexec XML-libexec Foundation-libexec +PageCompiler-libexec: Net-libexec Util-libexec XML-libexec Foundation-libexec $(MAKE) -C $(POCO_BASE)/PageCompiler PageCompiler-samples: PageCompiler-libexec @@ -310,13 +310,13 @@ PageCompiler-clean: $(MAKE) -C $(POCO_BASE)/PageCompiler clean $(MAKE) -C $(POCO_BASE)/PageCompiler/samples clean -PageCompiler/File2Page-libexec: Net-libexec Util-libexec XML-libexec Foundation-libexec +PageCompiler/File2Page-libexec: Net-libexec Util-libexec XML-libexec Foundation-libexec $(MAKE) -C $(POCO_BASE)/PageCompiler/File2Page PageCompiler/File2Page-clean: $(MAKE) -C $(POCO_BASE)/PageCompiler/File2Page clean -JWT-libexec: Foundation-libexec JSON-libexec Crypto-libexec +JWT-libexec: Foundation-libexec JSON-libexec Crypto-libexec $(MAKE) -C $(POCO_BASE)/JWT JWT-tests: JWT-libexec cppunit @@ -326,7 +326,7 @@ JWT-clean: $(MAKE) -C $(POCO_BASE)/JWT clean $(MAKE) -C $(POCO_BASE)/JWT/testsuite clean -CppParser-libexec: Foundation-libexec +CppParser-libexec: Foundation-libexec $(MAKE) -C $(POCO_BASE)/CppParser CppParser-tests: CppParser-libexec cppunit @@ -336,7 +336,7 @@ CppParser-clean: $(MAKE) -C $(POCO_BASE)/CppParser clean $(MAKE) -C $(POCO_BASE)/CppParser/testsuite clean -PDF-libexec: Util-libexec XML-libexec JSON-libexec Foundation-libexec +PDF-libexec: Util-libexec XML-libexec JSON-libexec Foundation-libexec $(MAKE) -C $(POCO_BASE)/PDF PDF-tests: PDF-libexec cppunit @@ -350,7 +350,7 @@ PDF-clean: $(MAKE) -C $(POCO_BASE)/PDF/testsuite clean $(MAKE) -C $(POCO_BASE)/PDF/samples clean -MongoDB-libexec: Foundation-libexec Net-libexec +MongoDB-libexec: Foundation-libexec Net-libexec $(MAKE) -C $(POCO_BASE)/MongoDB MongoDB-tests: MongoDB-libexec cppunit @@ -364,7 +364,7 @@ MongoDB-clean: $(MAKE) -C $(POCO_BASE)/MongoDB/testsuite clean $(MAKE) -C $(POCO_BASE)/MongoDB/samples clean -Redis-libexec: Foundation-libexec Net-libexec +Redis-libexec: Foundation-libexec Net-libexec $(MAKE) -C $(POCO_BASE)/Redis Redis-tests: Redis-libexec cppunit @@ -374,6 +374,22 @@ Redis-clean: $(MAKE) -C $(POCO_BASE)/Redis clean $(MAKE) -C $(POCO_BASE)/Redis/testsuite clean +ActiveRecord-libexec: Foundation-libexec Data-libexec + $(MAKE) -C $(POCO_BASE)/ActiveRecord + +ActiveRecord-tests: ActiveRecord-libexec Data/SQLite-libexec cppunit + $(MAKE) -C $(POCO_BASE)/ActiveRecord/testsuite + +ActiveRecord-clean: + $(MAKE) -C $(POCO_BASE)/ActiveRecord clean + $(MAKE) -C $(POCO_BASE)/ActiveRecord/testsuite clean + +ActiveRecord/Compiler-libexec: Foundation-libexec Util-libexec + $(MAKE) -C $(POCO_BASE)/ActiveRecord/Compiler + +ActiveRecord/Compiler-clean: + $(MAKE) -C $(POCO_BASE)/ActiveRecord/Compiler clean + clean: cleans CppUnit-clean distclean: diff --git a/components b/components index aba7ecea7..e89aca677 100644 --- a/components +++ b/components @@ -21,5 +21,6 @@ PDF CppParser MongoDB Redis +ActiveRecord PocoDoc ProGen