diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 739f99446c9a3169349c8bc17a614135f788a793..0e534d4e4d72985e54c4470e8a6189eafcde95bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -191,6 +191,9 @@ trigger_octavelib: trigger: project: caosdb/src/caosdb-octavelib branch: $OCTAVELIB_REF + # It's ok if octave fails in case of features that might already be + # implemented in proto, server, and cpplib, but not yet in Octave. + allow_failure: true # ... and for Julia. trigger_julialib: @@ -210,6 +213,9 @@ trigger_julialib: trigger: project: caosdb/src/caosdb-julialib branch: $JULIALIB_REF + # It's ok if julia fails in case of features that might already be + # implemented in proto, server, and cpplib, but not yet in Julia. + allow_failure: true # Build the sphinx documentation and make it ready for deployment by Gitlab Pages # Special job for serving a static website. See https://docs.gitlab.com/ee/ci/yaml/README.html#pages diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 7c73e5991c9369cca78349fa0d8d243971c75497..7dcd73afc2e172db348e63f31760d19973779f38 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Support for `SELECT` queries. + ### Changed ### Deprecated diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 29c27eaf536434d2f870fc111ca00f5febec1327..31f0e81d6a8fc8e55a6005b86d7160c834a2dfd7 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -35,6 +35,8 @@ set(libcaosdb_INCL ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/logging.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/message_code.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/protobuf_helper.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/result_table.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/result_set.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/status_code.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction_handler.h diff --git a/include/caosdb/result_set.h b/include/caosdb/result_set.h new file mode 100644 index 0000000000000000000000000000000000000000..2ce70e67e770a93e86512c564954ea57d10046eb --- /dev/null +++ b/include/caosdb/result_set.h @@ -0,0 +1,125 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#ifndef CAOSDB_RESULT_SET_H +#define CAOSDB_RESULT_SET_H + +#include "caosdb/entity.h" // for Entity, FileDe... +#include <algorithm> // for max +#include <iterator> // for iterator, next +#include <memory> // for unique_ptr +#include <utility> // for move +#include <vector> // for vector +// IWYU pragma: no_include <ext/alloc_traits.h> + +namespace caosdb::transaction { +using caosdb::entity::Entity; + +/** + * Abstract base class for the results of a Transaction. + */ +class ResultSet { + class iterator; + +public: + virtual ~ResultSet() = default; + [[nodiscard]] virtual auto size() const noexcept -> int = 0; + [[nodiscard]] virtual auto at(const int index) const -> const Entity & = 0; + [[nodiscard]] virtual auto mutable_at(int index) const -> Entity * = 0; + /** + * Return the Entity at the given index. + * + * This method releases the entity from the underlying collection and thus + * leaves the ResultSet in a corrupted state. + * + * This method can be called only once for each index. + */ + [[nodiscard]] virtual auto release_at(int index) -> Entity * = 0; + auto begin() const -> iterator; + auto end() const -> iterator; + +private: + class iterator : public std::iterator<std::output_iterator_tag, Entity> { + public: + explicit iterator(const ResultSet *result_set, int index = 0); + auto operator*() const -> const Entity &; + auto operator++() -> iterator &; + auto operator++(int) -> iterator; + auto operator!=(const iterator &rhs) const -> bool; + + private: + int current_index = 0; + const ResultSet *result_set; + }; +}; + +class AbstractMultiResultSet : public ResultSet { +public: + /** + * Copy Constructor. + * + * Copies the underlying collection of entities. + */ + inline AbstractMultiResultSet(const AbstractMultiResultSet &original) { + for (const Entity &entity : original) { + this->items.push_back(std::make_unique<Entity>(entity)); + } + } + virtual ~AbstractMultiResultSet() = default; + inline explicit AbstractMultiResultSet(std::vector<std::unique_ptr<Entity>> result_set) + : items(std::move(result_set)) {} + [[nodiscard]] inline auto size() const noexcept -> int override { return this->items.size(); } + [[nodiscard]] inline auto at(const int index) const -> const Entity & override { + return *(this->items.at(index)); + } + [[nodiscard]] inline auto mutable_at(int index) const -> Entity * override { + return this->items.at(index).get(); + } + /** + * Return the Entity at the given index. + * + * This method releases the entity from the underlying collection and thus + * leaves the ResultSet in a corrupted state. + * + * This method can be called only once for each index. + */ + [[nodiscard]] inline auto release_at(int index) -> Entity * override { + return this->items.at(index).release(); + } + /** + * Remove all entities from this result set. + */ + inline auto clear() noexcept -> void { this->items.clear(); } + +protected: + std::vector<std::unique_ptr<Entity>> items; +}; + +/** + * Container with results of a transaction. + */ +class MultiResultSet : public AbstractMultiResultSet { +public: + ~MultiResultSet() = default; + explicit MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set); +}; + +} // namespace caosdb::transaction +#endif diff --git a/include/caosdb/result_table.h b/include/caosdb/result_table.h new file mode 100644 index 0000000000000000000000000000000000000000..6795e27e5b60a680c1700d9fa98bcdd50b059474 --- /dev/null +++ b/include/caosdb/result_table.h @@ -0,0 +1,124 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#ifndef CAOSDB_RESULT_TABLE_H +#define CAOSDB_RESULT_TABLE_H + +#include "caosdb/value.h" // for Value +#include <iterator> // for iterator, next +#include <memory> // for unique_ptr +#include <string> // for string + +namespace caosdb::transaction { +using caosdb::entity::Value; + +class ResultTableImpl; + +class ResultTableColumnImpl; + +class ResultTableRowImpl; + +class ResultTableRow { +public: + [[nodiscard]] auto GetValue(const std::string &column) const noexcept -> Value; + friend class ResultTable; + explicit ResultTableRow(std::unique_ptr<ResultTableRowImpl> delegate); + +private: + std::unique_ptr<ResultTableRowImpl> delegate; +}; + +class ResultTableColumn { +public: + /** + * Get the name of the column. + */ + [[nodiscard]] auto GetName() const noexcept -> const std::string &; + + friend class ResultTable; + explicit ResultTableColumn(std::unique_ptr<ResultTableColumnImpl> delegate); + +private: + std::unique_ptr<ResultTableColumnImpl> delegate; +}; + +class ResultTable { + class HeaderIterator; + class RowIterator; + +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 -> HeaderIterator; + /** + * Get the data rows, i.e. the actual data. + */ + [[nodiscard]] auto GetRows() const noexcept -> RowIterator; + + friend class ResultTableImpl; + +private: + class HeaderIterator : std::iterator<std::output_iterator_tag, ResultTableColumn> { + public: + explicit HeaderIterator(const ResultTable *result_table, int index = 0); + HeaderIterator(const HeaderIterator &other); + 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; + }; + + class RowIterator : std::iterator<std::output_iterator_tag, ResultTableRow> { + public: + explicit RowIterator(const ResultTable *result_table, int index = 0); + RowIterator(const RowIterator &other); + auto operator*() const -> const ResultTableRow &; + auto operator++() -> RowIterator &; + auto operator++(int) -> RowIterator; + auto operator!=(const RowIterator &rhs) const -> bool; + auto size() const noexcept -> int; + auto begin() const -> RowIterator; + auto end() const -> RowIterator; + + private: + int current_index = 0; + const ResultTable *result_table; + }; + + explicit ResultTable(std::unique_ptr<ResultTableImpl> delegate); + std::unique_ptr<ResultTableImpl> delegate; +}; + +} // namespace caosdb::transaction +#endif diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index e73840e4f2aa355961f653b24817784a2773313f..b1d3bee8f740b1bcbb62c5f8fe924a937cbdf36c 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -30,20 +30,20 @@ #include "caosdb/logging.h" // for CAOSDB_LOG_ERR... #include "caosdb/protobuf_helper.h" // for get_arena #include "caosdb/status_code.h" // for StatusCode +#include "caosdb/result_set.h" // for ResultSet +#include "caosdb/result_table.h" // for ResultTable #include "caosdb/transaction_status.h" // for StatusCode -#include <algorithm> // for max +#include "caosdb/value.h" // for Value #include <future> // for async, future #include <google/protobuf/arena.h> // for Arena #include <google/protobuf/util/json_util.h> // for MessageToJsonS... #include <grpcpp/completion_queue.h> // for CompletionQueue #include <iterator> // for iterator, next #include <map> // for map -// IWYU pragma: no_include <ext/alloc_traits.h> -#include <memory> // for unique_ptr -#include <mutex> // for mutex -#include <string> // for string -#include <utility> // for move -#include <vector> // for vector +#include <memory> // for unique_ptr +#include <mutex> // for mutex +#include <string> // for string +#include <vector> // for vector /** * Do all necessary checks and assure that another retrieval (by id or by @@ -166,6 +166,7 @@ namespace caosdb::transaction { using caosdb::entity::Entity; using caosdb::entity::FileDescriptor; +using caosdb::entity::Value; using caosdb::entity::v1::EntityResponse; using caosdb::entity::v1::EntityTransactionService; using caosdb::entity::v1::FileDownloadRequest; @@ -187,95 +188,6 @@ using google::protobuf::Arena; class Transaction; -/** - * Abstract base class for the results of a Transaction. - */ -class ResultSet { - class iterator; - -public: - virtual ~ResultSet() = default; - [[nodiscard]] virtual auto size() const noexcept -> int = 0; - [[nodiscard]] virtual auto at(const int index) const -> const Entity & = 0; - [[nodiscard]] virtual auto mutable_at(int index) const -> Entity * = 0; - /** - * Return the Entity at the given index. - * - * This method releases the entity from the underlying collection and thus - * leaves the ResultSet in a corrupted state. - * - * This method can be called only once for each index. - */ - [[nodiscard]] virtual auto release_at(int index) -> Entity * = 0; - auto begin() const -> iterator; - auto end() const -> iterator; - -private: - class iterator : public std::iterator<std::output_iterator_tag, Entity> { - public: - explicit iterator(const ResultSet *result_set, int index = 0); - auto operator*() const -> const Entity &; - auto operator++() -> iterator &; - auto operator++(int) -> iterator; - auto operator!=(const iterator &rhs) const -> bool; - - private: - int current_index = 0; - const ResultSet *result_set; - }; -}; - -class AbstractMultiResultSet : public ResultSet { -public: - /** - * Copy Constructor. - * - * Copies the underlying collection of entities. - */ - inline AbstractMultiResultSet(const AbstractMultiResultSet &original) { - for (const Entity &entity : original) { - this->items.push_back(std::make_unique<Entity>(entity)); - } - } - virtual ~AbstractMultiResultSet() = default; - inline explicit AbstractMultiResultSet(std::vector<std::unique_ptr<Entity>> result_set) - : items(std::move(result_set)) {} - [[nodiscard]] inline auto size() const noexcept -> int override { return this->items.size(); } - [[nodiscard]] inline auto at(const int index) const -> const Entity & override { - return *(this->items.at(index)); - } - [[nodiscard]] inline auto mutable_at(int index) const -> Entity * override { - return this->items.at(index).get(); - } - /** - * Return the Entity at the given index. - * - * This method releases the entity from the underlying collection and thus - * leaves the ResultSet in a corrupted state. - * - * This method can be called only once for each index. - */ - [[nodiscard]] inline auto release_at(int index) -> Entity * override { - return this->items.at(index).release(); - } - /** - * Remove all entities from this result set. - */ - inline auto clear() noexcept -> void { this->items.clear(); } - -protected: - std::vector<std::unique_ptr<Entity>> items; -}; - -/** - * Container with results of a transaction. - */ -class MultiResultSet : public AbstractMultiResultSet { -public: - ~MultiResultSet() = default; - explicit MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set); -}; - /** * @brief Create a transaction via `CaosDBConnection.createTransaction()` */ @@ -422,6 +334,24 @@ 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."; + } 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 +477,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/include/caosdb/value.h b/include/caosdb/value.h index a92847a82d3cd15ad2dda61e64c6843b99ac6d75..cffb11162b9ed8b83508316c155f1fca46be2714 100644 --- a/include/caosdb/value.h +++ b/include/caosdb/value.h @@ -52,14 +52,6 @@ using ScalarValueCase = caosdb::entity::v1::ScalarValue::ScalarValueCase; class ScalarValue; class Value; -// Represents special values which are otherwise hard to tranfer via protobuf. -enum SpecialValue { - // Represent the NULL value. - NULL_VALUE = ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED, - // The empty string. - EMPTY_STRING = ProtoSpecialValue::SPECIAL_VALUE_EMPTY_STRING, -}; - /** * Pure abstract base class for values. */ @@ -297,6 +289,9 @@ public: this->wrapped->CopyFrom(*value.GetProtoValue()); } explicit inline Value(ProtoValue *wrapped) : ScalarProtoMessageWrapper<ProtoValue>(wrapped) {} + explicit inline Value(const ProtoValue &value) : ScalarProtoMessageWrapper<ProtoValue>() { + this->wrapped->CopyFrom(value); + } explicit inline Value(const std::string &value) : ScalarProtoMessageWrapper<ProtoValue>() { this->wrapped->mutable_scalar_value()->set_string_value(value); } diff --git a/proto b/proto index 0b301401cf28d7a3edeb2c55e418f072b83cf5a7..96e7a1fb667ed1bb3b2602af6c69724519bf5118 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 0b301401cf28d7a3edeb2c55e418f072b83cf5a7 +Subproject commit 96e7a1fb667ed1bb3b2602af6c69724519bf5118 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f64bc7671cbe142390ca5439916dfbddd3660294..939a85ba1f0492a6d985d4f873f2560adb22f039 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,9 @@ set(libcaosdb_SRC ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/configuration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/protobuf_helper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/result_set.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/result_table.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/result_table_impl.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction_handler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/utility.cpp @@ -45,6 +48,5 @@ IF(BUILD_ACM) ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/user_impl.h ) ENDIF() - # pass variable to parent scope set(libcaosdb_SRC ${libcaosdb_SRC} PARENT_SCOPE) diff --git a/src/caosdb/result_set.cpp b/src/caosdb/result_set.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4172a836d4e8bb9ed5e568439556ebace53c4e4 --- /dev/null +++ b/src/caosdb/result_set.cpp @@ -0,0 +1,55 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021-2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021-2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#include "caosdb/result_set.h" // for ResultSet +#include <memory> // for unique_ptr +#include <utility> // for move, pair + +namespace caosdb::transaction { + +ResultSet::iterator::iterator(const ResultSet *result_set_param, int index) + : current_index(index), result_set(result_set_param) {} + +auto ResultSet::iterator::operator*() const -> const Entity & { + return this->result_set->at(current_index); +} + +auto ResultSet::iterator::operator++() -> ResultSet::iterator & { + current_index++; + return *this; +} + +auto ResultSet::iterator::operator++(int) -> ResultSet::iterator { + iterator tmp(*this); + operator++(); + return tmp; +} + +auto ResultSet::iterator::operator!=(const iterator &rhs) const -> bool { + return this->current_index != rhs.current_index; +} + +auto ResultSet::begin() const -> ResultSet::iterator { return ResultSet::iterator(this, 0); } + +auto ResultSet::end() const -> ResultSet::iterator { return ResultSet::iterator(this, size()); } + +MultiResultSet::MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set) + : AbstractMultiResultSet(std::move(result_set)) {} + +} // namespace caosdb::transaction diff --git a/src/caosdb/result_table.cpp b/src/caosdb/result_table.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f7d5c3e3564fdad9b7f9755c77c6ddce37d3c25f --- /dev/null +++ b/src/caosdb/result_table.cpp @@ -0,0 +1,182 @@ + +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021-2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021-2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#include "caosdb/result_table.h" // for ResultTable, ResultTab... +#include "caosdb/entity/v1/main.pb.h" // for SelectQueryResult, Sel... +#include "caosdb/protobuf_helper.h" // for ScalarProtoMessageWrapper +#include "caosdb/result_table_impl.h" // for ResultTableImpl, Resul... +#include "caosdb/value.h" // for Value +#include <algorithm> // for max +#include <google/protobuf/repeated_field.h> // IWYU pragma: keep for RepeatedPtrField +#include <memory> // for unique_ptr +#include <string> // for string, operator== +#include <utility> // for move +#include <vector> // for vector +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" + +namespace caosdb::transaction { +using caosdb::entity::Value; +using ProtoSelectQueryResult = caosdb::entity::v1::SelectQueryResult; +using ProtoSelectQueryHeader = caosdb::entity::v1::SelectQueryHeader; +using ProtoSelectQueryColumn = caosdb::entity::v1::SelectQueryColumn; +using ProtoSelectQueryRow = caosdb::entity::v1::SelectQueryRow; +using caosdb::utility::ScalarProtoMessageWrapper; + +ResultTableRowImpl::ResultTableRowImpl(ProtoSelectQueryResult *table, int row) + : header(*table->mutable_header()), row(*table->mutable_data_rows(row)) {} + +auto ResultTableRowImpl::GetColumnIndex(const std::string &column_name) const noexcept -> int { + for (int i = 0; i < this->header.columns_size(); i++) { + if (this->header.columns(i).name() == column_name) { + return i; + } + } + return -1; +} + +auto ResultTableRowImpl::GetValue(const std::string &column) const noexcept -> Value { + const auto column_index = GetColumnIndex(column); + if (column_index == -1) { + // NULL_VALUE + return {}; + } + Value result(this->row.cells(column_index)); + return result; +} + +ResultTableRow::ResultTableRow(std::unique_ptr<ResultTableRowImpl> delegate) + : delegate(std::move(delegate)) {} + +auto ResultTableRow::GetValue(const std::string &column) const noexcept -> Value { + return this->delegate->GetValue(column); +} + +ResultTableColumnImpl::ResultTableColumnImpl(ProtoSelectQueryColumn *column) + : ScalarProtoMessageWrapper<ProtoSelectQueryColumn>(column) {} + +ResultTableColumn::ResultTableColumn(std::unique_ptr<ResultTableColumnImpl> delegate) + : delegate(std::move(delegate)) {} + +auto ResultTableColumn::GetName() const noexcept -> const std::string & { + return this->delegate->wrapped->name(); +} + +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) { + for (auto &column : *this->wrapped->mutable_header()->mutable_columns()) { + this->columns.emplace_back( + std::unique_ptr<ResultTableColumnImpl>(new ResultTableColumnImpl(&column))); + } + for (int i = 0; i < this->wrapped->data_rows_size(); i++) { + this->rows.emplace_back( + std::unique_ptr<ResultTableRowImpl>(new ResultTableRowImpl(this->wrapped, i))); + } +} + +ResultTable::ResultTable(std::unique_ptr<ResultTableImpl> delegate) + : delegate(std::move(delegate)) {} + +auto ResultTable::size() const noexcept -> int { return this->delegate->wrapped->data_rows_size(); } + +auto ResultTable::GetRows() const noexcept -> RowIterator { return RowIterator(this, 0); } + +ResultTable::RowIterator::RowIterator(const ResultTable *result_table_param, int index) + : current_index(index), result_table(result_table_param) {} + +ResultTable::RowIterator::RowIterator(const RowIterator &other) + : current_index(other.current_index), result_table(other.result_table) {} + +auto ResultTable::RowIterator::size() const noexcept -> int { + return this->result_table->delegate->wrapped->data_rows_size(); +} + +auto ResultTable::RowIterator::operator*() const -> const ResultTableRow & { + return this->result_table->delegate->rows.at(this->current_index); +} + +auto ResultTable::RowIterator::operator++() -> RowIterator & { + current_index++; + return *this; +} + +auto ResultTable::RowIterator::operator++(int) -> RowIterator { + RowIterator tmp(*this); + operator++(); + return tmp; +} + +auto ResultTable::RowIterator::operator!=(const RowIterator &rhs) const -> bool { + return this->current_index != rhs.current_index; +} + +auto ResultTable::RowIterator::begin() const -> RowIterator { + return RowIterator(this->result_table); +} + +auto ResultTable::RowIterator::end() const -> RowIterator { + return RowIterator(this->result_table, size()); +} + +auto ResultTable::GetHeader() const noexcept -> HeaderIterator { return HeaderIterator(this, 0); } + +ResultTable::HeaderIterator::HeaderIterator(const ResultTable *result_table_param, int index) + : current_index(index), result_table(result_table_param) {} + +ResultTable::HeaderIterator::HeaderIterator(const HeaderIterator &other) + : current_index(other.current_index), result_table(other.result_table) {} + +auto ResultTable::HeaderIterator::size() const noexcept -> int { + return this->result_table->delegate->wrapped->header().columns_size(); +} + +auto ResultTable::HeaderIterator::operator*() const -> const ResultTableColumn & { + return this->result_table->delegate->columns.at(this->current_index); +} + +auto ResultTable::HeaderIterator::operator++() -> HeaderIterator & { + current_index++; + return *this; +} + +auto ResultTable::HeaderIterator::operator++(int) -> HeaderIterator { + HeaderIterator tmp(*this); + operator++(); + return tmp; +} + +auto ResultTable::HeaderIterator::operator!=(const HeaderIterator &rhs) const -> bool { + return this->current_index != rhs.current_index; +} + +auto ResultTable::HeaderIterator::begin() const -> HeaderIterator { + return HeaderIterator(this->result_table); +} + +auto ResultTable::HeaderIterator::end() const -> HeaderIterator { + return HeaderIterator(this->result_table, size()); +} + +} // namespace caosdb::transaction diff --git a/src/caosdb/result_table_impl.h b/src/caosdb/result_table_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..3450a68cf7ae0638d42d3ea1c79fd9d181a3eaab --- /dev/null +++ b/src/caosdb/result_table_impl.h @@ -0,0 +1,84 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021-2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021-2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#ifndef CAOSDB_RESULT_TABLE_IMPL_H +#define CAOSDB_RESULT_TABLE_IMPL_H + +#include "caosdb/transaction.h" +#include "caosdb/entity/v1/main.grpc.pb.h" // for EntityTransac... +#include "caosdb/entity/v1/main.pb.h" // for TransactionRe... +#include "caosdb/file_transmission/download_request_handler.h" // Download... +#include "caosdb/file_transmission/file_reader.h" // for path +#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 +#include <filesystem> // for operator<<, path +#include <future> // for async, future +#include <google/protobuf/arena.h> // for Arena +#include <grpc/grpc.h> // for gpr_timespec +#include <map> // for map, operator!= +#include <memory> // for unique_ptr +#include <random> // for mt19937, rand... +#include <system_error> // for std::system_error +#include <utility> // for move, pair + +namespace caosdb::transaction { +using caosdb::entity::Value; +using ProtoSelectQueryResult = caosdb::entity::v1::SelectQueryResult; +using ProtoSelectQueryHeader = caosdb::entity::v1::SelectQueryHeader; +using ProtoSelectQueryColumn = caosdb::entity::v1::SelectQueryColumn; +using ProtoSelectQueryRow = caosdb::entity::v1::SelectQueryRow; +using caosdb::utility::ScalarProtoMessageWrapper; + +class ResultTableRowImpl { + explicit ResultTableRowImpl(ProtoSelectQueryResult *table, int row); + [[nodiscard]] auto GetValue(const std::string &column) const noexcept -> Value; + [[nodiscard]] auto GetColumnIndex(const std::string &column) const noexcept -> int; + friend class ResultTableRow; + friend class ResultTableImpl; + ProtoSelectQueryHeader &header; + ProtoSelectQueryRow &row; +}; + +class ResultTableColumnImpl : public ScalarProtoMessageWrapper<ProtoSelectQueryColumn> { + explicit ResultTableColumnImpl(ProtoSelectQueryColumn *column); + friend class ResultTableColumn; + friend class ResultTableImpl; +}; + +class ResultTableImpl : public ScalarProtoMessageWrapper<ProtoSelectQueryResult> { + static auto create(ProtoSelectQueryResult *select_result) -> std::unique_ptr<ResultTable>; + ResultTableImpl(); + explicit ResultTableImpl(ProtoSelectQueryResult *result_table); + std::vector<ResultTableColumn> columns; + std::vector<ResultTableRow> rows; + + friend class ResultTable; + friend class ResultTable::HeaderIterator; + friend class ResultTableColumn; + friend auto ProcessSelectResponse(ProtoSelectQueryResult *select_result) + -> std::unique_ptr<ResultTable>; +}; + +} // namespace caosdb::transaction +#endif diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index ca8338dce4e4b721202e72c3fdecf58339746ee0..28f9e35fc1a70083d07c99e7f8a4fabfbe58bc32 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -25,6 +25,10 @@ #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/result_set.h" // for ResultSet +#include "caosdb/result_table.h" // for ResultTable +#include "caosdb/result_table_impl.h" // for ResultTableImpl #include "caosdb/status_code.h" // for StatusCode #include "caosdb/transaction_handler.h" // for EntityTransactionHandler #include <algorithm> // for max @@ -38,7 +42,6 @@ #include <random> // for mt19937, rand... #include <system_error> // for std::system_error #include <utility> // for move, pair -// IWYU pragma: no_include <bits/exception.h> // IWYU pragma: no_include <cxxabi.h> // IWYU pragma: no_include "net/proto2/public/repeated_field.h" @@ -51,41 +54,13 @@ 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 google::protobuf::Arena; using NextStatus = grpc::CompletionQueue::NextStatus; using RegistrationStatus = caosdb::entity::v1::RegistrationStatus; -ResultSet::iterator::iterator(const ResultSet *result_set_param, int index) - : current_index(index), result_set(result_set_param) {} - -auto ResultSet::iterator::operator*() const -> const Entity & { - return this->result_set->at(current_index); -} - -auto ResultSet::iterator::operator++() -> ResultSet::iterator & { - current_index++; - return *this; -} - -auto ResultSet::iterator::operator++(int) -> ResultSet::iterator { - iterator tmp(*this); - operator++(); - return tmp; -} - -auto ResultSet::iterator::operator!=(const iterator &rhs) const -> bool { - return this->current_index != rhs.current_index; -} - -auto ResultSet::begin() const -> ResultSet::iterator { return ResultSet::iterator(this, 0); } - -auto ResultSet::end() const -> ResultSet::iterator { return ResultSet::iterator(this, size()); } - -MultiResultSet::MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set) - : AbstractMultiResultSet(std::move(result_set)) {} - Transaction::Transaction(std::shared_ptr<EntityTransactionService::Stub> entity_service, std::shared_ptr<FileTransmissionService::Stub> file_service) : entity_service(std::move(entity_service)), file_service(std::move(file_service)), @@ -318,6 +293,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 +308,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(); diff --git a/test/test_issues.cpp b/test/test_issues.cpp index b7a6cc8fe7df2bbcf8b2db0902c57c7146b77faf..6422da5f4c86128be44286d853d768cd58db3c8c 100644 --- a/test/test_issues.cpp +++ b/test/test_issues.cpp @@ -19,6 +19,7 @@ */ #include "caosdb/configuration.h" // for InsecureConnectionConfig... #include "caosdb/connection.h" // for Connection +#include "caosdb/result_set.h" // for ResultSet #include "caosdb/status_code.h" // for StatusCode, EXECUTING #include "caosdb/transaction.h" // for Transaction #include "caosdb/transaction_status.h" // for StatusCode diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp index 77d457c6eee2c5e9d95c53c95c1b2f0a713dcff4..ed4dcd2aa72e3e857bc871f401504147b330385b 100644 --- a/test/test_transaction.cpp +++ b/test/test_transaction.cpp @@ -22,6 +22,7 @@ #include "caosdb/entity.h" // for Entity #include "caosdb/entity/v1/main.pb.h" // for Entity #include "caosdb/exceptions.h" // for ConnectionError +#include "caosdb/result_set.h" // for MultiResultSet, Entity, Resu... #include "caosdb/status_code.h" // for StatusCode #include "caosdb/transaction.h" // for Transaction #include "caosdb/transaction_handler.h" // for MultiTransactionResponse