diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8f77eea4848b735b3489d93238e3335e36cf04c5..02c895db00b140cf0014c89bb3f33988884e6c8d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -50,6 +50,7 @@ info:
     - echo "$CPPLIB_REGISTRY_IMAGE"
     - echo "$CPPINTTEST_PIPELINE"
     - echo "$GIT_SUBMODULE_STRATEGY"
+    - echo "$CI_COMMIT_REF_NAME"
 
 # Build a docker image in which tests for this repository can run
 build-testenv:
@@ -98,16 +99,21 @@ trigger_inttest:
 
     ## Determine the cppinttest branch...
     # ... use an f-branch if posible...
+    - F_BRANCH=dev
     - if echo "$CI_COMMIT_REF_NAME" | grep -c "^f-" ; then
         CPPINT_REF=$CI_COMMIT_REF_NAME ;
+        F_BRANCH=$CI_COMMIT_REF_NAME ;
       fi;
     # ... or use main if possible...
     - if [[ "$CI_COMMIT_REF_NAME" == "main" ]] ; then
         CPPINT_REF=main ;
       fi
+    - if echo "$CI_COMMIT_REF_NAME" | grep -c "^v" ; then
+        CPPINT_REF=main ;
+        F_BRANCH=main ;
+      fi
     # ... and fall-back to dev
     - CPPINT_REF=${CPPINT_REF:-dev}
-    - F_BRANCH=$CI_COMMIT_REF_NAME
 
     - echo "Triggering caosdb-cppinttest@${CPPINT_REF} (F_BRANCH=$F_BRANCH)"
     - curl -w "%{stderr}HTTPCODE=%{http_code}" -X POST
diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h
index 90044e1d8bf8cf99604184bff83fffb9c9b79b27..f479683e434329f6c13799c6f8a59f866e149b36 100644
--- a/include/caosdb/transaction.h
+++ b/include/caosdb/transaction.h
@@ -215,7 +215,7 @@ private:
 class MultiResultSet : public ResultSet {
 public:
   ~MultiResultSet() = default;
-  explicit MultiResultSet(MultiTransactionResponse *response);
+  explicit MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set);
   [[nodiscard]] inline auto Size() const noexcept -> int override {
     return this->entities.size();
   }
@@ -384,6 +384,17 @@ public:
     return *result_set;
   }
 
+  /**
+   * Return the result of a count query
+   *
+   * Only meaningful if there was exactly one COUNT query executed in
+   * this transaction. In all other cases, the return value will be
+   * -1.
+   */
+  [[nodiscard]] inline auto GetCountResult() const -> int {
+    return query_count;
+  }
+
   /**
    * Return the number of sub-requests in this transaction.
    *
@@ -394,6 +405,23 @@ public:
     return this->request->requests_size();
   }
 
+  /**
+   * Get a JSON representation of the respone.
+   *
+   * For debugging.
+   */
+  inline auto ResponseToString() const -> const std::string {
+    google::protobuf::util::JsonOptions options;
+    std::string out;
+    google::protobuf::util::MessageToJsonString(*this->response, &out, options);
+    return out;
+  }
+
+  /**
+   * Get a JSON representation of the request.
+   *
+   * For debugging.
+   */
   inline auto RequestToString() const -> const std::string {
     google::protobuf::util::JsonOptions options;
     std::string out;
@@ -411,6 +439,7 @@ private:
   MultiTransactionRequest *request;
   mutable MultiTransactionResponse *response;
   std::string error_message;
+  mutable long query_count;
 };
 
 template <class InputIterator>
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
index c32d0090392e9b8c44cc8d1e0886f69747337066..5d5387c6f5d0e51cbd9dacdec6db259e140a762b 100644
--- a/src/caosdb/transaction.cpp
+++ b/src/caosdb/transaction.cpp
@@ -105,6 +105,8 @@ using caosdb::entity::v1alpha1::RegisterFileUploadRequest;
 using caosdb::entity::v1alpha1::RegisterFileUploadResponse;
 using WrappedResponseCase =
   caosdb::entity::v1alpha1::TransactionResponse::WrappedResponseCase;
