LCOV - code coverage report
Current view: top level - irohad/ametsuchi/impl - postgres_command_executor.cpp (source / functions) Hit Total Coverage
Test: coverage_cleared.info Lines: 513 541 94.8 %
Date: 2018-12-05 17:11:35 Functions: 112 122 91.8 %

          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_command_executor.hpp"
       7             : 
       8             : #include <soci/postgresql/soci-postgresql.h>
       9             : #include <boost/format.hpp>
      10             : #include "ametsuchi/impl/soci_utils.hpp"
      11             : #include "cryptography/public_key.hpp"
      12             : #include "interfaces/commands/add_asset_quantity.hpp"
      13             : #include "interfaces/commands/add_peer.hpp"
      14             : #include "interfaces/commands/add_signatory.hpp"
      15             : #include "interfaces/commands/append_role.hpp"
      16             : #include "interfaces/commands/create_account.hpp"
      17             : #include "interfaces/commands/create_asset.hpp"
      18             : #include "interfaces/commands/create_domain.hpp"
      19             : #include "interfaces/commands/create_role.hpp"
      20             : #include "interfaces/commands/detach_role.hpp"
      21             : #include "interfaces/commands/grant_permission.hpp"
      22             : #include "interfaces/commands/remove_signatory.hpp"
      23             : #include "interfaces/commands/revoke_permission.hpp"
      24             : #include "interfaces/commands/set_account_detail.hpp"
      25             : #include "interfaces/commands/set_quorum.hpp"
      26             : #include "interfaces/commands/subtract_asset_quantity.hpp"
      27             : #include "interfaces/commands/transfer_asset.hpp"
      28             : #include "interfaces/common_objects/types.hpp"
      29             : #include "interfaces/permission_to_string.hpp"
      30             : #include "utils/string_builder.hpp"
      31             : 
      32             : namespace {
      33             :   struct PreparedStatement {
      34             :     std::string command_name;
      35             :     std::string command_base;
      36             :     std::vector<std::string> permission_checks;
      37             : 
      38             :     static const std::string validationPrefix;
      39             :     static const std::string noValidationPrefix;
      40             :   };
      41             : 
      42             :   const std::string PreparedStatement::validationPrefix = "WithValidation";
      43             :   const std::string PreparedStatement::noValidationPrefix = "WithOutValidation";
      44             : 
      45             :   // Transforms prepared statement into two strings:
      46             :   //    1. SQL query with validation
      47             :   //    2. SQL query without validation
      48             :   std::pair<std::string, std::string> compileStatement(
      49             :       const PreparedStatement &statement) {
      50             :     // Create query with validation
      51       71232 :     auto with_validation = boost::format(statement.command_base)
      52       71232 :         % (statement.command_name + PreparedStatement::validationPrefix);
      53             : 
      54             :     // append all necessary checks to the query
      55      298284 :     for (const auto &check : statement.permission_checks) {
      56      227052 :       with_validation = with_validation % check;
      57             :     }
      58             : 
      59             :     // Create query without validation
      60       71232 :     auto without_validation = boost::format(statement.command_base)
      61       71232 :         % (statement.command_name + PreparedStatement::noValidationPrefix);
      62             : 
      63             :     // since checks are not needed, append empty strings to their place
      64      298284 :     for (size_t i = 0; i < statement.permission_checks.size(); i++) {
      65      227052 :       without_validation = without_validation % "";
      66      227052 :     }
      67             : 
      68       71232 :     return {with_validation.str(), without_validation.str()};
      69       71232 :   }
      70             : 
      71             :   void prepareStatement(soci::session &sql,
      72             :                         const PreparedStatement &statement) {
      73       71232 :     auto queries = compileStatement(statement);
      74             : 
      75       71232 :     sql << queries.first;
      76       71232 :     sql << queries.second;
      77       71232 :   }
      78             : 
      79             :   template <typename QueryArgsCallable>
      80             :   iroha::expected::Error<iroha::ametsuchi::CommandError> makeCommandError(
      81             :       std::string &&command_name,
      82             :       const iroha::ametsuchi::CommandError::ErrorCodeType code,
      83             :       QueryArgsCallable &&query_args) noexcept {
      84          15 :     return iroha::expected::makeError(iroha::ametsuchi::CommandError{
      85          15 :         std::move(command_name), code, query_args()});
      86           0 :   }
      87             : 
      88             :   /// mapping between pairs of SQL error substrings and related fake error
      89             :   /// codes, which are indices in this collection
      90             :   const std::vector<std::tuple<std::string, std::string>> kSqlToFakeErrorCode =
      91         430 :       {std::make_tuple("Key (account_id)=", "is not present in table"),
      92          43 :        std::make_tuple("Key (permittee_account_id)", "is not present in table"),
      93          43 :        std::make_tuple("Key (role_id)=", "is not present in table"),
      94          43 :        std::make_tuple("Key (domain_id)=", "is not present in table"),
      95          43 :        std::make_tuple("Key (asset_id)=", "already exists"),
      96          43 :        std::make_tuple("Key (domain_id)=", "already exists"),
      97          43 :        std::make_tuple("Key (role_id)=", "already exists"),
      98          43 :        std::make_tuple("Key (account_id, public_key)=", "already exists"),
      99          43 :        std::make_tuple("Key (account_id)=", "already exists"),
     100          43 :        std::make_tuple("Key (default_role)=", "is not present in table")};
     101             : 
     102             :   /// mapping between command name, fake error code and related real error code
     103             :   const std::map<std::string, std::map<int, int>> kCmdNameToErrorCode{
     104          43 :       std::make_pair(
     105             :           "AddSignatory",
     106          43 :           std::map<int, int>{std::make_pair(0, 3), std::make_pair(7, 4)}),
     107          43 :       std::make_pair(
     108             :           "AppendRole",
     109          43 :           std::map<int, int>{std::make_pair(0, 3), std::make_pair(2, 4)}),
     110          43 :       std::make_pair(
     111             :           "DetachRole",
     112          43 :           std::map<int, int>{std::make_pair(0, 3), std::make_pair(2, 5)}),
     113          43 :       std::make_pair("RemoveSignatory",
     114          43 :                      std::map<int, int>{std::make_pair(0, 3)}),
     115          43 :       std::make_pair("SetAccountDetail",
     116          43 :                      std::map<int, int>{std::make_pair(0, 3)}),
     117          43 :       std::make_pair("SetQuorum", std::map<int, int>{std::make_pair(0, 3)}),
     118          43 :       std::make_pair("GrantPermission",
     119          43 :                      std::map<int, int>{std::make_pair(1, 3)}),
     120          43 :       std::make_pair("RevokePermission",
     121          43 :                      std::map<int, int>{std::make_pair(1, 3)}),
     122          43 :       std::make_pair(
     123             :           "CreateAccount",
     124          43 :           std::map<int, int>{std::make_pair(3, 3), std::make_pair(8, 4)}),
     125          43 :       std::make_pair(
     126             :           "CreateAsset",
     127          43 :           std::map<int, int>{std::make_pair(3, 3), std::make_pair(4, 4)}),
     128          43 :       std::make_pair(
     129             :           "CreateDomain",
     130          43 :           std::map<int, int>{std::make_pair(5, 3), std::make_pair(9, 4)}),
     131          43 :       std::make_pair("CreateRole", std::map<int, int>{std::make_pair(6, 3)}),
     132          43 :       std::make_pair("AddSignatory", std::map<int, int>{std::make_pair(7, 4)})};
     133             : 
     134             :   /**
     135             :    * Get a real error code based on the fake one and a command name
     136             :    * @param fake_error_code - inner error code to be translated into the user's
     137             :    * one
     138             :    * @param command_name of the failed command
     139             :    * @return real error code
     140             :    */
     141             :   boost::optional<iroha::ametsuchi::CommandError::ErrorCodeType>
     142             :   getRealErrorCode(size_t fake_error_code, const std::string &command_name) {
     143          18 :     auto fake_to_real_code = kCmdNameToErrorCode.find(command_name);
     144          18 :     if (fake_to_real_code == kCmdNameToErrorCode.end()) {
     145           0 :       return {};
     146             :     }
     147             : 
     148          18 :     auto real_code = fake_to_real_code->second.find(fake_error_code);
     149          18 :     if (real_code == fake_to_real_code->second.end()) {
     150           0 :       return {};
     151             :     }
     152             : 
     153          18 :     return real_code->second;
     154          18 :   }
     155             : 
     156             :   // TODO [IR-1830] Akvinikym 31.10.18: make benchmarks to compare exception
     157             :   // parsing vs nested queries
     158             :   /**
     159             :    * Get an error code from the text SQL error
     160             :    * @tparam QueryArgsCallable - type of callable to get query arguments
     161             :    * @param command_name - name of the failed command
     162             :    * @param error - string error, which SQL gave out
     163             :    * @param query_args - callable to get a string representation of query
     164             :    * arguments
     165             :    * @return command_error structure
     166             :    */
     167             :   template <typename QueryArgsCallable>
     168             :   iroha::ametsuchi::CommandResult getCommandError(
     169             :       std::string &&command_name,
     170             :       const std::string &error,
     171             :       QueryArgsCallable &&query_args) noexcept {
     172           5 :     std::string key, to_be_presented;
     173             :     bool errors_matched;
     174             : 
     175             :     // go through mapping of SQL errors and get index of the current error - it
     176             :     // is "fake" error code
     177          38 :     for (size_t fakeErrorCode = 0; fakeErrorCode < kSqlToFakeErrorCode.size();
     178          33 :          ++fakeErrorCode) {
     179          38 :       std::tie(key, to_be_presented) = kSqlToFakeErrorCode[fakeErrorCode];
     180          38 :       errors_matched = error.find(key) != std::string::npos
     181          38 :           and error.find(to_be_presented) != std::string::npos;
     182          38 :       if (errors_matched) {
     183           5 :         if (auto real_error_code =
     184           5 :                 getRealErrorCode(fakeErrorCode, command_name)) {
     185           5 :           return makeCommandError(std::move(command_name),
     186           5 :                                   *real_error_code,
     187           5 :                                   std::forward<QueryArgsCallable>(query_args));
     188             :         }
     189           0 :         break;
     190             :       }
     191          33 :     }
     192             :     // parsing is not successful, return the general error
     193           1 :     return makeCommandError(std::move(command_name),
     194             :                             1,
     195           1 :                             std::forward<QueryArgsCallable>(query_args));
     196           5 :   }
     197             : 
     198             :   /**
     199             :    * Executes sql query
     200             :    * Assumes that statement query returns 0 in case of success
     201             :    * or error code in case of failure
     202             :    * @tparam QueryArgsCallable - type of callable to get query arguments
     203             :    * @param sql - connection on which to execute statement
     204             :    * @param cmd - sql query to be executed
     205             :    * @param command_name - which command executes a query
     206             :    * @param query_args - callable to get a string representation of query
     207             :    * arguments
     208             :    * @return CommandResult with command name and error message
     209             :    */
     210             :   template <typename QueryArgsCallable>
     211             :   iroha::ametsuchi::CommandResult executeQuery(
     212             :       soci::session &sql,
     213             :       const std::string &cmd,
     214             :       std::string command_name,
     215             :       QueryArgsCallable &&query_args) noexcept {
     216             :     uint32_t result;
     217             :     try {
     218        1568 :       sql << cmd, soci::into(result);
     219        1566 :       if (result != 0) {
     220          15 :         return makeCommandError(std::move(command_name),
     221          15 :                                 result,
     222          15 :                                 std::forward<QueryArgsCallable>(query_args));
     223             :       }
     224        1564 :       return {};
     225           5 :     } catch (const std::exception &e) {
     226           5 :       return getCommandError(std::move(command_name),
     227           5 :                              e.what(),
     228           5 :                              std::forward<QueryArgsCallable>(query_args));
     229           5 :     }
     230        1568 :   }
     231             : 
     232             :   std::string checkAccountRolePermission(
     233             :       shared_model::interface::permissions::Role permission,
     234             :       const shared_model::interface::types::AccountIdType &account_id) {
     235             :     const auto perm_str =
     236       66780 :         shared_model::interface::RolePermissionSet({permission}).toBitstring();
     237       66780 :     const auto bits = shared_model::interface::RolePermissionSet::size();
     238       66780 :     std::string query = (boost::format(R"(
     239             :           SELECT COALESCE(bit_or(rp.permission), '0'::bit(%1%))
     240             :           & '%2%' = '%2%' FROM role_has_permissions AS rp
     241             :               JOIN account_has_roles AS ar on ar.role_id = rp.role_id
     242             :               WHERE ar.account_id = %3%)")
     243       66780 :                          % bits % perm_str % account_id)
     244       66780 :                             .str();
     245       66780 :     return query;
     246       66780 :   }
     247             : 
     248             :   std::string checkAccountGrantablePermission(
     249             :       shared_model::interface::permissions::Grantable permission,
     250             :       const shared_model::interface::types::AccountIdType &creator_id,
     251             :       const shared_model::interface::types::AccountIdType &account_id) {
     252             :     const auto perm_str =
     253       22260 :         shared_model::interface::GrantablePermissionSet({permission})
     254       22260 :             .toBitstring();
     255       22260 :     const auto bits = shared_model::interface::GrantablePermissionSet::size();
     256       22260 :     std::string query = (boost::format(R"(
     257             :           SELECT COALESCE(bit_or(permission), '0'::bit(%1%))
     258             :           & '%2%' = '%2%' FROM account_has_grantable_permissions
     259             :               WHERE account_id = %4% AND
     260             :               permittee_account_id = %3%
     261       22260 :           )") % bits % perm_str
     262       22260 :                          % creator_id % account_id)
     263       22260 :                             .str();
     264       22260 :     return query;
     265       22260 :   }
     266             : 
     267             :   std::string checkAccountHasRoleOrGrantablePerm(
     268             :       shared_model::interface::permissions::Role role,
     269             :       shared_model::interface::permissions::Grantable grantable,
     270             :       const shared_model::interface::types::AccountIdType &creator_id,
     271             :       const shared_model::interface::types::AccountIdType &account_id) {
     272       13356 :     return (boost::format(R"(WITH
     273             :           has_role_perm AS (%s),
     274             :           has_grantable_perm AS (%s)
     275             :           SELECT CASE
     276             :                            WHEN (SELECT * FROM has_grantable_perm) THEN true
     277             :                            WHEN (%s = %s) THEN
     278             :                                CASE
     279             :                                    WHEN (SELECT * FROM has_role_perm) THEN true
     280             :                                    ELSE false
     281             :                                 END
     282             :                            ELSE false END
     283             :           )")
     284       13356 :             % checkAccountRolePermission(role, creator_id)
     285       13356 :             % checkAccountGrantablePermission(grantable, creator_id, account_id)
     286       13356 :             % creator_id % account_id)
     287       13356 :         .str();
     288           0 :   }
     289             : 
     290             :   template <typename Format>
     291             :   void appendCommandName(const std::string &name,
     292             :                          Format &cmd,
     293             :                          bool do_validation) {
     294        6796 :     auto command_name = name
     295        6796 :         + (do_validation ? PreparedStatement::validationPrefix
     296             :                          : PreparedStatement::noValidationPrefix);
     297        6796 :     cmd % command_name;
     298        6796 :   }
     299             : 
     300             :   /**
     301             :    * Get a pretty string builder initialized for query arguments append
     302             :    * @return string builder
     303             :    */
     304             :   shared_model::detail::PrettyStringBuilder getQueryArgsStringBuilder() {
     305          97 :     return shared_model::detail::PrettyStringBuilder().init("Query arguments");
     306           0 :   }
     307             : }  // namespace
     308             : 
     309             : namespace iroha {
     310             :   namespace ametsuchi {
     311             :     // TODO [IR-1830] Akvinikym 31.10.18: make benchmarks to compare exception
     312             :     // parsing vs nested queries
     313             :     const std::string PostgresCommandExecutor::addAssetQuantityBase = R"(
     314             :           PREPARE %s (text, text, int, text) AS
     315             :           WITH has_account AS (SELECT account_id FROM account
     316             :                                WHERE account_id = $1 LIMIT 1),
     317             :                has_asset AS (SELECT asset_id FROM asset
     318             :                              WHERE asset_id = $2 AND
     319             :                              precision >= $3 LIMIT 1),
     320             :                %s
     321             :                amount AS (SELECT amount FROM account_has_asset
     322             :                           WHERE asset_id = $2 AND
     323             :                           account_id = $1 LIMIT 1),
     324             :                new_value AS (SELECT $4::decimal +
     325             :                               (SELECT
     326             :                                   CASE WHEN EXISTS
     327             :                                       (SELECT amount FROM amount LIMIT 1) THEN
     328             :                                       (SELECT amount FROM amount LIMIT 1)
     329             :                                   ELSE 0::decimal
     330             :                               END) AS value
     331             :                           ),
     332             :                inserted AS
     333             :                (
     334             :                   INSERT INTO account_has_asset(account_id, asset_id, amount)
     335             :                   (
     336             :                       SELECT $1, $2, value FROM new_value
     337             :                       WHERE EXISTS (SELECT * FROM has_account LIMIT 1) AND
     338             :                         EXISTS (SELECT * FROM has_asset LIMIT 1) AND
     339             :                         EXISTS (SELECT value FROM new_value
     340             :                                 WHERE value < 2::decimal ^ (256 - $3)
     341             :                                 LIMIT 1)
     342             :                         %s
     343             :                   )
     344             :                   ON CONFLICT (account_id, asset_id) DO UPDATE
     345             :                   SET amount = EXCLUDED.amount
     346             :                   RETURNING (1)
     347             :                )
     348             :           SELECT CASE
     349             :               WHEN EXISTS (SELECT * FROM inserted LIMIT 1) THEN 0
     350             :               %s
     351             :               WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 3
     352             :               WHEN NOT EXISTS (SELECT value FROM new_value
     353             :                                WHERE value < 2::decimal ^ (256 - $3)
     354             :                                LIMIT 1) THEN 4
     355             :               ELSE 1
     356             :           END AS result;)";
     357             : 
     358             :     const std::string PostgresCommandExecutor::addPeerBase = R"(
     359             :           PREPARE %s (text, text, text) AS
     360             :           WITH
     361             :           %s
     362             :           inserted AS (
     363             :               INSERT INTO peer(public_key, address)
     364             :               (
     365             :                   SELECT $2, $3
     366             :                   %s
     367             :               ) RETURNING (1)
     368             :           )
     369             :           SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
     370             :               %s
     371             :               ELSE 1 END AS result)";
     372             : 
     373             :     const std::string PostgresCommandExecutor::addSignatoryBase = R"(
     374             :           PREPARE %s (text, text, text) AS
     375             :           WITH %s
     376             :           insert_signatory AS
     377             :           (
     378             :               INSERT INTO signatory(public_key)
     379             :               (SELECT $3 %s) ON CONFLICT DO NOTHING RETURNING (1)
     380             :           ),
     381             :           has_signatory AS (SELECT * FROM signatory WHERE public_key = $3),
     382             :           insert_account_signatory AS
     383             :           (
     384             :               INSERT INTO account_has_signatory(account_id, public_key)
     385             :               (
     386             :                   SELECT $2, $3 WHERE (EXISTS
     387             :                   (SELECT * FROM insert_signatory) OR
     388             :                   EXISTS (SELECT * FROM has_signatory))
     389             :                   %s
     390             :               )
     391             :               RETURNING (1)
     392             :           )
     393             :           SELECT CASE
     394             :               WHEN EXISTS (SELECT * FROM insert_account_signatory) THEN 0
     395             :               %s
     396             :               ELSE 1
     397             :           END AS RESULT;)";
     398             : 
     399             :     const std::string PostgresCommandExecutor::appendRoleBase = R"(
     400             :             PREPARE %s (text, text, text) AS
     401             :             WITH %s
     402             :             role_exists AS (SELECT * FROM role WHERE role_id = $3),
     403             :             inserted AS (
     404             :                 INSERT INTO account_has_roles(account_id, role_id)
     405             :                 (
     406             :                     SELECT $2, $3 %s) RETURNING (1)
     407             :             )
     408             :             SELECT CASE
     409             :                 WHEN EXISTS (SELECT * FROM inserted) THEN 0
     410             :                 WHEN NOT EXISTS (SELECT * FROM role_exists) THEN 4
     411             :                 %s
     412             :                 ELSE 1
     413             :             END AS result)";
     414             : 
     415             :     const std::string PostgresCommandExecutor::createAccountBase = R"(
     416             :           PREPARE %s (text, text, text, text) AS
     417             :           WITH get_domain_default_role AS (SELECT default_role FROM domain
     418             :                                            WHERE domain_id = $3),
     419             :           %s
     420             :           insert_signatory AS
     421             :           (
     422             :               INSERT INTO signatory(public_key)
     423             :               (
     424             :                   SELECT $4 WHERE EXISTS
     425             :                   (SELECT * FROM get_domain_default_role)
     426             :               ) ON CONFLICT DO NOTHING RETURNING (1)
     427             :           ),
     428             :           has_signatory AS (SELECT * FROM signatory WHERE public_key = $4),
     429             :           insert_account AS
     430             :           (
     431             :               INSERT INTO account(account_id, domain_id, quorum, data)
     432             :               (
     433             :                   SELECT $2, $3, 1, '{}' WHERE (EXISTS
     434             :                       (SELECT * FROM insert_signatory) OR EXISTS
     435             :                       (SELECT * FROM has_signatory)
     436             :                   ) AND EXISTS (SELECT * FROM get_domain_default_role)
     437             :                   %s
     438             :               ) RETURNING (1)
     439             :           ),
     440             :           insert_account_signatory AS
     441             :           (
     442             :               INSERT INTO account_has_signatory(account_id, public_key)
     443             :               (
     444             :                   SELECT $2, $4 WHERE
     445             :                      EXISTS (SELECT * FROM insert_account)
     446             :               )
     447             :               RETURNING (1)
     448             :           ),
     449             :           insert_account_role AS
     450             :           (
     451             :               INSERT INTO account_has_roles(account_id, role_id)
     452             :               (
     453             :                   SELECT $2, default_role FROM get_domain_default_role
     454             :                   WHERE EXISTS (SELECT * FROM get_domain_default_role)
     455             :                     AND EXISTS (SELECT * FROM insert_account_signatory)
     456             :               ) RETURNING (1)
     457             :           )
     458             :           SELECT CASE
     459             :               WHEN EXISTS (SELECT * FROM insert_account_role) THEN 0
     460             :               %s
     461             :               WHEN NOT EXISTS (SELECT * FROM get_domain_default_role) THEN 3
     462             :               ELSE 1
     463             :               END AS result)";
     464             : 
     465             :     const std::string PostgresCommandExecutor::createAssetBase = R"(
     466             :               PREPARE %s (text, text, text, int) AS
     467             :               WITH %s
     468             :               inserted AS
     469             :               (
     470             :                   INSERT INTO asset(asset_id, domain_id, precision, data)
     471             :                   (
     472             :                       SELECT $2, $3, $4, NULL
     473             :                       %s
     474             :                   ) RETURNING (1)
     475             :               )
     476             :               SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
     477             :               %s
     478             :               ELSE 1 END AS result)";
     479             : 
     480             :     const std::string PostgresCommandExecutor::createDomainBase = R"(
     481             :               PREPARE %s (text, text, text) AS
     482             :               WITH %s
     483             :               inserted AS
     484             :               (
     485             :                   INSERT INTO domain(domain_id, default_role)
     486             :                   (
     487             :                       SELECT $2, $3
     488             :                       %s
     489             :                   ) RETURNING (1)
     490             :               )
     491             :               SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
     492             :               %s
     493             :               ELSE 1 END AS result)";
     494             : 
     495             :     const std::string PostgresCommandExecutor::createRoleBase = R"(
     496             :           PREPARE %s (text, text, bit) AS
     497             :           WITH %s
     498             :           insert_role AS (INSERT INTO role(role_id)
     499             :                               (SELECT $2
     500             :                               %s) RETURNING (1)),
     501             :           insert_role_permissions AS
     502             :           (
     503             :               INSERT INTO role_has_permissions(role_id, permission)
     504             :               (
     505             :                   SELECT $2, $3 WHERE EXISTS
     506             :                       (SELECT * FROM insert_role)
     507             :               ) RETURNING (1)
     508             :           )
     509             :           SELECT CASE
     510             :               WHEN EXISTS (SELECT * FROM insert_role_permissions) THEN 0
     511             :               %s
     512             :               WHEN EXISTS (SELECT * FROM role WHERE role_id = $2) THEN 2
     513             :               ELSE 1
     514             :               END AS result)";
     515             : 
     516             :     const std::string PostgresCommandExecutor::detachRoleBase = R"(
     517             :             PREPARE %s (text, text, text) AS
     518             :             WITH %s
     519             :             deleted AS
     520             :             (
     521             :               DELETE FROM account_has_roles
     522             :               WHERE account_id=$2
     523             :               AND role_id=$3
     524             :               %s
     525             :               RETURNING (1)
     526             :             )
     527             :             SELECT CASE WHEN EXISTS (SELECT * FROM deleted) THEN 0
     528             :             WHEN NOT EXISTS (SELECT * FROM account
     529             :                              WHERE account_id = $2) THEN 3
     530             :             WHEN NOT EXISTS (SELECT * FROM role
     531             :                              WHERE role_id = $3) THEN 5
     532             :             WHEN NOT EXISTS (SELECT * FROM account_has_roles
     533             :                              WHERE account_id=$2 AND role_id=$3) THEN 4
     534             :             %s
     535             :             ELSE 1 END AS result)";
     536             : 
     537             :     const std::string PostgresCommandExecutor::grantPermissionBase = R"(
     538             :           PREPARE %s (text, text, bit, bit) AS
     539             :           WITH %s
     540             :             inserted AS (
     541             :               INSERT INTO account_has_grantable_permissions AS
     542             :               has_perm(permittee_account_id, account_id, permission)
     543             :               (SELECT $2, $1, $3 %s) ON CONFLICT
     544             :               (permittee_account_id, account_id)
     545             :               DO UPDATE SET permission=(SELECT has_perm.permission | $3
     546             :               WHERE (has_perm.permission & $3) <> $3)
     547             :               RETURNING (1)
     548             :             )
     549             :             SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
     550             :               %s
     551             :               ELSE 1 END AS result)";
     552             : 
     553             :     const std::string PostgresCommandExecutor::removeSignatoryBase = R"(
     554             :           PREPARE %s (text, text, text) AS
     555             :           WITH
     556             :           %s
     557             :           delete_account_signatory AS (DELETE FROM account_has_signatory
     558             :               WHERE account_id = $2
     559             :               AND public_key = $3
     560             :               %s
     561             :               RETURNING (1)),
     562             :           delete_signatory AS
     563             :           (
     564             :               DELETE FROM signatory WHERE public_key = $3 AND
     565             :                   NOT EXISTS (SELECT 1 FROM account_has_signatory
     566             :                               WHERE public_key = $3)
     567             :                   AND NOT EXISTS (SELECT 1 FROM peer WHERE public_key = $3)
     568             :               RETURNING (1)
     569             :           )
     570             :           SELECT CASE
     571             :               WHEN EXISTS (SELECT * FROM delete_account_signatory) THEN
     572             :               CASE
     573             :                   WHEN EXISTS (SELECT * FROM delete_signatory) THEN 0
     574             :                   WHEN EXISTS (SELECT 1 FROM account_has_signatory
     575             :                                WHERE public_key = $3) THEN 0
     576             :                   WHEN EXISTS (SELECT 1 FROM peer
     577             :                                WHERE public_key = $3) THEN 0
     578             :                   ELSE 1
     579             :               END
     580             :               %s
     581             :               ELSE 1
     582             :           END AS result)";
     583             : 
     584             :     const std::string PostgresCommandExecutor::revokePermissionBase = R"(
     585             :           PREPARE %s (text, text, bit, bit) AS
     586             :           WITH %s
     587             :               inserted AS (
     588             :                   UPDATE account_has_grantable_permissions as has_perm
     589             :                   SET permission=(SELECT has_perm.permission & $4
     590             :                   WHERE has_perm.permission & $3 = $3 AND
     591             :                   has_perm.permittee_account_id=$2 AND
     592             :                   has_perm.account_id=$1) WHERE
     593             :                   permittee_account_id=$2 AND
     594             :                   account_id=$1 %s
     595             :                 RETURNING (1)
     596             :               )
     597             :               SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
     598             :                   %s
     599             :                   ELSE 1 END AS result)";
     600             : 
     601             :     const std::string PostgresCommandExecutor::setAccountDetailBase = R"(
     602             :           PREPARE %s (text, text, text[], text[], text, text) AS
     603             :           WITH %s
     604             :               inserted AS
     605             :               (
     606             :                   UPDATE account SET data = jsonb_set(
     607             :                   CASE WHEN data ?$1 THEN data ELSE
     608             :                   jsonb_set(data, $3, $6::jsonb) END,
     609             :                   $4, $5::jsonb) WHERE account_id=$2 %s
     610             :                   RETURNING (1)
     611             :               )
     612             :               SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
     613             :                   %s
     614             :                   WHEN NOT EXISTS
     615             :                       (SELECT * FROM account WHERE account_id=$2) THEN 3
     616             :                   ELSE 1 END AS result)";
     617             : 
     618             :     const std::string PostgresCommandExecutor::setQuorumBase = R"(
     619             :           PREPARE %s (text, text, int) AS
     620             :           WITH
     621             :           %s
     622             :           %s
     623             :           updated AS (
     624             :               UPDATE account SET quorum=$3
     625             :               WHERE account_id=$2
     626             :               %s
     627             :               RETURNING (1)
     628             :           )
     629             :           SELECT CASE WHEN EXISTS (SELECT * FROM updated) THEN 0
     630             :               %s
     631             :               ELSE 1
     632             :           END AS result)";
     633             : 
     634             :     const std::string PostgresCommandExecutor::subtractAssetQuantityBase = R"(
     635             :           PREPARE %s (text, text, int, text) AS
     636             :           WITH %s
     637             :                has_account AS (SELECT account_id FROM account
     638             :                                WHERE account_id = $1 LIMIT 1),
     639             :                has_asset AS (SELECT asset_id FROM asset
     640             :                              WHERE asset_id = $2
     641             :                              AND precision >= $3 LIMIT 1),
     642             :                amount AS (SELECT amount FROM account_has_asset
     643             :                           WHERE asset_id = $2
     644             :                           AND account_id = $1 LIMIT 1),
     645             :                new_value AS (SELECT
     646             :                               (SELECT
     647             :                                   CASE WHEN EXISTS
     648             :                                       (SELECT amount FROM amount LIMIT 1)
     649             :                                       THEN (SELECT amount FROM amount LIMIT 1)
     650             :                                   ELSE 0::decimal
     651             :                               END) - $4::decimal AS value
     652             :                           ),
     653             :                inserted AS
     654             :                (
     655             :                   INSERT INTO account_has_asset(account_id, asset_id, amount)
     656             :                   (
     657             :                       SELECT $1, $2, value FROM new_value
     658             :                       WHERE EXISTS (SELECT * FROM has_account LIMIT 1) AND
     659             :                         EXISTS (SELECT * FROM has_asset LIMIT 1) AND
     660             :                         EXISTS (SELECT value FROM new_value WHERE value >= 0 LIMIT 1)
     661             :                         %s
     662             :                   )
     663             :                   ON CONFLICT (account_id, asset_id)
     664             :                   DO UPDATE SET amount = EXCLUDED.amount
     665             :                   RETURNING (1)
     666             :                )
     667             :           SELECT CASE
     668             :               WHEN EXISTS (SELECT * FROM inserted LIMIT 1) THEN 0
     669             :               %s
     670             :               WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 3
     671             :               WHEN NOT EXISTS
     672             :                   (SELECT value FROM new_value WHERE value >= 0 LIMIT 1) THEN 4
     673             :               ELSE 1
     674             :           END AS result)";
     675             : 
     676             :     const std::string PostgresCommandExecutor::transferAssetBase = R"(
     677             :           PREPARE %s (text, text, text, text, int, text) AS
     678             :           WITH
     679             :               %s
     680             :               has_src_account AS (SELECT account_id FROM account
     681             :                                    WHERE account_id = $2 LIMIT 1),
     682             :               has_dest_account AS (SELECT account_id FROM account
     683             :                                     WHERE account_id = $3
     684             :                                     LIMIT 1),
     685             :               has_asset AS (SELECT asset_id FROM asset
     686             :                              WHERE asset_id = $4 AND
     687             :                              precision >= $5 LIMIT 1),
     688             :               src_amount AS (SELECT amount FROM account_has_asset
     689             :                               WHERE asset_id = $4 AND
     690             :                               account_id = $2 LIMIT 1),
     691             :               dest_amount AS (SELECT amount FROM account_has_asset
     692             :                                WHERE asset_id = $4 AND
     693             :                                account_id = $3 LIMIT 1),
     694             :               new_src_value AS (SELECT
     695             :                               (SELECT
     696             :                                   CASE WHEN EXISTS
     697             :                                       (SELECT amount FROM src_amount LIMIT 1)
     698             :                                       THEN
     699             :                                       (SELECT amount FROM src_amount LIMIT 1)
     700             :                                   ELSE 0::decimal
     701             :                               END) - $6::decimal AS value
     702             :                           ),
     703             :               new_dest_value AS (SELECT
     704             :                               (SELECT $6::decimal +
     705             :                                   CASE WHEN EXISTS
     706             :                                       (SELECT amount FROM dest_amount LIMIT 1)
     707             :                                           THEN
     708             :                                       (SELECT amount FROM dest_amount LIMIT 1)
     709             :                                   ELSE 0::decimal
     710             :                               END) AS value
     711             :                           ),
     712             :               insert_src AS
     713             :               (
     714             :                   INSERT INTO account_has_asset(account_id, asset_id, amount)
     715             :                   (
     716             :                       SELECT $2, $4, value
     717             :                       FROM new_src_value
     718             :                       WHERE EXISTS (SELECT * FROM has_src_account LIMIT 1) AND
     719             :                         EXISTS (SELECT * FROM has_dest_account LIMIT 1) AND
     720             :                         EXISTS (SELECT * FROM has_asset LIMIT 1) AND
     721             :                         EXISTS (SELECT value FROM new_src_value
     722             :                                 WHERE value >= 0 LIMIT 1) %s
     723             :                   )
     724             :                   ON CONFLICT (account_id, asset_id)
     725             :                   DO UPDATE SET amount = EXCLUDED.amount
     726             :                   RETURNING (1)
     727             :               ),
     728             :               insert_dest AS
     729             :               (
     730             :                   INSERT INTO account_has_asset(account_id, asset_id, amount)
     731             :                   (
     732             :                       SELECT $3, $4, value
     733             :                       FROM new_dest_value
     734             :                       WHERE EXISTS (SELECT * FROM insert_src) AND
     735             :                         EXISTS (SELECT * FROM has_src_account LIMIT 1) AND
     736             :                         EXISTS (SELECT * FROM has_dest_account LIMIT 1) AND
     737             :                         EXISTS (SELECT * FROM has_asset LIMIT 1) AND
     738             :                         EXISTS (SELECT value FROM new_dest_value
     739             :                                 WHERE value < 2::decimal ^ (256 - $5)
     740             :                                 LIMIT 1) %s
     741             :                   )
     742             :                   ON CONFLICT (account_id, asset_id)
     743             :                   DO UPDATE SET amount = EXCLUDED.amount
     744             :                   RETURNING (1)
     745             :                )
     746             :           SELECT CASE
     747             :               WHEN EXISTS (SELECT * FROM insert_dest LIMIT 1) THEN 0
     748             :               %s
     749             :               WHEN NOT EXISTS (SELECT * FROM has_dest_account LIMIT 1) THEN 4
     750             :               WHEN NOT EXISTS (SELECT * FROM has_src_account LIMIT 1) THEN 3
     751             :               WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 5
     752             :               WHEN NOT EXISTS (SELECT value FROM new_src_value
     753             :                                WHERE value >= 0 LIMIT 1) THEN 6
     754             :               WHEN NOT EXISTS (SELECT value FROM new_dest_value
     755             :                                WHERE value < 2::decimal ^ (256 - $5)
     756             :                                LIMIT 1) THEN 7
     757             :               ELSE 1
     758             :           END AS result)";
     759             : 
     760             :     std::string CommandError::toString() const {
     761           0 :       return (boost::format("%s: %d with extra info '%s'") % command_name
     762           0 :               % error_code % error_extra)
     763           0 :           .str();
     764           0 :     }
     765             : 
     766             :     PostgresCommandExecutor::PostgresCommandExecutor(
     767             :         soci::session &sql,
     768             :         std::shared_ptr<shared_model::interface::PermissionToString>
     769             :             perm_converter)
     770        1363 :         : sql_(sql),
     771        1363 :           do_validation_(true),
     772        1363 :           perm_converter_{std::move(perm_converter)} {}
     773             : 
     774             :     void PostgresCommandExecutor::setCreatorAccountId(
     775             :         const shared_model::interface::types::AccountIdType
     776             :             &creator_account_id) {
     777        2134 :       creator_account_id_ = creator_account_id;
     778        2134 :     }
     779             : 
     780             :     void PostgresCommandExecutor::doValidation(bool do_validation) {
     781        2134 :       do_validation_ = do_validation;
     782        2134 :     }
     783             : 
     784             :     CommandResult PostgresCommandExecutor::operator()(
     785             :         const shared_model::interface::AddAssetQuantity &command) {
     786         143 :       auto &account_id = creator_account_id_;
     787         143 :       auto &asset_id = command.assetId();
     788         143 :       auto amount = command.amount().toStringRepr();
     789         143 :       int precision = command.amount().precision();
     790             : 
     791             :       // 14.09.2018 nickaleks: IR-1707 move common logic to separate function
     792         143 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', %4%, '%5%')");
     793             : 
     794         143 :       appendCommandName("addAssetQuantity", cmd, do_validation_);
     795             : 
     796         143 :       cmd = (cmd % account_id % asset_id % precision % amount);
     797             : 
     798             :       auto str_args = [&account_id, &asset_id, &amount, precision] {
     799           6 :         return getQueryArgsStringBuilder()
     800           6 :             .append("account_id", account_id)
     801           6 :             .append("asset_id", asset_id)
     802           6 :             .append("amount", amount)
     803           6 :             .append("precision", std::to_string(precision))
     804           6 :             .finalize();
     805           0 :       };
     806             : 
     807         143 :       return executeQuery(
     808         143 :           sql_, cmd.str(), "AddAssetQuantity", std::move(str_args));
     809         143 :     }
     810             : 
     811             :     CommandResult PostgresCommandExecutor::operator()(
     812             :         const shared_model::interface::AddPeer &command) {
     813         513 :       auto &peer = command.peer();
     814             : 
     815         513 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
     816             : 
     817         513 :       appendCommandName("addPeer", cmd, do_validation_);
     818             : 
     819         513 :       cmd = (cmd % creator_account_id_ % peer.pubkey().hex() % peer.address());
     820             : 
     821             :       auto str_args = [&peer] {
     822           1 :         return getQueryArgsStringBuilder()
     823           1 :             .append("peer", peer.toString())
     824           1 :             .finalize();
     825           0 :       };
     826             : 
     827         513 :       return executeQuery(sql_, cmd.str(), "AddPeer", std::move(str_args));
     828         513 :     }
     829             : 
     830             :     CommandResult PostgresCommandExecutor::operator()(
     831             :         const shared_model::interface::AddSignatory &command) {
     832         112 :       auto &account_id = command.accountId();
     833         112 :       auto pubkey = command.pubkey().hex();
     834         112 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
     835             : 
     836         112 :       appendCommandName("addSignatory", cmd, do_validation_);
     837             : 
     838         112 :       cmd = (cmd % creator_account_id_ % account_id % pubkey);
     839             : 
     840             :       auto str_args = [&account_id, &pubkey] {
     841           5 :         return getQueryArgsStringBuilder()
     842           5 :             .append("account_id", account_id)
     843           5 :             .append("pubkey", pubkey)
     844           5 :             .finalize();
     845           0 :       };
     846             : 
     847         112 :       return executeQuery(sql_, cmd.str(), "AddSignatory", std::move(str_args));
     848         112 :     }
     849             : 
     850             :     CommandResult PostgresCommandExecutor::operator()(
     851             :         const shared_model::interface::AppendRole &command) {
     852         916 :       auto &account_id = command.accountId();
     853         916 :       auto &role_name = command.roleName();
     854         916 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
     855             : 
     856         916 :       appendCommandName("appendRole", cmd, do_validation_);
     857             : 
     858         916 :       cmd = (cmd % creator_account_id_ % account_id % role_name);
     859             : 
     860             :       auto str_args = [&account_id, &role_name] {
     861           4 :         return getQueryArgsStringBuilder()
     862           4 :             .append("account_id", account_id)
     863           4 :             .append("role_name", role_name)
     864           4 :             .finalize();
     865           0 :       };
     866             : 
     867         916 :       return executeQuery(sql_, cmd.str(), "AppendRole", std::move(str_args));
     868         916 :     }
     869             : 
     870             :     CommandResult PostgresCommandExecutor::operator()(
     871             :         const shared_model::interface::CreateAccount &command) {
     872        1120 :       auto &account_name = command.accountName();
     873        1120 :       auto &domain_id = command.domainId();
     874        1120 :       auto &pubkey = command.pubkey().hex();
     875             :       shared_model::interface::types::AccountIdType account_id =
     876        1120 :           account_name + "@" + domain_id;
     877             : 
     878        1120 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%')");
     879             : 
     880        1120 :       appendCommandName("createAccount", cmd, do_validation_);
     881             : 
     882        1120 :       cmd = (cmd % creator_account_id_ % account_id % domain_id % pubkey);
     883             : 
     884             :       auto str_args = [&account_id, &domain_id, &pubkey] {
     885           6 :         return getQueryArgsStringBuilder()
     886           6 :             .append("account_id", account_id)
     887           6 :             .append("domain_id", domain_id)
     888           6 :             .append("pubkey", pubkey)
     889           6 :             .finalize();
     890           0 :       };
     891             : 
     892        1120 :       return executeQuery(
     893        1120 :           sql_, cmd.str(), "CreateAccount", std::move(str_args));
     894        1120 :     }
     895             : 
     896             :     CommandResult PostgresCommandExecutor::operator()(
     897             :         const shared_model::interface::CreateAsset &command) {
     898         577 :       auto &domain_id = command.domainId();
     899         577 :       auto asset_id = command.assetName() + "#" + domain_id;
     900         577 :       int precision = command.precision();
     901         577 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', %5%)");
     902             : 
     903         577 :       appendCommandName("createAsset", cmd, do_validation_);
     904             : 
     905         577 :       cmd = (cmd % creator_account_id_ % asset_id % domain_id % precision);
     906             : 
     907             :       auto str_args = [&domain_id, &asset_id, precision] {
     908           7 :         return getQueryArgsStringBuilder()
     909           7 :             .append("domain_id", domain_id)
     910           7 :             .append("asset_id", asset_id)
     911           7 :             .append("precision", std::to_string(precision))
     912           7 :             .finalize();
     913           0 :       };
     914             : 
     915         577 :       return executeQuery(sql_, cmd.str(), "CreateAsset", std::move(str_args));
     916         577 :     }
     917             : 
     918             :     CommandResult PostgresCommandExecutor::operator()(
     919             :         const shared_model::interface::CreateDomain &command) {
     920         753 :       auto &domain_id = command.domainId();
     921         753 :       auto &default_role = command.userDefaultRole();
     922         753 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
     923             : 
     924         753 :       appendCommandName("createDomain", cmd, do_validation_);
     925             : 
     926         753 :       cmd = (cmd % creator_account_id_ % domain_id % default_role);
     927             : 
     928             :       auto str_args = [&domain_id, &default_role] {
     929           7 :         return getQueryArgsStringBuilder()
     930           7 :             .append("domain_id", domain_id)
     931           7 :             .append("default_role", default_role)
     932           7 :             .finalize();
     933           0 :       };
     934             : 
     935         753 :       return executeQuery(sql_, cmd.str(), "CreateDomain", std::move(str_args));
     936         753 :     }
     937             : 
     938             :     CommandResult PostgresCommandExecutor::operator()(
     939             :         const shared_model::interface::CreateRole &command) {
     940        1568 :       auto &role_id = command.roleName();
     941        1568 :       auto &permissions = command.rolePermissions();
     942        1568 :       auto perm_str = permissions.toBitstring();
     943        1568 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
     944             : 
     945        1568 :       appendCommandName("createRole", cmd, do_validation_);
     946             : 
     947        1568 :       cmd = (cmd % creator_account_id_ % role_id % perm_str);
     948             : 
     949             :       auto str_args = [&role_id, &perm_str] {
     950             :         // TODO [IR-1889] Akvinikym 21.11.18: integrate
     951             :         // PermissionSet::toString() instead of bit string, when it is created
     952           4 :         return getQueryArgsStringBuilder()
     953           4 :             .append("role_id", role_id)
     954           4 :             .append("perm_str", perm_str)
     955           4 :             .finalize();
     956           0 :       };
     957             : 
     958        1568 :       return executeQuery(sql_, cmd.str(), "CreateRole", std::move(str_args));
     959        1568 :     }
     960             : 
     961             :     CommandResult PostgresCommandExecutor::operator()(
     962             :         const shared_model::interface::DetachRole &command) {
     963         817 :       auto &account_id = command.accountId();
     964         817 :       auto &role_name = command.roleName();
     965         817 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
     966             : 
     967         817 :       appendCommandName("detachRole", cmd, do_validation_);
     968             : 
     969         817 :       cmd = (cmd % creator_account_id_ % account_id % role_name);
     970             : 
     971             :       auto str_args = [&account_id, &role_name] {
     972           4 :         return getQueryArgsStringBuilder()
     973           4 :             .append("account_id", account_id)
     974           4 :             .append("role_name", role_name)
     975           4 :             .finalize();
     976           0 :       };
     977             : 
     978         817 :       return executeQuery(sql_, cmd.str(), "DetachRole", std::move(str_args));
     979         817 :     }
     980             : 
     981             :     CommandResult PostgresCommandExecutor::operator()(
     982             :         const shared_model::interface::GrantPermission &command) {
     983          35 :       auto &permittee_account_id = command.accountId();
     984          35 :       auto permission = command.permissionName();
     985          35 :       auto perm = shared_model::interface::RolePermissionSet(
     986          35 :                       {shared_model::interface::permissions::permissionFor(
     987          35 :                           command.permissionName())})
     988          35 :                       .toBitstring();
     989             :       const auto perm_str =
     990          35 :           shared_model::interface::GrantablePermissionSet({permission})
     991          35 :               .toBitstring();
     992          35 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%')");
     993             : 
     994          35 :       appendCommandName("grantPermission", cmd, do_validation_);
     995             : 
     996          35 :       cmd =
     997          35 :           (cmd % creator_account_id_ % permittee_account_id % perm_str % perm);
     998             : 
     999             :       auto str_args = [&creator_account_id = creator_account_id_,
    1000          35 :                        &permittee_account_id,
    1001          35 :                        permission = perm_converter_->toString(permission)] {
    1002           9 :         return getQueryArgsStringBuilder()
    1003           9 :             .append("creator_account_id_", creator_account_id)
    1004           9 :             .append("permittee_account_id", permittee_account_id)
    1005           9 :             .append("permission", permission)
    1006           9 :             .finalize();
    1007           0 :       };
    1008             : 
    1009          35 :       return executeQuery(
    1010          35 :           sql_, cmd.str(), "GrantPermission", std::move(str_args));
    1011          35 :     }
    1012             : 
    1013             :     CommandResult PostgresCommandExecutor::operator()(
    1014             :         const shared_model::interface::RemoveSignatory &command) {
    1015          19 :       auto &account_id = command.accountId();
    1016          19 :       auto &pubkey = command.pubkey().hex();
    1017          19 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
    1018             : 
    1019          19 :       appendCommandName("removeSignatory", cmd, do_validation_);
    1020             : 
    1021          19 :       cmd = (cmd % creator_account_id_ % account_id % pubkey);
    1022             : 
    1023             :       auto str_args = [&account_id, &pubkey] {
    1024           9 :         return getQueryArgsStringBuilder()
    1025           9 :             .append("account_id", account_id)
    1026           9 :             .append("pubkey", pubkey)
    1027           9 :             .finalize();
    1028           0 :       };
    1029             : 
    1030          19 :       return executeQuery(
    1031          19 :           sql_, cmd.str(), "RemoveSignatory", std::move(str_args));
    1032          19 :     }
    1033             : 
    1034             :     CommandResult PostgresCommandExecutor::operator()(
    1035             :         const shared_model::interface::RevokePermission &command) {
    1036          11 :       auto &permittee_account_id = command.accountId();
    1037          11 :       auto permission = command.permissionName();
    1038             :       const auto without_perm_str =
    1039          11 :           shared_model::interface::GrantablePermissionSet()
    1040          11 :               .set()
    1041          11 :               .unset(permission)
    1042          11 :               .toBitstring();
    1043          11 :       const auto perms = shared_model::interface::GrantablePermissionSet()
    1044          11 :                              .set(permission)
    1045          11 :                              .toBitstring();
    1046             : 
    1047          11 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%')");
    1048             : 
    1049          11 :       appendCommandName("revokePermission", cmd, do_validation_);
    1050             : 
    1051          11 :       cmd = (cmd % creator_account_id_ % permittee_account_id % perms
    1052          11 :              % without_perm_str);
    1053             : 
    1054             :       auto str_args = [&creator_account_id = creator_account_id_,
    1055          11 :                        &permittee_account_id,
    1056          11 :                        permission = perm_converter_->toString(permission)] {
    1057           4 :         return getQueryArgsStringBuilder()
    1058           4 :             .append("creator_account_id_", creator_account_id)
    1059           4 :             .append("permittee_account_id", permittee_account_id)
    1060           4 :             .append("permission", permission)
    1061           4 :             .finalize();
    1062           0 :       };
    1063             : 
    1064          11 :       return executeQuery(
    1065          11 :           sql_, cmd.str(), "RevokePermission", std::move(str_args));
    1066          11 :     }
    1067             : 
    1068             :     CommandResult PostgresCommandExecutor::operator()(
    1069             :         const shared_model::interface::SetAccountDetail &command) {
    1070          89 :       auto &account_id = command.accountId();
    1071          89 :       auto &key = command.key();
    1072          89 :       auto &value = command.value();
    1073          89 :       if (creator_account_id_.empty()) {
    1074             :         // When creator is not known, it is genesis block
    1075           0 :         creator_account_id_ = "genesis";
    1076           0 :       }
    1077          89 :       std::string json = "{" + creator_account_id_ + "}";
    1078          89 :       std::string empty_json = "{}";
    1079          89 :       std::string filled_json = "{" + creator_account_id_ + ", " + key + "}";
    1080          89 :       std::string val = "\"" + value + "\"";
    1081             : 
    1082          89 :       auto cmd = boost::format(
    1083             :           "EXECUTE %1% ('%2%', '%3%', '%4%', '%5%', '%6%', '%7%')");
    1084             : 
    1085          89 :       appendCommandName("setAccountDetail", cmd, do_validation_);
    1086             : 
    1087          89 :       cmd = (cmd % creator_account_id_ % account_id % json % filled_json % val
    1088          89 :              % empty_json);
    1089             : 
    1090             :       auto str_args = [&account_id, &key, &value] {
    1091           5 :         return getQueryArgsStringBuilder()
    1092           5 :             .append("account_id", account_id)
    1093           5 :             .append("key", key)
    1094           5 :             .append("value", value)
    1095           5 :             .finalize();
    1096           0 :       };
    1097             : 
    1098          89 :       return executeQuery(
    1099          89 :           sql_, cmd.str(), "SetAccountDetail", std::move(str_args));
    1100          89 :     }
    1101             : 
    1102             :     CommandResult PostgresCommandExecutor::operator()(
    1103             :         const shared_model::interface::SetQuorum &command) {
    1104          18 :       auto &account_id = command.accountId();
    1105          18 :       int quorum = command.newQuorum();
    1106          18 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', %4%)");
    1107             : 
    1108          18 :       appendCommandName("setQuorum", cmd, do_validation_);
    1109             : 
    1110          18 :       cmd = (cmd % creator_account_id_ % account_id % quorum);
    1111             : 
    1112             :       auto str_args = [&account_id, quorum] {
    1113           4 :         return getQueryArgsStringBuilder()
    1114           4 :             .append("account_id", account_id)
    1115           4 :             .append("quorum", std::to_string(quorum))
    1116           4 :             .finalize();
    1117           0 :       };
    1118             : 
    1119          18 :       return executeQuery(sql_, cmd.str(), "SetQuorum", std::move(str_args));
    1120          18 :     }
    1121             : 
    1122             :     CommandResult PostgresCommandExecutor::operator()(
    1123             :         const shared_model::interface::SubtractAssetQuantity &command) {
    1124           9 :       auto &asset_id = command.assetId();
    1125           9 :       auto amount = command.amount().toStringRepr();
    1126           9 :       uint32_t precision = command.amount().precision();
    1127           9 :       auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', %4%, '%5%')");
    1128             : 
    1129           9 :       appendCommandName("subtractAssetQuantity", cmd, do_validation_);
    1130             : 
    1131           9 :       cmd = (cmd % creator_account_id_ % asset_id % precision % amount);
    1132             : 
    1133             :       auto str_args = [&creator_account_id = creator_account_id_,
    1134           9 :                        &asset_id,
    1135             :                        &amount,
    1136           9 :                        precision] {
    1137           7 :         return getQueryArgsStringBuilder()
    1138           7 :             .append("creator_account_id", creator_account_id)
    1139           7 :             .append("asset_id", asset_id)
    1140           7 :             .append("amount", amount)
    1141           7 :             .append("precision", std::to_string(precision))
    1142           7 :             .finalize();
    1143           0 :       };
    1144             : 
    1145           9 :       return executeQuery(
    1146           9 :           sql_, cmd.str(), "SubtractAssetQuantity", std::move(str_args));
    1147           9 :     }
    1148             : 
    1149             :     CommandResult PostgresCommandExecutor::operator()(
    1150             :         const shared_model::interface::TransferAsset &command) {
    1151          96 :       auto &src_account_id = command.srcAccountId();
    1152          96 :       auto &dest_account_id = command.destAccountId();
    1153          96 :       auto &asset_id = command.assetId();
    1154          96 :       auto amount = command.amount().toStringRepr();
    1155          96 :       uint32_t precision = command.amount().precision();
    1156             :       auto cmd =
    1157          96 :           boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%', %6%, '%7%')");
    1158             : 
    1159          96 :       appendCommandName("transferAsset", cmd, do_validation_);
    1160             : 
    1161          96 :       cmd = (cmd % creator_account_id_ % src_account_id % dest_account_id
    1162          96 :              % asset_id % precision % amount);
    1163             : 
    1164             :       auto str_args =
    1165             :           [&src_account_id, &dest_account_id, &asset_id, &amount, precision] {
    1166          15 :             return getQueryArgsStringBuilder()
    1167          15 :                 .append("src_account_id", src_account_id)
    1168          15 :                 .append("dest_account_id", dest_account_id)
    1169          15 :                 .append("asset_id", asset_id)
    1170          15 :                 .append("amount", amount)
    1171          15 :                 .append("precision", std::to_string(precision))
    1172          15 :                 .finalize();
    1173           0 :           };
    1174             : 
    1175          96 :       return executeQuery(
    1176          96 :           sql_, cmd.str(), "TransferAsset", std::move(str_args));
    1177          96 :     }
    1178             : 
    1179             :     void PostgresCommandExecutor::prepareStatements(soci::session &sql) {
    1180        4452 :       std::vector<PreparedStatement> statements;
    1181             : 
    1182       13356 :       statements.push_back(
    1183        4452 :           {"addAssetQuantity",
    1184        4452 :            addAssetQuantityBase,
    1185        4452 :            {(boost::format(R"(has_perm AS (%s),)")
    1186        4452 :              % checkAccountRolePermission(
    1187             :                    shared_model::interface::permissions::Role::kAddAssetQty,
    1188        4452 :                    "$1"))
    1189        4452 :                 .str(),
    1190        4452 :             "AND (SELECT * from has_perm)",
    1191        4452 :             "WHEN NOT (SELECT * from has_perm) THEN 2"}});
    1192             : 
    1193       13356 :       statements.push_back(
    1194        4452 :           {"addPeer",
    1195        4452 :            addPeerBase,
    1196        4452 :            {(boost::format(R"(has_perm AS (%s),)")
    1197        4452 :              % checkAccountRolePermission(
    1198        4452 :                    shared_model::interface::permissions::Role::kAddPeer, "$1"))
    1199        4452 :                 .str(),
    1200        4452 :             "WHERE (SELECT * FROM has_perm)",
    1201        4452 :             "WHEN NOT (SELECT * from has_perm) THEN 2"}});
    1202             : 
    1203       17808 :       statements.push_back(
    1204        4452 :           {"addSignatory",
    1205        4452 :            addSignatoryBase,
    1206        4452 :            {(boost::format(R"(
    1207             :                 has_perm AS (%s),)")
    1208        4452 :              % checkAccountHasRoleOrGrantablePerm(
    1209             :                    shared_model::interface::permissions::Role::kAddSignatory,
    1210             :                    shared_model::interface::permissions::Grantable::
    1211             :                        kAddMySignatory,
    1212        4452 :                    "$1",
    1213        4452 :                    "$2"))
    1214        4452 :                 .str(),
    1215        4452 :             " WHERE (SELECT * FROM has_perm)",
    1216        4452 :             " AND (SELECT * FROM has_perm)",
    1217        4452 :             "WHEN NOT (SELECT * from has_perm) THEN 2"}});
    1218             : 
    1219        4452 :       const auto bits = shared_model::interface::RolePermissionSet::size();
    1220        4452 :       const auto grantable_bits =
    1221        4452 :           shared_model::interface::GrantablePermissionSet::size();
    1222             : 
    1223       13356 :       statements.push_back(
    1224        4452 :           {"appendRole",
    1225        4452 :            appendRoleBase,
    1226        4452 :            {(boost::format(R"(
    1227             :             has_perm AS (%1%),
    1228             :             role_permissions AS (
    1229             :                 SELECT permission FROM role_has_permissions
    1230             :                 WHERE role_id = $3
    1231             :             ),
    1232             :             account_roles AS (
    1233             :                 SELECT role_id FROM account_has_roles WHERE account_id = $1
    1234             :             ),
    1235             :             account_has_role_permissions AS (
    1236             :                 SELECT COALESCE(bit_or(rp.permission), '0'::bit(%2%)) &
    1237             :                     (SELECT * FROM role_permissions) =
    1238             :                     (SELECT * FROM role_permissions)
    1239             :                 FROM role_has_permissions AS rp
    1240             :                 JOIN account_has_roles AS ar on ar.role_id = rp.role_id
    1241             :                 WHERE ar.account_id = $1
    1242             :             ),)")
    1243        4452 :              % checkAccountRolePermission(
    1244             :                    shared_model::interface::permissions::Role::kAppendRole,
    1245        4452 :                    "$1")
    1246        4452 :              % bits)
    1247        4452 :                 .str(),
    1248        4452 :             R"( WHERE
    1249             :                     EXISTS (SELECT * FROM account_roles) AND
    1250             :                     (SELECT * FROM account_has_role_permissions)
    1251             :                     AND (SELECT * FROM has_perm))",
    1252        4452 :             R"(
    1253             :                 WHEN NOT EXISTS (SELECT * FROM account_roles) THEN 2
    1254             :                 WHEN NOT (SELECT * FROM account_has_role_permissions) THEN 2
    1255             :                 WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
    1256             : 
    1257       13356 :       statements.push_back(
    1258        4452 :           {"createAccount",
    1259        4452 :            createAccountBase,
    1260        4452 :            {(boost::format(R"(
    1261             :             has_perm AS (%s),)")
    1262        4452 :              % checkAccountRolePermission(
    1263             :                    shared_model::interface::permissions::Role::kCreateAccount,
    1264        4452 :                    "$1"))
    1265        4452 :                 .str(),
    1266        4452 :             R"(AND (SELECT * FROM has_perm))",
    1267        4452 :             R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
    1268             : 
    1269       13356 :       statements.push_back(
    1270        4452 :           {"createAsset",
    1271        4452 :            createAssetBase,
    1272        4452 :            {(boost::format(R"(
    1273             :               has_perm AS (%s),)")
    1274        4452 :              % checkAccountRolePermission(
    1275             :                    shared_model::interface::permissions::Role::kCreateAsset,
    1276        4452 :                    "$1"))
    1277        4452 :                 .str(),
    1278        4452 :             R"(WHERE (SELECT * FROM has_perm))",
    1279        4452 :             R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
    1280             : 
    1281       13356 :       statements.push_back(
    1282        4452 :           {"createDomain",
    1283        4452 :            createDomainBase,
    1284        4452 :            {(boost::format(R"(
    1285             :               has_perm AS (%s),)")
    1286        4452 :              % checkAccountRolePermission(
    1287             :                    shared_model::interface::permissions::Role::kCreateDomain,
    1288        4452 :                    "$1"))
    1289        4452 :                 .str(),
    1290        4452 :             R"(WHERE (SELECT * FROM has_perm))",
    1291        4452 :             R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
    1292             : 
    1293       13356 :       statements.push_back(
    1294        4452 :           {"createRole",
    1295        4452 :            createRoleBase,
    1296        4452 :            {(boost::format(R"(
    1297             :           account_has_role_permissions AS (
    1298             :                 SELECT COALESCE(bit_or(rp.permission), '0'::bit(%s)) &
    1299             :                     $3 = $3
    1300             :                 FROM role_has_permissions AS rp
    1301             :                 JOIN account_has_roles AS ar on ar.role_id = rp.role_id
    1302             :                 WHERE ar.account_id = $1),
    1303             :           has_perm AS (%s),)")
    1304        4452 :              % bits
    1305        4452 :              % checkAccountRolePermission(
    1306             :                    shared_model::interface::permissions::Role::kCreateRole,
    1307        4452 :                    "$1"))
    1308        4452 :                 .str(),
    1309        4452 :             R"(WHERE (SELECT * FROM account_has_role_permissions)
    1310             :                           AND (SELECT * FROM has_perm))",
    1311        4452 :             R"(WHEN NOT (SELECT * FROM
    1312             :                                account_has_role_permissions) THEN 2
    1313             :                         WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
    1314             : 
    1315       13356 :       statements.push_back(
    1316        4452 :           {"detachRole",
    1317        4452 :            detachRoleBase,
    1318        4452 :            {(boost::format(R"(
    1319             :             has_perm AS (%s),)")
    1320        4452 :              % checkAccountRolePermission(
    1321             :                    shared_model::interface::permissions::Role::kDetachRole,
    1322        4452 :                    "$1"))
    1323        4452 :                 .str(),
    1324        4452 :             R"(AND (SELECT * FROM has_perm))",
    1325        4452 :             R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
    1326             : 
    1327       13356 :       statements.push_back({"grantPermission",
    1328        4452 :                             grantPermissionBase,
    1329        4452 :                             {(boost::format(R"(
    1330             :             has_perm AS (SELECT COALESCE(bit_or(rp.permission), '0'::bit(%1%))
    1331             :           & $4 = $4 FROM role_has_permissions AS rp
    1332             :               JOIN account_has_roles AS ar on ar.role_id = rp.role_id
    1333             :               WHERE ar.account_id = $1),)")
    1334        4452 :                               % bits)
    1335        4452 :                                  .str(),
    1336        4452 :                              R"( WHERE (SELECT * FROM has_perm))",
    1337        4452 :                              R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
    1338             : 
    1339       13356 :       statements.push_back(
    1340        4452 :           {"removeSignatory",
    1341        4452 :            removeSignatoryBase,
    1342        4452 :            {(boost::format(R"(
    1343             :           has_perm AS (%s),
    1344             :           get_account AS (
    1345             :               SELECT quorum FROM account WHERE account_id = $2 LIMIT 1
    1346             :            ),
    1347             :           get_signatories AS (
    1348             :               SELECT public_key FROM account_has_signatory
    1349             :               WHERE account_id = $2
    1350             :           ),
    1351             :           get_signatory AS (
    1352             :               SELECT * FROM get_signatories
    1353             :               WHERE public_key = $3
    1354             :           ),
    1355             :           check_account_signatories AS (
    1356             :               SELECT quorum FROM get_account
    1357             :               WHERE quorum < (SELECT COUNT(*) FROM get_signatories)
    1358             :           ),
    1359             :           )")
    1360        4452 :              % checkAccountHasRoleOrGrantablePerm(
    1361             :                    shared_model::interface::permissions::Role::kRemoveSignatory,
    1362             :                    shared_model::interface::permissions::Grantable::
    1363             :                        kRemoveMySignatory,
    1364        4452 :                    "$1",
    1365        4452 :                    "$2"))
    1366        4452 :                 .str(),
    1367        4452 :             R"(
    1368             :               AND (SELECT * FROM has_perm)
    1369             :               AND EXISTS (SELECT * FROM get_account)
    1370             :               AND EXISTS (SELECT * FROM get_signatories)
    1371             :               AND EXISTS (SELECT * FROM check_account_signatories)
    1372             :           )",
    1373        4452 :             R"(
    1374             :               WHEN NOT EXISTS (SELECT * FROM get_account) THEN 3
    1375             :               WHEN NOT (SELECT * FROM has_perm) THEN 2
    1376             :               WHEN NOT EXISTS (SELECT * FROM get_signatory) THEN 4
    1377             :               WHEN NOT EXISTS (SELECT * FROM check_account_signatories) THEN 5
    1378             :           )"}});
    1379             : 
    1380       13356 :       statements.push_back({"revokePermission",
    1381        4452 :                             revokePermissionBase,
    1382        4452 :                             {(boost::format(R"(
    1383             :             has_perm AS (SELECT COALESCE(bit_or(permission), '0'::bit(%1%))
    1384             :           & $3 = $3 FROM account_has_grantable_permissions
    1385             :               WHERE account_id = $1 AND
    1386             :               permittee_account_id = $2),)")
    1387        4452 :                               % grantable_bits)
    1388        4452 :                                  .str(),
    1389        4452 :                              R"( AND (SELECT * FROM has_perm))",
    1390        4452 :                              R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
    1391             : 
    1392       13356 :       statements.push_back(
    1393        4452 :           {"setAccountDetail",
    1394        4452 :            setAccountDetailBase,
    1395        4452 :            {(boost::format(R"(
    1396             :               has_role_perm AS (%s),
    1397             :               has_grantable_perm AS (%s),
    1398             :               has_perm AS (SELECT CASE
    1399             :                                WHEN (SELECT * FROM has_grantable_perm) THEN true
    1400             :                                WHEN ($1 = $2) THEN true
    1401             :                                WHEN (SELECT * FROM has_role_perm) THEN true
    1402             :                                ELSE false END
    1403             :               ),
    1404             :               )")
    1405        4452 :              % checkAccountRolePermission(
    1406        4452 :                    shared_model::interface::permissions::Role::kSetDetail, "$1")
    1407        4452 :              % checkAccountGrantablePermission(
    1408             :                    shared_model::interface::permissions::Grantable::
    1409             :                        kSetMyAccountDetail,
    1410        4452 :                    "$1",
    1411        4452 :                    "$2"))
    1412        4452 :                 .str(),
    1413        4452 :             R"( AND (SELECT * FROM has_perm))",
    1414        4452 :             R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
    1415             : 
    1416       17808 :       statements.push_back(
    1417        4452 :           {"setQuorum",
    1418        4452 :            setQuorumBase,
    1419        4452 :            {R"( get_signatories AS (
    1420             :                     SELECT public_key FROM account_has_signatory
    1421             :                     WHERE account_id = $2
    1422             :                 ),
    1423             :                 check_account_signatories AS (
    1424             :                     SELECT 1 FROM account
    1425             :                     WHERE $3 <= (SELECT COUNT(*) FROM get_signatories)
    1426             :                     AND account_id = $2
    1427             :                 ),)",
    1428        4452 :             (boost::format(R"(
    1429             :           has_perm AS (%s),)")
    1430        4452 :              % checkAccountHasRoleOrGrantablePerm(
    1431             :                    shared_model::interface::permissions::Role::kSetQuorum,
    1432             :                    shared_model::interface::permissions::Grantable::
    1433             :                        kSetMyQuorum,
    1434        4452 :                    "$1",
    1435        4452 :                    "$2"))
    1436        4452 :                 .str(),
    1437        4452 :             R"(AND EXISTS
    1438             :               (SELECT * FROM get_signatories)
    1439             :               AND EXISTS (SELECT * FROM check_account_signatories)
    1440             :               AND (SELECT * FROM has_perm))",
    1441        4452 :             R"(
    1442             :               WHEN NOT (SELECT * FROM has_perm) THEN 2
    1443             :               WHEN NOT EXISTS (SELECT * FROM get_signatories) THEN 4
    1444             :               WHEN NOT EXISTS (SELECT * FROM check_account_signatories) THEN 5
    1445             :               )"}});
    1446             : 
    1447       13356 :       statements.push_back(
    1448        4452 :           {"subtractAssetQuantity",
    1449        4452 :            subtractAssetQuantityBase,
    1450        4452 :            {(boost::format(R"(
    1451             :                has_perm AS (%s),)")
    1452        4452 :              % checkAccountRolePermission(shared_model::interface::permissions::
    1453             :                                               Role::kSubtractAssetQty,
    1454        4452 :                                           "$1"))
    1455        4452 :                 .str(),
    1456        4452 :             R"( AND (SELECT * FROM has_perm))",
    1457        4452 :             R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
    1458             : 
    1459       17808 :       statements.push_back(
    1460        4452 :           {"transferAsset",
    1461        4452 :            transferAssetBase,
    1462        4452 :            {(boost::format(R"(
    1463             :               has_role_perm AS (%s),
    1464             :               has_grantable_perm AS (%s),
    1465             :               dest_can_receive AS (%s),
    1466             :               has_perm AS (SELECT
    1467             :                                CASE WHEN (SELECT * FROM dest_can_receive) THEN
    1468             :                                    CASE WHEN NOT ($1 = $2) THEN
    1469             :                                        CASE WHEN (SELECT * FROM has_grantable_perm)
    1470             :                                            THEN true
    1471             :                                        ELSE false END
    1472             :                                    ELSE
    1473             :                                         CASE WHEN (SELECT * FROM has_role_perm)
    1474             :                                             THEN true
    1475             :                                         ELSE false END
    1476             :                                    END
    1477             :                                ELSE false END
    1478             :               ),
    1479             :               )")
    1480        4452 :              % checkAccountRolePermission(
    1481        4452 :                    shared_model::interface::permissions::Role::kTransfer, "$1")
    1482        4452 :              % checkAccountGrantablePermission(
    1483             :                    shared_model::interface::permissions::Grantable::
    1484             :                        kTransferMyAssets,
    1485        4452 :                    "$1",
    1486        4452 :                    "$2")
    1487        4452 :              % checkAccountRolePermission(
    1488        4452 :                    shared_model::interface::permissions::Role::kReceive, "$3"))
    1489        4452 :                 .str(),
    1490        4452 :             R"( AND (SELECT * FROM has_perm))",
    1491        4452 :             R"( AND (SELECT * FROM has_perm))",
    1492        4452 :             R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
    1493             : 
    1494       75684 :       for (const auto &st : statements) {
    1495       71232 :         prepareStatement(sql, st);
    1496             :       }
    1497        4452 :     };
    1498             : 
    1499             :   }  // namespace ametsuchi
    1500             : }  // namespace iroha

Generated by: LCOV version 1.13