LCOV - code coverage report
Current view: top level - irohad/ametsuchi/impl - postgres_query_executor.cpp (source / functions) Hit Total Coverage
Test: coverage_cleared.info Lines: 335 358 93.6 %
Date: 2018-12-05 17:11:35 Functions: 155 155 100.0 %

          Line data    Source code
       1             : /**
       2             :  * Copyright Soramitsu Co., Ltd. All Rights Reserved.
       3             :  * SPDX-License-Identifier: Apache-2.0
       4             :  */
       5             : 
       6             : #include "ametsuchi/impl/postgres_query_executor.hpp"
       7             : 
       8             : #include <boost-tuple.h>
       9             : #include <soci/boost-tuple.h>
      10             : #include <soci/postgresql/soci-postgresql.h>
      11             : #include <boost/algorithm/string.hpp>
      12             : #include <boost/format.hpp>
      13             : #include <boost/range/adaptor/filtered.hpp>
      14             : #include <boost/range/adaptor/transformed.hpp>
      15             : #include <boost/range/algorithm/for_each.hpp>
      16             : #include <boost/range/algorithm/transform.hpp>
      17             : #include <boost/range/irange.hpp>
      18             : 
      19             : #include "ametsuchi/impl/soci_utils.hpp"
      20             : #include "common/byteutils.hpp"
      21             : #include "cryptography/public_key.hpp"
      22             : #include "interfaces/queries/blocks_query.hpp"
      23             : #include "interfaces/queries/get_account.hpp"
      24             : #include "interfaces/queries/get_account_asset_transactions.hpp"
      25             : #include "interfaces/queries/get_account_assets.hpp"
      26             : #include "interfaces/queries/get_account_detail.hpp"
      27             : #include "interfaces/queries/get_account_transactions.hpp"
      28             : #include "interfaces/queries/get_asset_info.hpp"
      29             : #include "interfaces/queries/get_pending_transactions.hpp"
      30             : #include "interfaces/queries/get_role_permissions.hpp"
      31             : #include "interfaces/queries/get_roles.hpp"
      32             : #include "interfaces/queries/get_signatories.hpp"
      33             : #include "interfaces/queries/get_transactions.hpp"
      34             : #include "interfaces/queries/query.hpp"
      35             : 
      36             : using namespace shared_model::interface::permissions;
      37             : 
      38             : namespace {
      39             : 
      40             :   using namespace iroha;
      41             : 
      42             :   shared_model::interface::types::DomainIdType getDomainFromName(
      43             :       const shared_model::interface::types::AccountIdType &account_id) {
      44             :     // TODO 03.10.18 andrei: IR-1728 Move getDomainFromName to shared_model
      45         224 :     std::vector<std::string> res;
      46         224 :     boost::split(res, account_id, boost::is_any_of("@"));
      47         224 :     return res.at(1);
      48         224 :   }
      49             : 
      50             :   std::string checkAccountRolePermission(
      51             :       shared_model::interface::permissions::Role permission,
      52             :       const std::string &account_alias = "role_account_id") {
      53             :     const auto perm_str =
      54          85 :         shared_model::interface::RolePermissionSet({permission}).toBitstring();
      55          85 :     const auto bits = shared_model::interface::RolePermissionSet::size();
      56             :     // TODO 14.09.18 andrei: IR-1708 Load SQL from separate files
      57          88 :     std::string query = (boost::format(R"(
      58             :           SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
      59             :           & '%2%') = '%2%' AS perm FROM role_has_permissions AS rp
      60             :               JOIN account_has_roles AS ar on ar.role_id = rp.role_id
      61             :               WHERE ar.account_id = :%3%)")
      62          86 :                          % bits % perm_str % account_alias)
      63          87 :                             .str();
      64          88 :     return query;
      65          88 :   }
      66             : 
      67             :   /**
      68             :    * Generate an SQL subquery which checks if creator has corresponding
      69             :    * permissions for target account
      70             :    * It verifies individual, domain, and global permissions, and returns true if
      71             :    * any of listed permissions is present
      72             :    */
      73             :   auto hasQueryPermission(
      74             :       const shared_model::interface::types::AccountIdType &creator,
      75             :       const shared_model::interface::types::AccountIdType &target_account,
      76             :       Role indiv_permission_id,
      77             :       Role all_permission_id,
      78             :       Role domain_permission_id) {
      79         112 :     const auto bits = shared_model::interface::RolePermissionSet::size();
      80             :     const auto perm_str =
      81         112 :         shared_model::interface::RolePermissionSet({indiv_permission_id})
      82             :             .toBitstring();
      83             :     const auto all_perm_str =
      84         112 :         shared_model::interface::RolePermissionSet({all_permission_id})
      85         112 :             .toBitstring();
      86             :     const auto domain_perm_str =
      87         112 :         shared_model::interface::RolePermissionSet({domain_permission_id})
      88         112 :             .toBitstring();
      89             : 
      90         112 :     boost::format cmd(R"(
      91             :     WITH
      92             :         has_indiv_perm AS (
      93             :           SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
      94             :           & '%3%') = '%3%' FROM role_has_permissions AS rp
      95             :               JOIN account_has_roles AS ar on ar.role_id = rp.role_id
      96             :               WHERE ar.account_id = '%2%'
      97             :         ),
      98             :         has_all_perm AS (
      99             :           SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
     100             :           & '%4%') = '%4%' FROM role_has_permissions AS rp
     101             :               JOIN account_has_roles AS ar on ar.role_id = rp.role_id
     102             :               WHERE ar.account_id = '%2%'
     103             :         ),
     104             :         has_domain_perm AS (
     105             :           SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
     106             :           & '%5%') = '%5%' FROM role_has_permissions AS rp
     107             :               JOIN account_has_roles AS ar on ar.role_id = rp.role_id
     108             :               WHERE ar.account_id = '%2%'
     109             :         )
     110             :     SELECT ('%2%' = '%6%' AND (SELECT * FROM has_indiv_perm))
     111             :         OR (SELECT * FROM has_all_perm)
     112             :         OR ('%7%' = '%8%' AND (SELECT * FROM has_domain_perm)) AS perm
     113             :     )");
     114             : 
     115         112 :     return (cmd % bits % creator % perm_str % all_perm_str % domain_perm_str
     116         112 :             % target_account % getDomainFromName(creator)
     117         112 :             % getDomainFromName(target_account))
     118         112 :         .str();
     119         112 :   }
     120             : 
     121             :   /// Query result is a tuple of optionals, since there could be no entry
     122             :   template <typename... Value>
     123             :   using QueryType = boost::tuple<boost::optional<Value>...>;
     124             : 
     125             :   /**
     126             :    * Create an error response in case user does not have permissions to perform
     127             :    * a query
     128             :    * @tparam Roles - type of roles
     129             :    * @param roles, which user lacks
     130             :    * @return lambda returning the error response itself
     131             :    */
     132             :   template <typename... Roles>
     133             :   auto notEnoughPermissionsResponse(
     134             :       std::shared_ptr<shared_model::interface::PermissionToString>
     135             :           perm_converter,
     136             :       Roles... roles) {
     137             :     return [perm_converter, roles...] {
     138          43 :       std::string error = "user must have at least one of the permissions: ";
     139         172 :       for (auto role : {roles...}) {
     140         129 :         error += perm_converter->toString(role) + ", ";
     141             :       }
     142          43 :       return error;
     143          43 :     };
     144             :   }
     145             : 
     146             : }  // namespace
     147             : 
     148             : namespace iroha {
     149             :   namespace ametsuchi {
     150             : 
     151             :     template <typename RangeGen, typename Pred>
     152             :     std::vector<std::unique_ptr<shared_model::interface::Transaction>>
     153             :     PostgresQueryExecutorVisitor::getTransactionsFromBlock(uint64_t block_id,
     154             :                                                            RangeGen &&range_gen,
     155             :                                                            Pred &&pred) {
     156          30 :       std::vector<std::unique_ptr<shared_model::interface::Transaction>> result;
     157          30 :       auto serialized_block = block_store_.get(block_id);
     158          33 :       if (not serialized_block) {
     159           0 :         log_->error("Failed to retrieve block with id {}", block_id);
     160           0 :         return result;
     161             :       }
     162             :       auto deserialized_block =
     163          33 :           converter_->deserialize(bytesToString(*serialized_block));
     164             :       // boost::get of pointer returns pointer to requested type, or nullptr
     165          33 :       if (auto e =
     166          33 :               boost::get<expected::Error<std::string>>(&deserialized_block)) {
     167           0 :         log_->error(e->error);
     168           0 :         return result;
     169             :       }
     170             : 
     171          33 :       auto &block =
     172          33 :           boost::get<
     173             :               expected::Value<std::unique_ptr<shared_model::interface::Block>>>(
     174          33 :               deserialized_block)
     175          33 :               .value;
     176             : 
     177          33 :       boost::transform(range_gen(boost::size(block->transactions()))
     178          33 :                            | boost::adaptors::transformed(
     179             :                                  [&block](auto i) -> decltype(auto) {
     180          69 :                                    return block->transactions()[i];
     181           0 :                                  })
     182          33 :                            | boost::adaptors::filtered(pred),
     183          33 :                        std::back_inserter(result),
     184             :                        [&](const auto &tx) { return clone(tx); });
     185             : 
     186          32 :       return result;
     187          33 :     }
     188             : 
     189             :     template <typename QueryTuple,
     190             :               typename PermissionTuple,
     191             :               typename QueryExecutor,
     192             :               typename ResponseCreator,
     193             :               typename ErrResponse>
     194             :     QueryExecutorResult PostgresQueryExecutorVisitor::executeQuery(
     195             :         QueryExecutor &&query_executor,
     196             :         ResponseCreator &&response_creator,
     197             :         ErrResponse &&err_response) {
     198             :       using T = concat<QueryTuple, PermissionTuple>;
     199             :       try {
     200          34 :         soci::rowset<T> st = std::forward<QueryExecutor>(query_executor)();
     201          34 :         auto range = boost::make_iterator_range(st.begin(), st.end());
     202             : 
     203          28 :         return apply(
     204          34 :             viewPermissions<PermissionTuple>(range.front()),
     205             :             [this, range, &response_creator, &err_response](auto... perms) {
     206          34 :               bool temp[] = {not perms...};
     207             :               if (std::all_of(std::begin(temp), std::end(temp), [](auto b) {
     208          37 :                     return b;
     209             :                   })) {
     210           8 :                 return this->logAndReturnErrorResponse(
     211             :                     QueryErrorType::kStatefulFailed,
     212           8 :                     std::forward<ErrResponse>(err_response)());
     213             :               }
     214          33 :               auto query_range = range
     215             :                   | boost::adaptors::transformed([](auto &t) {
     216          89 :                                    return rebind(viewQuery<QueryTuple>(t));
     217           0 :                                  })
     218             :                   | boost::adaptors::filtered([](const auto &t) {
     219          45 :                                    return static_cast<bool>(t);
     220             :                                  })
     221             :                   | boost::adaptors::transformed([](auto t) { return *t; });
     222          33 :               return std::forward<ResponseCreator>(response_creator)(
     223          33 :                   query_range, perms...);
     224          34 :             });
     225          34 :       } catch (const std::exception &e) {
     226           0 :         return logAndReturnErrorResponse(QueryErrorType::kStatefulFailed,
     227           0 :                                          e.what());
     228           0 :       }
     229          34 :     }
     230             : 
     231             :     PostgresQueryExecutor::PostgresQueryExecutor(
     232             :         std::unique_ptr<soci::session> sql,
     233             :         KeyValueStorage &block_store,
     234             :         std::shared_ptr<PendingTransactionStorage> pending_txs_storage,
     235             :         std::shared_ptr<shared_model::interface::BlockJsonConverter> converter,
     236             :         std::shared_ptr<shared_model::interface::QueryResponseFactory>
     237             :             response_factory,
     238             :         std::shared_ptr<shared_model::interface::PermissionToString>
     239             :             perm_converter)
     240         171 :         : sql_(std::move(sql)),
     241         171 :           block_store_(block_store),
     242         171 :           pending_txs_storage_(std::move(pending_txs_storage)),
     243         171 :           visitor_(*sql_,
     244         170 :                    block_store_,
     245         170 :                    pending_txs_storage_,
     246         170 :                    std::move(converter),
     247         170 :                    response_factory,
     248         170 :                    perm_converter),
     249         171 :           query_response_factory_{std::move(response_factory)},
     250         171 :           log_(logger::log("PostgresQueryExecutor")) {}
     251             : 
     252             :     QueryExecutorResult PostgresQueryExecutor::validateAndExecute(
     253             :         const shared_model::interface::Query &query) {
     254         169 :       visitor_.setCreatorId(query.creatorAccountId());
     255         169 :       visitor_.setQueryHash(query.hash());
     256         169 :       return boost::apply_visitor(visitor_, query.get());
     257             :     }
     258             : 
     259             :     bool PostgresQueryExecutor::validate(
     260             :         const shared_model::interface::BlocksQuery &query) {
     261             :       using T = boost::tuple<int>;
     262           2 :       boost::format cmd(R"(%s)");
     263             :       try {
     264           2 :         soci::rowset<T> st =
     265           2 :             (sql_->prepare
     266           2 :                  << (cmd % checkAccountRolePermission(Role::kGetBlocks)).str(),
     267           2 :              soci::use(query.creatorAccountId(), "role_account_id"));
     268             : 
     269           2 :         return st.begin()->get<0>();
     270           2 :       } catch (const std::exception &e) {
     271           0 :         log_->error("Failed to validate query: {}", e.what());
     272           0 :         return false;
     273           0 :       }
     274           2 :     }
     275             : 
     276             :     PostgresQueryExecutorVisitor::PostgresQueryExecutorVisitor(
     277             :         soci::session &sql,
     278             :         KeyValueStorage &block_store,
     279             :         std::shared_ptr<PendingTransactionStorage> pending_txs_storage,
     280             :         std::shared_ptr<shared_model::interface::BlockJsonConverter> converter,
     281             :         std::shared_ptr<shared_model::interface::QueryResponseFactory>
     282             :             response_factory,
     283             :         std::shared_ptr<shared_model::interface::PermissionToString>
     284             :             perm_converter)
     285         170 :         : sql_(sql),
     286         170 :           block_store_(block_store),
     287         171 :           pending_txs_storage_(std::move(pending_txs_storage)),
     288         171 :           converter_(std::move(converter)),
     289         171 :           query_response_factory_{std::move(response_factory)},
     290         171 :           perm_converter_(std::move(perm_converter)),
     291         171 :           log_(logger::log("PostgresQueryExecutorVisitor")) {}
     292             : 
     293             :     void PostgresQueryExecutorVisitor::setCreatorId(
     294             :         const shared_model::interface::types::AccountIdType &creator_id) {
     295         169 :       creator_id_ = creator_id;
     296         169 :     }
     297             : 
     298             :     void PostgresQueryExecutorVisitor::setQueryHash(
     299             :         const shared_model::interface::types::HashType &query_hash) {
     300         169 :       query_hash_ = query_hash;
     301         169 :     }
     302             : 
     303             :     std::unique_ptr<shared_model::interface::QueryResponse>
     304             :     PostgresQueryExecutorVisitor::logAndReturnErrorResponse(
     305             :         iroha::ametsuchi::QueryErrorType error_type,
     306             :         std::string error_body) const {
     307             :       using QueryErrorType = iroha::ametsuchi::QueryErrorType;
     308             : 
     309             :       auto make_error_response = [this, error_type](std::string error) {
     310          56 :         return query_response_factory_->createErrorQueryResponse(
     311          56 :             error_type, error, query_hash_);
     312           0 :       };
     313             : 
     314          56 :       std::string error;
     315          56 :       switch (error_type) {
     316             :         case QueryErrorType::kNoAccount:
     317           1 :           error = "could find account with such id: " + error_body;
     318           1 :           break;
     319             :         case QueryErrorType::kNoSignatories:
     320           1 :           error = "no signatories found in account with such id: " + error_body;
     321           1 :           break;
     322             :         case QueryErrorType::kNoAccountDetail:
     323           1 :           error = "no details in account with such id: " + error_body;
     324           1 :           break;
     325             :         case QueryErrorType::kNoRoles:
     326           1 :           error =
     327           1 :               "no role with such name in account with such id: " + error_body;
     328           1 :           break;
     329             :         case QueryErrorType::kNoAsset:
     330           2 :           error =
     331           2 :               "no asset with such name in account with such id: " + error_body;
     332           2 :           break;
     333             :           // other error are either handled by generic response or do not appear
     334             :           // yet
     335             :         default:
     336          50 :           error = "failed to execute query: " + error_body;
     337          50 :           break;
     338             :       }
     339             : 
     340          56 :       log_->error(error);
     341          56 :       return make_error_response(error);
     342          56 :     }
     343             : 
     344             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     345             :         const shared_model::interface::GetAccount &q) {
     346             :       using QueryTuple =
     347             :           QueryType<shared_model::interface::types::AccountIdType,
     348             :                     shared_model::interface::types::DomainIdType,
     349             :                     shared_model::interface::types::QuorumType,
     350             :                     shared_model::interface::types::DetailType,
     351             :                     std::string>;
     352             :       using PermissionTuple = boost::tuple<int>;
     353             : 
     354          19 :       auto cmd = (boost::format(R"(WITH has_perms AS (%s),
     355             :       t AS (
     356             :           SELECT a.account_id, a.domain_id, a.quorum, a.data, ARRAY_AGG(ar.role_id) AS roles
     357             :           FROM account AS a, account_has_roles AS ar
     358             :           WHERE a.account_id = :target_account_id
     359             :           AND ar.account_id = a.account_id
     360             :           GROUP BY a.account_id
     361             :       )
     362             :       SELECT account_id, domain_id, quorum, data, roles, perm
     363             :       FROM t RIGHT OUTER JOIN has_perms AS p ON TRUE
     364             :       )")
     365          19 :                   % hasQueryPermission(creator_id_,
     366          19 :                                        q.accountId(),
     367             :                                        Role::kGetMyAccount,
     368             :                                        Role::kGetAllAccounts,
     369             :                                        Role::kGetDomainAccounts))
     370          19 :                      .str();
     371             : 
     372             :       auto query_apply = [this](auto &account_id,
     373             :                                 auto &domain_id,
     374             :                                 auto &quorum,
     375             :                                 auto &data,
     376             :                                 auto &roles_str) {
     377          10 :         std::vector<shared_model::interface::types::RoleIdType> roles;
     378          10 :         auto roles_str_no_brackets = roles_str.substr(1, roles_str.size() - 2);
     379          10 :         boost::split(
     380             :             roles, roles_str_no_brackets, [](char c) { return c == ','; });
     381          10 :         return query_response_factory_->createAccountResponse(
     382          10 :             account_id, domain_id, quorum, data, std::move(roles), query_hash_);
     383          10 :       };
     384             : 
     385          19 :       return executeQuery<QueryTuple, PermissionTuple>(
     386             :           [&] {
     387          19 :             return (sql_.prepare << cmd,
     388          19 :                     soci::use(q.accountId(), "target_account_id"));
     389           0 :           },
     390             :           [this, &q, &query_apply](auto range, auto &) {
     391          11 :             if (range.empty()) {
     392           1 :               return this->logAndReturnErrorResponse(QueryErrorType::kNoAccount,
     393           1 :                                                      q.accountId());
     394             :             }
     395             : 
     396          10 :             return apply(range.front(), query_apply);
     397          11 :           },
     398          19 :           notEnoughPermissionsResponse(perm_converter_,
     399             :                                        Role::kGetMyAccount,
     400             :                                        Role::kGetAllAccounts,
     401             :                                        Role::kGetDomainAccounts));
     402          19 :     }
     403             : 
     404             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     405             :         const shared_model::interface::GetSignatories &q) {
     406             :       using QueryTuple = QueryType<std::string>;
     407             :       using PermissionTuple = boost::tuple<int>;
     408             : 
     409          20 :       auto cmd = (boost::format(R"(WITH has_perms AS (%s),
     410             :       t AS (
     411             :           SELECT public_key FROM account_has_signatory
     412             :           WHERE account_id = :account_id
     413             :       )
     414             :       SELECT public_key, perm FROM t
     415             :       RIGHT OUTER JOIN has_perms ON TRUE
     416             :       )")
     417          20 :                   % hasQueryPermission(creator_id_,
     418          20 :                                        q.accountId(),
     419             :                                        Role::kGetMySignatories,
     420             :                                        Role::kGetAllSignatories,
     421             :                                        Role::kGetDomainSignatories))
     422          20 :                      .str();
     423             : 
     424          20 :       return executeQuery<QueryTuple, PermissionTuple>(
     425             :           [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
     426             :           [this, &q](auto range, auto &) {
     427          13 :             if (range.empty()) {
     428           1 :               return this->logAndReturnErrorResponse(
     429           1 :                   QueryErrorType::kNoSignatories, q.accountId());
     430             :             }
     431             : 
     432          12 :             auto pubkeys = boost::copy_range<
     433             :                 std::vector<shared_model::interface::types::PubkeyType>>(
     434             :                 range | boost::adaptors::transformed([](auto t) {
     435             :                   return apply(t, [&](auto &public_key) {
     436          44 :                     return shared_model::interface::types::PubkeyType{
     437          44 :                         shared_model::crypto::Blob::fromHexString(public_key)};
     438           0 :                   });
     439             :                 }));
     440             : 
     441          12 :             return query_response_factory_->createSignatoriesResponse(
     442          12 :                 pubkeys, query_hash_);
     443          13 :           },
     444          20 :           notEnoughPermissionsResponse(perm_converter_,
     445             :                                        Role::kGetMySignatories,
     446             :                                        Role::kGetAllSignatories,
     447             :                                        Role::kGetDomainSignatories));
     448          20 :     }
     449             : 
     450             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     451             :         const shared_model::interface::GetAccountTransactions &q) {
     452             :       using QueryTuple =
     453             :           QueryType<shared_model::interface::types::HeightType, uint64_t>;
     454             :       using PermissionTuple = boost::tuple<int>;
     455             : 
     456          17 :       auto cmd = (boost::format(R"(WITH has_perms AS (%s),
     457             :       t AS (
     458             :           SELECT DISTINCT has.height, index
     459             :           FROM height_by_account_set AS has
     460             :           JOIN index_by_creator_height AS ich ON has.height = ich.height
     461             :           AND has.account_id = ich.creator_id
     462             :           WHERE account_id = :account_id
     463             :           ORDER BY has.height, index ASC
     464             :       )
     465             :       SELECT height, index, perm FROM t
     466             :       RIGHT OUTER JOIN has_perms ON TRUE
     467             :       )")
     468          17 :                   % hasQueryPermission(creator_id_,
     469          17 :                                        q.accountId(),
     470             :                                        Role::kGetMyAccTxs,
     471             :                                        Role::kGetAllAccTxs,
     472             :                                        Role::kGetDomainAccTxs))
     473          17 :                      .str();
     474             : 
     475          17 :       return executeQuery<QueryTuple, PermissionTuple>(
     476             :           [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
     477             :           [&](auto range, auto &) {
     478           9 :             std::map<uint64_t, std::vector<uint64_t>> index;
     479             :             boost::for_each(range, [&index](auto t) {
     480             :               apply(t, [&index](auto &height, auto &idx) {
     481          25 :                 index[height].push_back(idx);
     482          25 :               });
     483          25 :             });
     484             : 
     485             :             std::vector<std::unique_ptr<shared_model::interface::Transaction>>
     486           9 :                 response_txs;
     487          33 :             for (auto &block : index) {
     488          24 :               auto txs = this->getTransactionsFromBlock(
     489          24 :                   block.first,
     490             :                   [&block](auto) { return block.second; },
     491             :                   [](auto &) { return true; });
     492          24 :               std::move(
     493          24 :                   txs.begin(), txs.end(), std::back_inserter(response_txs));
     494          24 :             }
     495             : 
     496           9 :             return query_response_factory_->createTransactionsResponse(
     497           9 :                 std::move(response_txs), query_hash_);
     498           9 :           },
     499          17 :           notEnoughPermissionsResponse(perm_converter_,
     500             :                                        Role::kGetMyAccTxs,
     501             :                                        Role::kGetAllAccTxs,
     502             :                                        Role::kGetDomainAccTxs));
     503          17 :     }
     504             : 
     505             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     506             :         const shared_model::interface::GetTransactions &q) {
     507             :       auto escape = [](auto &hash) { return "'" + hash.hex() + "'"; };
     508          33 :       std::string hash_str = std::accumulate(
     509          33 :           std::next(q.transactionHashes().begin()),
     510          33 :           q.transactionHashes().end(),
     511          33 :           escape(q.transactionHashes().front()),
     512             :           [&escape](auto &acc, auto &val) { return acc + "," + escape(val); });
     513             : 
     514             :       using QueryTuple =
     515             :           QueryType<shared_model::interface::types::HeightType, std::string>;
     516             :       using PermissionTuple = boost::tuple<int, int>;
     517             : 
     518          34 :       auto cmd = (boost::format(R"(WITH has_my_perm AS (%s),
     519             :       has_all_perm AS (%s),
     520             :       t AS (
     521             :           SELECT height, hash FROM height_by_hash WHERE hash IN (%s)
     522             :       )
     523             :       SELECT height, hash, has_my_perm.perm, has_all_perm.perm FROM t
     524             :       RIGHT OUTER JOIN has_my_perm ON TRUE
     525             :       RIGHT OUTER JOIN has_all_perm ON TRUE
     526          34 :       )") % checkAccountRolePermission(Role::kGetMyTxs, "account_id")
     527          34 :                   % checkAccountRolePermission(Role::kGetAllTxs, "account_id")
     528          34 :                   % hash_str)
     529          34 :                      .str();
     530             : 
     531          34 :       return executeQuery<QueryTuple, PermissionTuple>(
     532             :           [&] {
     533          34 :             return (sql_.prepare << cmd, soci::use(creator_id_, "account_id"));
     534           0 :           },
     535             :           [&](auto range, auto &my_perm, auto &all_perm) {
     536          32 :             std::map<uint64_t, std::unordered_set<std::string>> index;
     537             :             boost::for_each(range, [&index](auto t) {
     538             :               apply(t, [&index](auto &height, auto &hash) {
     539          34 :                 index[height].insert(hash);
     540          34 :               });
     541          34 :             });
     542             : 
     543             :             std::vector<std::unique_ptr<shared_model::interface::Transaction>>
     544          33 :                 response_txs;
     545          66 :             for (auto &block : index) {
     546          26 :               auto txs = this->getTransactionsFromBlock(
     547          26 :                   block.first,
     548             :                   [](auto size) {
     549          33 :                     return boost::irange(static_cast<decltype(size)>(0), size);
     550             :                   },
     551             :                   [&](auto &tx) {
     552          37 :                     return block.second.count(tx.hash().hex()) > 0
     553          37 :                         and (all_perm
     554          34 :                              or (my_perm
     555          30 :                                  and tx.creatorAccountId() == creator_id_));
     556             :                   });
     557          33 :               std::move(
     558          33 :                   txs.begin(), txs.end(), std::back_inserter(response_txs));
     559          33 :             }
     560             : 
     561          33 :             return query_response_factory_->createTransactionsResponse(
     562          33 :                 std::move(response_txs), query_hash_);
     563          33 :           },
     564          34 :           notEnoughPermissionsResponse(
     565          34 :               perm_converter_, Role::kGetMyTxs, Role::kGetAllTxs));
     566          34 :     }
     567             : 
     568             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     569             :         const shared_model::interface::GetAccountAssetTransactions &q) {
     570             :       using QueryTuple =
     571             :           QueryType<shared_model::interface::types::HeightType, uint64_t>;
     572             :       using PermissionTuple = boost::tuple<int>;
     573             : 
     574          15 :       auto cmd = (boost::format(R"(WITH has_perms AS (%s),
     575             :       t AS (
     576             :           SELECT DISTINCT has.height, index
     577             :           FROM height_by_account_set AS has
     578             :           JOIN index_by_id_height_asset AS ich ON has.height = ich.height
     579             :           AND has.account_id = ich.id
     580             :           WHERE account_id = :account_id
     581             :           AND asset_id = :asset_id
     582             :           ORDER BY has.height, index ASC
     583             :       )
     584             :       SELECT height, index, perm FROM t
     585             :       RIGHT OUTER JOIN has_perms ON TRUE
     586             :       )")
     587          15 :                   % hasQueryPermission(creator_id_,
     588          15 :                                        q.accountId(),
     589             :                                        Role::kGetMyAccAstTxs,
     590             :                                        Role::kGetAllAccAstTxs,
     591             :                                        Role::kGetDomainAccAstTxs))
     592          15 :                      .str();
     593             : 
     594          15 :       return executeQuery<QueryTuple, PermissionTuple>(
     595             :           [&] {
     596          15 :             return (sql_.prepare << cmd,
     597          15 :                     soci::use(q.accountId(), "account_id"),
     598          15 :                     soci::use(q.assetId(), "asset_id"));
     599           0 :           },
     600             :           [&](auto range, auto &) {
     601           9 :             std::map<uint64_t, std::vector<uint64_t>> index;
     602             :             boost::for_each(range, [&index](auto t) {
     603             :               apply(t, [&index](auto &height, auto &idx) {
     604          18 :                 index[height].push_back(idx);
     605          18 :               });
     606          18 :             });
     607             : 
     608             :             std::vector<std::unique_ptr<shared_model::interface::Transaction>>
     609           9 :                 response_txs;
     610          27 :             for (auto &block : index) {
     611          18 :               auto txs = this->getTransactionsFromBlock(
     612          18 :                   block.first,
     613             :                   [&block](auto) { return block.second; },
     614             :                   [](auto &) { return true; });
     615          18 :               std::move(
     616          18 :                   txs.begin(), txs.end(), std::back_inserter(response_txs));
     617          18 :             }
     618             : 
     619           9 :             return query_response_factory_->createTransactionsResponse(
     620           9 :                 std::move(response_txs), query_hash_);
     621           9 :           },
     622          15 :           notEnoughPermissionsResponse(perm_converter_,
     623             :                                        Role::kGetMyAccAstTxs,
     624             :                                        Role::kGetAllAccAstTxs,
     625             :                                        Role::kGetDomainAccAstTxs));
     626          15 :     }
     627             : 
     628             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     629             :         const shared_model::interface::GetAccountAssets &q) {
     630             :       using QueryTuple =
     631             :           QueryType<shared_model::interface::types::AccountIdType,
     632             :                     shared_model::interface::types::AssetIdType,
     633             :                     std::string>;
     634             :       using PermissionTuple = boost::tuple<int>;
     635             : 
     636          19 :       auto cmd = (boost::format(R"(WITH has_perms AS (%s),
     637             :       t AS (
     638             :           SELECT * FROM account_has_asset
     639             :           WHERE account_id = :account_id
     640             :       )
     641             :       SELECT account_id, asset_id, amount, perm FROM t
     642             :       RIGHT OUTER JOIN has_perms ON TRUE
     643             :       )")
     644          19 :                   % hasQueryPermission(creator_id_,
     645          19 :                                        q.accountId(),
     646             :                                        Role::kGetMyAccAst,
     647             :                                        Role::kGetAllAccAst,
     648             :                                        Role::kGetDomainAccAst))
     649          19 :                      .str();
     650             : 
     651          19 :       return executeQuery<QueryTuple, PermissionTuple>(
     652             :           [&] { return (sql_.prepare << cmd, soci::use(q.accountId())); },
     653             :           [&](auto range, auto &) {
     654             :             std::vector<
     655             :                 std::tuple<shared_model::interface::types::AccountIdType,
     656             :                            shared_model::interface::types::AssetIdType,
     657             :                            shared_model::interface::Amount>>
     658          12 :                 assets;
     659             :             boost::for_each(range, [&assets](auto t) {
     660          17 :               apply(t,
     661             :                     [&assets](auto &account_id, auto &asset_id, auto &amount) {
     662          17 :                       assets.push_back(std::make_tuple(
     663          17 :                           std::move(account_id),
     664          17 :                           std::move(asset_id),
     665          17 :                           shared_model::interface::Amount(amount)));
     666          17 :                     });
     667          17 :             });
     668          12 :             return query_response_factory_->createAccountAssetResponse(
     669          12 :                 assets, query_hash_);
     670          12 :           },
     671          19 :           notEnoughPermissionsResponse(perm_converter_,
     672             :                                        Role::kGetMyAccAst,
     673             :                                        Role::kGetAllAccAst,
     674             :                                        Role::kGetDomainAccAst));
     675          19 :     }
     676             : 
     677             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     678             :         const shared_model::interface::GetAccountDetail &q) {
     679             :       using QueryTuple = QueryType<shared_model::interface::types::DetailType>;
     680             :       using PermissionTuple = boost::tuple<int>;
     681             : 
     682          22 :       std::string query_detail;
     683          22 :       if (q.key() and q.writer()) {
     684           1 :         auto filled_json = (boost::format("{\"%s\", \"%s\"}") % q.writer().get()
     685           1 :                             % q.key().get());
     686           1 :         query_detail = (boost::format(R"(SELECT json_build_object('%s'::text,
     687             :             json_build_object('%s'::text, (SELECT data #>> '%s'
     688             :             FROM account WHERE account_id = :account_id))) AS json)")
     689           1 :                         % q.writer().get() % q.key().get() % filled_json)
     690           1 :                            .str();
     691          21 :       } else if (q.key() and not q.writer()) {
     692           1 :         query_detail =
     693           1 :             (boost::format(
     694             :                  R"(SELECT json_object_agg(key, value) AS json FROM (SELECT
     695             :             json_build_object(kv.key, json_build_object('%1%'::text,
     696             :             kv.value -> '%1%')) FROM jsonb_each((SELECT data FROM account
     697             :             WHERE account_id = :account_id)) kv WHERE kv.value ? '%1%') AS
     698             :             jsons, json_each(json_build_object))")
     699           1 :              % q.key().get())
     700           1 :                 .str();
     701          20 :       } else if (not q.key() and q.writer()) {
     702           1 :         query_detail = (boost::format(R"(SELECT json_build_object('%1%'::text,
     703             :           (SELECT data -> '%1%' FROM account WHERE account_id =
     704             :            :account_id)) AS json)")
     705           1 :                         % q.writer().get())
     706           1 :                            .str();
     707           1 :       } else {
     708          19 :         query_detail = (boost::format(R"(SELECT data#>>'{}' AS json FROM account
     709             :             WHERE account_id = :account_id)"))
     710          19 :                            .str();
     711             :       }
     712          22 :       auto cmd = (boost::format(R"(WITH has_perms AS (%s),
     713             :       detail AS (%s)
     714             :       SELECT json, perm FROM detail
     715             :       RIGHT OUTER JOIN has_perms ON TRUE
     716             :       )")
     717          22 :                   % hasQueryPermission(creator_id_,
     718          22 :                                        q.accountId(),
     719             :                                        Role::kGetMyAccDetail,
     720             :                                        Role::kGetAllAccDetail,
     721             :                                        Role::kGetDomainAccDetail)
     722          22 :                   % query_detail)
     723          22 :                      .str();
     724             : 
     725          22 :       return executeQuery<QueryTuple, PermissionTuple>(
     726             :           [&] {
     727          22 :             return (sql_.prepare << cmd,
     728          22 :                     soci::use(q.accountId(), "account_id"));
     729           0 :           },
     730             :           [this, &q](auto range, auto &) {
     731          15 :             if (range.empty()) {
     732           1 :               return this->logAndReturnErrorResponse(
     733           1 :                   QueryErrorType::kNoAccountDetail, q.accountId());
     734             :             }
     735             : 
     736             :             return apply(range.front(), [this](auto &json) {
     737          14 :               return query_response_factory_->createAccountDetailResponse(
     738          14 :                   json, query_hash_);
     739           0 :             });
     740          15 :           },
     741          22 :           notEnoughPermissionsResponse(perm_converter_,
     742             :                                        Role::kGetMyAccDetail,
     743             :                                        Role::kGetAllAccDetail,
     744             :                                        Role::kGetDomainAccDetail));
     745          22 :     }
     746             : 
     747             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     748             :         const shared_model::interface::GetRoles &q) {
     749             :       using QueryTuple = QueryType<shared_model::interface::types::RoleIdType>;
     750             :       using PermissionTuple = boost::tuple<int>;
     751             : 
     752           8 :       auto cmd = (boost::format(
     753             :                       R"(WITH has_perms AS (%s)
     754             :       SELECT role_id, perm FROM role
     755             :       RIGHT OUTER JOIN has_perms ON TRUE
     756           8 :       )") % checkAccountRolePermission(Role::kGetRoles))
     757           8 :                      .str();
     758             : 
     759           8 :       return executeQuery<QueryTuple, PermissionTuple>(
     760             :           [&] {
     761           8 :             return (sql_.prepare << cmd,
     762           8 :                     soci::use(creator_id_, "role_account_id"));
     763           0 :           },
     764             :           [&](auto range, auto &) {
     765           6 :             auto roles = boost::copy_range<
     766             :                 std::vector<shared_model::interface::types::RoleIdType>>(
     767             :                 range | boost::adaptors::transformed([](auto t) {
     768             :                   return apply(t, [](auto &role_id) { return role_id; });
     769             :                 }));
     770             : 
     771           6 :             return query_response_factory_->createRolesResponse(roles,
     772           6 :                                                                 query_hash_);
     773           6 :           },
     774           8 :           notEnoughPermissionsResponse(perm_converter_, Role::kGetRoles));
     775           8 :     }
     776             : 
     777             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     778             :         const shared_model::interface::GetRolePermissions &q) {
     779             :       using QueryTuple = QueryType<std::string>;
     780             :       using PermissionTuple = boost::tuple<int>;
     781             : 
     782           5 :       auto cmd = (boost::format(
     783             :                       R"(WITH has_perms AS (%s),
     784             :       perms AS (SELECT permission FROM role_has_permissions
     785             :                 WHERE role_id = :role_name)
     786             :       SELECT permission, perm FROM perms
     787             :       RIGHT OUTER JOIN has_perms ON TRUE
     788           5 :       )") % checkAccountRolePermission(Role::kGetRoles))
     789           5 :                      .str();
     790             : 
     791           5 :       return executeQuery<QueryTuple, PermissionTuple>(
     792             :           [&] {
     793           5 :             return (sql_.prepare << cmd,
     794           5 :                     soci::use(creator_id_, "role_account_id"),
     795           5 :                     soci::use(q.roleId(), "role_name"));
     796           0 :           },
     797             :           [this, &q](auto range, auto &) {
     798           3 :             if (range.empty()) {
     799           1 :               return this->logAndReturnErrorResponse(
     800             :                   QueryErrorType::kNoRoles,
     801           1 :                   "{" + q.roleId() + ", " + creator_id_ + "}");
     802             :             }
     803             : 
     804             :             return apply(range.front(), [this](auto &permission) {
     805           2 :               return query_response_factory_->createRolePermissionsResponse(
     806           2 :                   shared_model::interface::RolePermissionSet(permission),
     807           2 :                   query_hash_);
     808             :             });
     809           3 :           },
     810           5 :           notEnoughPermissionsResponse(perm_converter_, Role::kGetRoles));
     811           5 :     }
     812             : 
     813             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     814             :         const shared_model::interface::GetAssetInfo &q) {
     815             :       using QueryTuple =
     816             :           QueryType<shared_model::interface::types::DomainIdType, uint32_t>;
     817             :       using PermissionTuple = boost::tuple<int>;
     818             : 
     819           5 :       auto cmd = (boost::format(
     820             :                       R"(WITH has_perms AS (%s),
     821             :       perms AS (SELECT domain_id, precision FROM asset
     822             :                 WHERE asset_id = :asset_id)
     823             :       SELECT domain_id, precision, perm FROM perms
     824             :       RIGHT OUTER JOIN has_perms ON TRUE
     825           5 :       )") % checkAccountRolePermission(Role::kReadAssets))
     826           5 :                      .str();
     827             : 
     828           5 :       return executeQuery<QueryTuple, PermissionTuple>(
     829             :           [&] {
     830           5 :             return (sql_.prepare << cmd,
     831           5 :                     soci::use(creator_id_, "role_account_id"),
     832           5 :                     soci::use(q.assetId(), "asset_id"));
     833           0 :           },
     834             :           [this, &q](auto range, auto &) {
     835           3 :             if (range.empty()) {
     836           2 :               return this->logAndReturnErrorResponse(
     837             :                   QueryErrorType::kNoAsset,
     838           2 :                   "{" + q.assetId() + ", " + creator_id_ + "}");
     839             :             }
     840             : 
     841           1 :             return apply(range.front(),
     842             :                          [this, &q](auto &domain_id, auto &precision) {
     843           1 :                            return query_response_factory_->createAssetResponse(
     844           1 :                                q.assetId(), domain_id, precision, query_hash_);
     845           0 :                          });
     846           3 :           },
     847           5 :           notEnoughPermissionsResponse(perm_converter_, Role::kReadAssets));
     848           5 :     }
     849             : 
     850             :     QueryExecutorResult PostgresQueryExecutorVisitor::operator()(
     851             :         const shared_model::interface::GetPendingTransactions &q) {
     852             :       std::vector<std::unique_ptr<shared_model::interface::Transaction>>
     853           5 :           response_txs;
     854             :       auto interface_txs =
     855           5 :           pending_txs_storage_->getPendingTransactions(creator_id_);
     856           5 :       response_txs.reserve(interface_txs.size());
     857             : 
     858           5 :       std::transform(interface_txs.begin(),
     859           5 :                      interface_txs.end(),
     860           5 :                      std::back_inserter(response_txs),
     861             :                      [](auto &tx) { return clone(*tx); });
     862           5 :       return query_response_factory_->createTransactionsResponse(
     863           5 :           std::move(response_txs), query_hash_);
     864           5 :     }
     865             : 
     866             :   }  // namespace ametsuchi
     867             : }  // namespace iroha

Generated by: LCOV version 1.13