+using QueryResponseCase =
+  caosdb::entity::v1alpha1::RetrieveResponse::QueryResponseCase;
 using caosdb::utility::get_arena;
 using grpc::ClientAsyncResponseReader;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
@@ -140,28 +142,8 @@ auto ResultSet::end() const -> ResultSet::iterator {
   return ResultSet::iterator(this, Size());
 }
 
-MultiResultSet::MultiResultSet(MultiTransactionResponse *response) {
-  auto *responses = response->mutable_responses();
-  for (auto sub_response : *responses) {
-    switch (sub_response.wrapped_response_case()) {
-    case WrappedResponseCase::kRetrieveResponse:
-      this->entities.push_back(std::make_unique<Entity>(
-        sub_response.mutable_retrieve_response()->release_entity()));
-      break;
-    case WrappedResponseCase::kInsertResponse:
-      this->entities.push_back(
-        std::make_unique<Entity>(sub_response.release_insert_response()));
-      break;
-    case WrappedResponseCase::kDeleteResponse:
-      this->entities.push_back(
-        std::make_unique<Entity>(sub_response.release_insert_response()));
-      break;
-    default:
-      // TODO(tf) Updates
-      break;
-    }
-  }
-}
+MultiResultSet::MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set)
+  : entities(std::move(result_set)) {}
 
 [[nodiscard]] auto UniqueResult::GetEntity() const -> const Entity & {
   const Entity *result = this->entity.get();
@@ -177,9 +159,7 @@ Transaction::Transaction(
       get_arena())) {
   this->entity_service = std::move(entity_service);
   this->file_service = std::move(file_service);
-}
-
-auto Transaction::RetrieveById(const std::string &id) noexcept -> StatusCode {
+  this->query_count = -1;
   ASSERT_CAN_ADD_RETRIEVAL
 
   auto *sub_request = this->request->add_requests();
@@ -317,12 +297,29 @@ auto Transaction::WaitForIt() const noexcept -> TransactionStatus {
     auto *responses = this->response->mutable_responses(0);
     switch (responses->wrapped_response_case()) {
     case WrappedResponseCase::kRetrieveResponse: {
-      auto *entity = responses->mutable_retrieve_response()->release_entity();
-      if (!entity->errors().empty()) {
-        this->status = TransactionStatus::TRANSACTION_ERROR(
-          "The request returned with errors.");
+      auto *retrieve_response = responses->mutable_retrieve_response();
+      switch (retrieve_response->query_response_case()) {
+      case QueryResponseCase::kEntity: {
+        auto *entity = retrieve_response->release_entity();
+        if (!entity->errors().empty()) {
+          this->status = TransactionStatus::TRANSACTION_ERROR(
+            "The request returned with errors.");
+        }
+        this->result_set = std::make_unique<UniqueResult>(entity);
+      } break;
+      case QueryResponseCase::kSelectResult: {
+        // TODO(tf) Select queries
+      } break;
+      case QueryResponseCase::kCountResult: {
+        this->query_count = retrieve_response->count_result();
+        std::vector<std::unique_ptr<Entity>> entities;
+        this->result_set =
+          std::make_unique<MultiResultSet>(std::move(entities));
+      } break;
+      default:
+        // TODO(tf) Error
+        break;
       }
-      this->result_set = std::make_unique<UniqueResult>(entity);
     } break;
     case WrappedResponseCase::kUpdateResponse: {
       auto *updatedIdResponse = responses->mutable_update_response();
@@ -353,7 +350,28 @@ auto Transaction::WaitForIt() const noexcept -> TransactionStatus {
       break;
     }
   } else {
-    this->result_set = std::make_unique<MultiResultSet>(this->response);
+    auto *responses = this->response->mutable_responses();
+    std::vector<std::unique_ptr<Entity>> entities;
+    for (auto sub_response : *responses) {
+      switch (sub_response.wrapped_response_case()) {
+      case WrappedResponseCase::kRetrieveResponse:
+        entities.push_back(std::make_unique<Entity>(
+          sub_response.mutable_retrieve_response()->release_entity()));
+        break;
+      case WrappedResponseCase::kInsertResponse:
+        entities.push_back(
+          std::make_unique<Entity>(sub_response.release_insert_response()));
+        break;
+      case WrappedResponseCase::kDeleteResponse:
+        entities.push_back(
+          std::make_unique<Entity>(sub_response.release_insert_response()));
+        break;
+      default:
+        // TODO(tf) Updates
+        break;
+      }
+    }
+    this->result_set = std::make_unique<MultiResultSet>(std::move(entities));
   }
 
   return this->status;
diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp
index 557ded7b87fe9947391bb47f6c37b49ee1824cfb..86d3294e13166867ef7a409f9ceb7513753d6bae 100644
--- a/test/test_transaction.cpp
+++ b/test/test_transaction.cpp
@@ -31,6 +31,7 @@
 #include "gtest/gtest_pred_impl.h"     // for Test, TestInfo, TEST
 #include <memory>                      // for allocator, unique_ptr
 #include <string>                      // for string, basic_string
+#include <utility>                     // for move
 #include <vector>                      // for vector
 
 namespace caosdb::transaction {
@@ -39,6 +40,7 @@ using caosdb::connection::Connection;
 using caosdb::exceptions::ConnectionError;
 using caosdb::transaction::UniqueResult;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
+using caosdb::entity::v1alpha1::RetrieveResponse;
 
 TEST(test_transaction, create_transaction) {
   const auto *host = "localhost";
@@ -92,21 +94,18 @@ TEST(test_transaction, test_retrieve_by_ids) {
 }
 
 TEST(test_transaction, test_multi_result_set_empty) {
-  MultiTransactionResponse response;
-
-  MultiResultSet rs(&response);
+  std::vector<std::unique_ptr<Entity>> empty;
+  MultiResultSet rs(std::move(empty));
   EXPECT_EQ(rs.Size(), 0);
 }
 
 TEST(test_transaction, test_multi_result_iterator) {
-  MultiTransactionResponse response;
-
-  response.add_responses()
-    ->mutable_retrieve_response()
-    ->mutable_entity()
-    ->set_id("100");
+  std::vector<std::unique_ptr<Entity>> one_elem;
+  RetrieveResponse response;
+  response.mutable_entity()->set_id("100");
+  one_elem.push_back(std::make_unique<Entity>(response.release_entity()));
 
-  MultiResultSet rs(&response);
+  MultiResultSet rs(std::move(one_elem));
   EXPECT_EQ(rs.Size(), 1);
 
   for (const Entity &entity : rs) {
@@ -126,18 +125,19 @@ TEST(test_transaction, test_unique_result_iterator) {
 }
 
 TEST(test_transaction, test_multi_result_set_one) {
-  MultiTransactionResponse response;
-  response.add_responses()
-    ->mutable_retrieve_response()
-    ->mutable_entity()
-    ->set_id("100");
+  std::vector<std::unique_ptr<Entity>> one_elem;
+  RetrieveResponse response;
+  response.mutable_entity()->set_id("100");
+  one_elem.push_back(std::make_unique<Entity>(response.release_entity()));
 
-  MultiResultSet rs(&response);
+  MultiResultSet rs(std::move(one_elem));
   EXPECT_EQ(rs.Size(), 1);
   EXPECT_EQ(rs.At(0).GetId(), "100");
 }
 
 TEST(test_transaction, test_multi_result_set_three) {
+  std::vector<std::unique_ptr<Entity>> three_elem;
+
   MultiTransactionResponse response;
   response.add_responses()
     ->mutable_retrieve_response()
@@ -152,7 +152,14 @@ TEST(test_transaction, test_multi_result_set_three) {
     ->mutable_entity()
     ->set_id("102");
 
-  MultiResultSet rs(&response);
+  auto *responses = response.mutable_responses();
+  std::vector<std::unique_ptr<Entity>> entities;
+  for (auto sub_response : *responses) {
+    three_elem.push_back(std::make_unique<Entity>(
+      sub_response.mutable_retrieve_response()->release_entity()));
+  }
+
+  MultiResultSet rs(std::move(three_elem));
   EXPECT_EQ(rs.Size(), 3);
   EXPECT_TRUE(rs.At(1).HasErrors());
 }