diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index e73840e4f2aa355961f653b24817784a2773313f..002a9d3eaca381732b0c0104cb4f076d6eab2fe6 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -185,6 +185,54 @@ using TransactionResponseCase = caosdb::entity::v1::TransactionResponse::Transac using caosdb::utility::get_arena; using google::protobuf::Arena; +class ResultTableImpl; + +class ResultTableColumn { +public: + /** + * Get the name of the column. + */ + [[nodiscard]] auto GetName() const noexcept -> const std::string &; +}; + +class ResultTable { + class HeaderIterator; + +public: + /** + * Number of rows. + * + * The header is not counted as a row. + */ + [[nodiscard]] auto size() const noexcept -> int; + /** + * Get the header of this table, i.e. the list of columns. + */ + [[nodiscard]] auto GetHeader() const noexcept -> const HeaderIterator; + + friend class ResultTableImpl; + +private: + class HeaderIterator : std::iterator<std::output_iterator_tag, ResultTableColumn> { + public: + explicit HeaderIterator(const ResultTable *result_table, int index = 0); + auto operator*() const -> const ResultTableColumn &; + auto operator++() -> HeaderIterator &; + auto operator++(int) -> HeaderIterator; + auto operator!=(const HeaderIterator &rhs) const -> bool; + auto size() const noexcept -> int; + auto begin() const -> HeaderIterator; + auto end() const -> HeaderIterator; + + private: + int current_index = 0; + const ResultTable *result_table; + }; + + explicit ResultTable(std::unique_ptr<ResultTableImpl> delegate); + std::unique_ptr<ResultTableImpl> delegate; +}; + class Transaction; /** @@ -422,6 +470,28 @@ public: return *(this->result_set.get()); } + /** + * Return the ResultTable of a SELECT query. + */ + [[nodiscard]] inline auto GetResultTable() const noexcept -> const ResultTable & { + if (this->GetStatus().GetCode() < 0) { + CAOSDB_LOG_ERROR(logger_name) + << "GetResultTable was called before the transaction has terminated. This is a programming " + "error of the code which uses the transaction."; + // TODO(tf) This is a really bad SegFault factory. When the transaction + // terminates and the result_set is being overriden, the unique_ptr + // created here will be deleted and any client of the return ResultTable + // will have a SegFault. + } else if (this->GetStatus().GetCode() == StatusCode::SPOILED) { + CAOSDB_LOG_ERROR(logger_name) + << "GetResultTable was called on a \"spoiled\" transaction. That means " + "that the result table has already been released via " + "ReleaseResultTable(). This is a programming error of the code which " + "uses the transaction."; + } + return *(this->result_table.get()); + } + /** * Return the ResultSet of this transaction. * @@ -547,6 +617,7 @@ private: std::string error_message; mutable long query_count; mutable std::unique_ptr<ResultSet> result_set; + mutable std::unique_ptr<ResultTable> result_table; }; template <class InputIterator> diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index ca8338dce4e4b721202e72c3fdecf58339746ee0..59c94387558f29813c28ad17c487af4513d22d56 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -25,6 +25,7 @@ #include "caosdb/file_transmission/register_file_upload_handler.h" // for RegisterFileUploadHandler #include "caosdb/file_transmission/upload_request_handler.h" // Upload... #include "caosdb/logging.h" // for CAOSDB_LOG_FATAL +#include "caosdb/protobuf_helper.h" // for ProtoMessageWrapper #include "caosdb/status_code.h" // for StatusCode #include "caosdb/transaction_handler.h" // for EntityTransactionHandler #include <algorithm> // for max @@ -51,12 +52,47 @@ using TransactionResponseCase = caosdb::entity::v1::TransactionResponse::Transac using RetrieveResponseCase = caosdb::entity::v1::RetrieveResponse::RetrieveResponseCase; using RetrieveResponse = caosdb::entity::v1::RetrieveResponse; using ProtoEntity = caosdb::entity::v1::Entity; +using ProtoSelectQueryResult = caosdb::entity::v1::SelectQueryResult; using caosdb::entity::v1::EntityRequest; +using caosdb::utility::ScalarProtoMessageWrapper; using google::protobuf::Arena; using NextStatus = grpc::CompletionQueue::NextStatus; using RegistrationStatus = caosdb::entity::v1::RegistrationStatus; + +class ResultTableImpl : public ScalarProtoMessageWrapper<ProtoSelectQueryResult> { + static auto create(ProtoSelectQueryResult *select_result) -> std::unique_ptr<ResultTable>; + ResultTableImpl(); + explicit ResultTableImpl(ProtoSelectQueryResult *result_table); + + friend class ResultTable; + friend auto ProcessSelectResponse(ProtoSelectQueryResult *select_result) -> std::unique_ptr<ResultTable>; +}; + +auto ResultTableImpl::create(ProtoSelectQueryResult *select_result) -> std::unique_ptr<ResultTable> { + return std::unique_ptr<ResultTable>(new ResultTable(std::unique_ptr<ResultTableImpl>(new ResultTableImpl(select_result)))); +} + +ResultTableImpl::ResultTableImpl(ProtoSelectQueryResult *result_table) : ScalarProtoMessageWrapper<ProtoSelectQueryResult>(result_table) {} + +ResultTable::ResultTable(std::unique_ptr<ResultTableImpl> delegate) : delegate(std::move(delegate)) {} + +auto ResultTable::size() const noexcept -> int { + // TODO + return 0; +} + +ResultTable::HeaderIterator::HeaderIterator(const ResultTable *result_table_param, int index) : current_index(index), result_table(result_table_param) {} + +auto ResultTable::GetHeader() const noexcept -> const HeaderIterator { + return HeaderIterator(this, 0); +} + +auto ResultTable::HeaderIterator::size() const noexcept -> int { + return this->result_table->delegate->wrapped->header().columns_size(); +} + ResultSet::iterator::iterator(const ResultSet *result_set_param, int index) : current_index(index), result_set(result_set_param) {} @@ -318,6 +354,10 @@ auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { return StatusCode::EXECUTING; } +auto ProcessSelectResponse(ProtoSelectQueryResult *select_result) -> std::unique_ptr<ResultTable> { + return ResultTableImpl::create(select_result); +} + auto Transaction::ProcessRetrieveResponse(RetrieveResponse *retrieve_response, std::vector<std::unique_ptr<Entity>> *entities, bool *set_error) const noexcept @@ -329,9 +369,7 @@ auto Transaction::ProcessRetrieveResponse(RetrieveResponse *retrieve_response, result = std::make_unique<Entity>(retrieve_entity_response); } break; case RetrieveResponseCase::kSelectResult: { - CAOSDB_LOG_ERROR(logger_name) << "Results of a SELECT query cannot be " - "processed by this client yet."; - // TODO(tf) Select queries + this->result_table = ProcessSelectResponse(retrieve_response->mutable_select_result()); } break; case RetrieveResponseCase::kCountResult: { this->query_count = retrieve_response->count_result();