Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for container CRUD operations, tested. #10

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Support for container CRUD operations, tested.
- IntTest.cpp would not compile on windows without the #define NOMINMAX
- Usage for the Container CRUD operations is as follows.
 In the query where the expansion is needed, add %array%. This will the variables in the array like this ($1), ($2)...; the parenthesis are part of postgres syntax.
Now if the object in the array is complex and requires further expansion, then this syntax is used: %array :member1, :member2%. This will be expanded like this ($1,$2), ($3,$4)... where $1 and $2 point to array[i].member1  and array[i].member2 respectively.
For sample usage see the ContainerTest.cpp
Kraken committed Jul 3, 2021
commit 79a1e0dce44747fcd8c72a7cc068082324a2afd4
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -45,3 +45,5 @@ build/

**/.DS_Store

/.vs
/CMakePresets.json
68 changes: 68 additions & 0 deletions src/oatpp-postgresql/Executor.cpp
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@

#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
#include "oatpp/core/macro/codegen.hpp"
#include "oatpp/core/utils/ConversionUtils.hpp"

#include <vector>

@@ -261,6 +262,67 @@ std::shared_ptr<QueryResult> Executor::executeQuery(const StringTemplate& queryT

}

template<class ContainerPtr>
std::string expandQuery(ContainerPtr container,
const std::shared_ptr<ql_template::Parser::ArrayVariableExtra>& a_extra,
std::unordered_map<String,Void>& params,
orm::Executor::ParamsTypeMap& paramsTypeMap)
{
std::string values;
bool is_first = true;
int index = 0;
for(const Void& entity : *container)
{
values += std::string(is_first ? "" : ",") + "(";
is_first = true;
String entity_name = String("array_value") + utils::conversion::int32ToStr(index++);
if(a_extra->variables.empty())
{
values += std::string(is_first ? "" : ",") + ":" + entity_name->c_str();
is_first = false;
}
else{
for(const auto& member : a_extra->variables)
{
values += std::string(is_first ? "" : ",") + ":" + entity_name->c_str() + "." + member.name->c_str();
is_first = false;
}
}
params.insert({entity_name, entity});
paramsTypeMap.insert({entity_name, entity.valueType});
values += ")";
}
return values;
}

std::shared_ptr<QueryResult> Executor::expandAndExecuteQuery(const StringTemplate& queryTemplate,
const std::unordered_map<oatpp::String, oatpp::Void>& params,
const std::shared_ptr<const data::mapping::TypeResolver>& typeResolver,
const std::shared_ptr<postgresql::Connection>& connection)
{
const auto& extra = std::static_pointer_cast<ql_template::Parser::TemplateExtra>(queryTemplate.getExtraData());
const auto& var = queryTemplate.getTemplateVariables()[0];
const auto& it = params.at(var.name);
const auto& array_extra = std::static_pointer_cast<ql_template::Parser::ArrayVariableExtra>(var.extra);

std::unordered_map<String,Void> new_params;
orm::Executor::ParamsTypeMap paramsTypeMap;
std::string values;

if(it.valueType->classId.id == oatpp::Vector<Void>::Class::CLASS_ID.id)
values = expandQuery(it.staticCast<Vector<Void>>(), array_extra,new_params,paramsTypeMap);
else if(it.valueType->classId.id == oatpp::List<Void>::Class::CLASS_ID.id)
values = expandQuery(it.staticCast<List<Void>>(), array_extra,new_params,paramsTypeMap);
else if(it.valueType->classId.id == oatpp::UnorderedSet<Void>::Class::CLASS_ID.id)
values = expandQuery(it.staticCast<UnorderedSet<Void>>(), array_extra,new_params,paramsTypeMap);

const std::string& prepared_query = extra->preparedTemplate->std_str();
const String& query = (prepared_query.substr(0, var.posStart) + values + prepared_query.substr(var.posStart + 2)).c_str();
const auto& newQueryTemplate = parseQueryTemplate(extra->templateName + utils::conversion::uint64ToStr(new_params.size()), query, paramsTypeMap, extra->prepare);

return executeQuery(newQueryTemplate, new_params, typeResolver, connection);
}

data::share::StringTemplate Executor::parseQueryTemplate(const oatpp::String& name,
const oatpp::String& text,
const ParamsTypeMap& paramsTypeMap,
@@ -325,6 +387,12 @@ std::shared_ptr<orm::QueryResult> Executor::execute(const StringTemplate& queryT
return executeQueryPrepared(queryTemplate, params, tr, pgConnection);

}
if (queryTemplate.getTemplateVariables().size() == 1)
{
const auto& var = queryTemplate.getTemplateVariables()[0];
if (std::static_pointer_cast<ql_template::Parser::ArrayVariableExtra>(var.extra) != nullptr)
return expandAndExecuteQuery(queryTemplate,params,tr,pgConnection);
}

return executeQuery(queryTemplate, params, tr, pgConnection);

5 changes: 5 additions & 0 deletions src/oatpp-postgresql/Executor.hpp
Original file line number Diff line number Diff line change
@@ -108,6 +108,11 @@ class Executor : public orm::Executor {
const std::shared_ptr<const data::mapping::TypeResolver>& typeResolver,
const std::shared_ptr<postgresql::Connection>& connection);

std::shared_ptr<QueryResult> expandAndExecuteQuery(const StringTemplate& queryTemplate,
const std::unordered_map<oatpp::String, oatpp::Void>& params,
const std::shared_ptr<const data::mapping::TypeResolver>& typeResolver,
const std::shared_ptr<postgresql::Connection>& connection);

private:
std::shared_ptr<provider::Provider<Connection>> m_connectionProvider;
std::shared_ptr<mapping::ResultMapper> m_resultMapper;
42 changes: 41 additions & 1 deletion src/oatpp-postgresql/ql_template/Parser.cpp
Original file line number Diff line number Diff line change
@@ -126,6 +126,39 @@ data::share::StringTemplate::Variable Parser::parseIdentifier(parser::Caret& car
result.posEnd = caret.getPosition() - 1;
return result;
}
data::share::StringTemplate::Variable Parser::parseArrayIdentifier(parser::Caret& caret) {
data::share::StringTemplate::Variable result;
auto vars = std::make_shared<ArrayVariableExtra>();
result.posStart = caret.getPosition();
if(caret.canContinueAtChar('%', 1)) {
auto label = caret.putLabel();
while(caret.canContinue()) {
v_char8 a = *caret.getCurrData();
bool isAllowedChar = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || (a >= '0' && a <= '9') || (a == '_') || (a == '.');
if(!isAllowedChar) {
result.name = label.toString();
break;
}
caret.inc();
}
while(!caret.isAtChar('%')) {
if(caret.isAtChar(':'))
{
auto var = parseIdentifier(caret);
if(var.name) {
vars->variables.push_back(var);
}
}
caret.inc();
}
} else {
caret.setError("Invalid identifier");
}
result.extra = std::move(vars);
result.posEnd = caret.getPosition();
caret.inc();
return result;
}

void Parser::skipStringInQuotes(parser::Caret& caret) {

@@ -203,7 +236,14 @@ data::share::StringTemplate Parser::parseTemplate(const oatpp::String& text) {
}
}
break;

case '%':
{
auto var = parseArrayIdentifier(caret);
if(var.name) {
variables.push_back(var);
}
}
break;
case '\'': skipStringInQuotes(caret); break;
case '$': skipStringInDollars(caret); break;

5 changes: 5 additions & 0 deletions src/oatpp-postgresql/ql_template/Parser.hpp
Original file line number Diff line number Diff line change
@@ -65,6 +65,10 @@ class Parser {

};

struct ArrayVariableExtra {
std::vector<oatpp::data::share::StringTemplate::Variable> variables;
};

public:

struct CleanSection {
@@ -78,6 +82,7 @@ class Parser {

private:
static data::share::StringTemplate::Variable parseIdentifier(parser::Caret& caret);
static data::share::StringTemplate::Variable parseArrayIdentifier(parser::Caret& caret);
static void skipStringInQuotes(parser::Caret& caret);
static void skipStringInDollars(parser::Caret& caret);
public:
5 changes: 3 additions & 2 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -22,8 +22,9 @@ add_executable(module-tests
oatpp-postgresql/types/InterpretationTest.hpp
oatpp-postgresql/types/IntTest.cpp
oatpp-postgresql/types/IntTest.hpp
oatpp-postgresql/tests.cpp
)
oatpp-postgresql/types/ContainerTest.hpp
oatpp-postgresql/types/ContainerTest.cpp
oatpp-postgresql/tests.cpp)

set_target_properties(module-tests PROPERTIES
CXX_STANDARD 11
7 changes: 7 additions & 0 deletions test/oatpp-postgresql/migration/ContainerTest.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DROP TABLE IF EXISTS test_user;


CREATE TABLE test_user (
username text,
pass text
);
2 changes: 2 additions & 0 deletions test/oatpp-postgresql/tests.cpp
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
#include "types/ArrayTest.hpp"
#include "types/IntTest.hpp"
#include "types/FloatTest.hpp"
#include "types/ContainerTest.hpp"
#include "types/InterpretationTest.hpp"


@@ -39,6 +40,7 @@ void runTests() {
OATPP_RUN_TEST(oatpp::test::postgresql::types::IntTest);
OATPP_RUN_TEST(oatpp::test::postgresql::types::FloatTest);
OATPP_RUN_TEST(oatpp::test::postgresql::types::ArrayTest);
OATPP_RUN_TEST(oatpp::test::postgresql::types::ContainerTest);
OATPP_RUN_TEST(oatpp::test::postgresql::types::InterpretationTest);

}
179 changes: 179 additions & 0 deletions test/oatpp-postgresql/types/ContainerTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#include "ContainerTest.hpp"

#include "oatpp-postgresql/orm.hpp"
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
#include "oatpp/core/utils/ConversionUtils.hpp"

namespace oatpp { namespace test { namespace postgresql { namespace types {

namespace {

#include OATPP_CODEGEN_BEGIN(DTO)

class User : public oatpp::DTO {

DTO_INIT(User, DTO)

DTO_FIELD(String, username);
DTO_FIELD(String, pass);

};

#include OATPP_CODEGEN_END(DTO)

bool operator==(const Object<User>& us1, const Object<User>& us2)
{
return us1->username == us2->username && us1->pass == us2->pass;
}

#include OATPP_CODEGEN_BEGIN(DbClient)

class MyClient : public oatpp::orm::DbClient {
public:

MyClient(const std::shared_ptr<oatpp::orm::Executor>& executor)
: oatpp::orm::DbClient(executor)
{

executeQuery("DROP TABLE IF EXISTS oatpp_schema_version_ContainerTest;", {});

oatpp::orm::SchemaMigration migration(executor, "ContainerTest");
migration.addFile(1, TEST_DB_MIGRATION "ContainerTest.sql");
migration.migrate();

auto version = executor->getSchemaVersion("ContainerTest");
OATPP_LOGD("DbClient", "Migration - OK. Version=%d.", version);

}

QUERY(insert_all,
"INSERT INTO test_user (username,pass) VALUES"
" %users (:username, :pass)% RETURNING *;",
PARAM(oatpp::Vector<oatpp::Object<User>>, users), PREPARE(false));

QUERY(update_all,
"UPDATE test_user as target SET pass=source.pass FROM (VALUES %users (:username, :pass)%) "
"as source(username, pass) WHERE target.username = source.username RETURNING target.*;",
PARAM(oatpp::Vector<oatpp::Object<User>>, users), PREPARE(false));

QUERY(find_all,
"SELECT * FROM test_user WHERE username IN (%users%)",
PARAM(oatpp::Vector<String>, users), PREPARE(false));

QUERY(delete_all,
"DELETE FROM test_user WHERE username IN (%users%)",
PARAM(oatpp::Vector<String>, users), PREPARE(false));

};

#include OATPP_CODEGEN_END(DbClient)

}

void ContainerTest::onRun() {

OATPP_LOGI(TAG, "DB-URL='%s'", TEST_DB_URL);

auto connectionProvider = std::make_shared<oatpp::postgresql::ConnectionProvider>(TEST_DB_URL);
const auto& executor = std::make_shared<oatpp::postgresql::Executor>(connectionProvider);

auto client = MyClient(executor);

{
auto users = Vector<Object<User>>::createShared();
for(size_t i = 0; i < 3; ++i)
{
auto o = Object<User>::createShared();
o->username = "test_" + utils::conversion::uint64ToStr(i);
o->pass = "pass_" + utils::conversion::uint64ToStr(i);
users->push_back(o);
}
auto res = client.insert_all(users);
OATPP_ASSERT(res->isSuccess())
const auto& data = res->fetch<Vector<Object<User>>>();
for(size_t i = 0; i < users->size(); ++i)
{
OATPP_ASSERT(users[i] == data[i])
}
client.executeQuery("DELETE FROM test_user",{});
}

{
auto users = Vector<Object<User>>::createShared();
for(size_t i = 0; i < 3; ++i)
{
auto o = Object<User>::createShared();
o->username = "test_" + utils::conversion::uint64ToStr(i);
o->pass = "pass_" + utils::conversion::uint64ToStr(i);
users->push_back(o);
}
auto res = client.insert_all(users);
OATPP_ASSERT(res->isSuccess())

for(size_t i = 0; i < users->size(); ++i)
{
users[i]->pass = "changed_pass_" + utils::conversion::uint64ToStr(i);
}
res = client.update_all(users);
OATPP_ASSERT(res->isSuccess())
const auto& data = res->fetch<Vector<Object<User>>>();
for(size_t i = 0; i < users->size(); ++i)
{
OATPP_ASSERT(users[i] == data[i])
}
client.executeQuery("DELETE FROM test_user",{});
}

{
auto users = Vector<Object<User>>::createShared();
for(size_t i = 0; i < 3; ++i)
{
auto o = Object<User>::createShared();
o->username = "test_" + utils::conversion::uint64ToStr(i);
o->pass = "pass_" + utils::conversion::uint64ToStr(i);
users->push_back(o);
}
auto res = client.insert_all(users);
OATPP_ASSERT(res->isSuccess())

auto usernames = Vector<String>::createShared();
for(size_t i = 0; i < users->size(); ++i)
{
usernames->push_back(users[i]->username);
}
res = client.find_all(usernames);
OATPP_ASSERT(res->isSuccess())
const auto& data = res->fetch<Vector<Object<User>>>();
for(size_t i = 0; i < users->size(); ++i)
{
OATPP_ASSERT(users[i] == data[i])
}
client.executeQuery("DELETE FROM test_user",{});
}

{
auto users = Vector<Object<User>>::createShared();
for(size_t i = 0; i < 3; ++i)
{
auto o = Object<User>::createShared();
o->username = "test_" + utils::conversion::uint64ToStr(i);
o->pass = "pass_" + utils::conversion::uint64ToStr(i);
users->push_back(o);
}
auto res = client.insert_all(users);
OATPP_ASSERT(res->isSuccess())

auto usernames = Vector<String>::createShared();
for(size_t i = 0; i < users->size(); ++i)
{
usernames->push_back(users[i]->username);
}
res = client.delete_all(usernames);
OATPP_ASSERT(res->isSuccess())
const auto& data = res->fetch<Vector<Object<User>>>();
OATPP_ASSERT(data->empty());
}

}

}}}}
16 changes: 16 additions & 0 deletions test/oatpp-postgresql/types/ContainerTest.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef oatpp_test_postgresql_types_ContainerTest_hpp
#define oatpp_test_postgresql_types_ContainerTest_hpp

#include "oatpp-test/UnitTest.hpp"

namespace oatpp { namespace test { namespace postgresql { namespace types {

class ContainerTest : public UnitTest {
public:
ContainerTest() : UnitTest("TEST[postgresql::types::ContainerTest]") {}
void onRun() override;
};

}}}}

#endif // oatpp_test_postgresql_types_ContainerTest_hpp
2 changes: 1 addition & 1 deletion test/oatpp-postgresql/types/IntTest.cpp
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
* limitations under the License.
*
***************************************************************************/

#define NOMINMAX
#include "IntTest.hpp"

#include "oatpp-postgresql/orm.hpp"