diff --git a/native/cpp/CommonCpp/DatabaseManagers/CMakeLists.txt b/native/cpp/CommonCpp/DatabaseManagers/CMakeLists.txt
--- a/native/cpp/CommonCpp/DatabaseManagers/CMakeLists.txt
+++ b/native/cpp/CommonCpp/DatabaseManagers/CMakeLists.txt
@@ -7,6 +7,9 @@
   "DatabaseManager.h"
   "DatabaseQueryExecutor.h"
   "SQLiteQueryExecutor.h"
+  "entities/SQLiteStatementWrapper.h"
+  "entities/EntityQueryHelpers.h"
+  "entities/SQLiteDataConverters.h"
   "entities/Draft.h"
   "entities/Media.h"
   "entities/Message.h"
@@ -18,6 +21,8 @@
 
 set(DBM_SRCS
   "SQLiteQueryExecutor.cpp"
+  "entities/SQLiteDataConverters.cpp"
+  "entities/SQLiteStatementWrapper.cpp"
 )
 
 add_library(comm-databasemanagers
diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
--- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
+++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
@@ -1,6 +1,7 @@
 #include "SQLiteQueryExecutor.h"
 #include "Logger.h"
 
+#include "entities/EntityQueryHelpers.h"
 #include "entities/KeyserverInfo.h"
 #include "entities/Metadata.h"
 #include "entities/UserInfo.h"
@@ -1147,7 +1148,11 @@
 }
 
 std::vector<Draft> SQLiteQueryExecutor::getAllDrafts() const {
-  return SQLiteQueryExecutor::getStorage().get_all<Draft>();
+  static std::string getAllDraftsSQL =
+      "SELECT * "
+      "FROM drafts;";
+  return getAllEntities<Draft>(
+      SQLiteQueryExecutor::getConnection(), getAllDraftsSQL);
 }
 
 void SQLiteQueryExecutor::removeAllDrafts() const {
@@ -1166,69 +1171,34 @@
 
 std::vector<std::pair<Message, std::vector<Media>>>
 SQLiteQueryExecutor::getAllMessages() const {
-
-  auto rows = SQLiteQueryExecutor::getStorage().select(
-      columns(
-          &Message::id,
-          &Message::local_id,
-          &Message::thread,
-          &Message::user,
-          &Message::type,
-          &Message::future_type,
-          &Message::content,
-          &Message::time,
-          &Media::id,
-          &Media::container,
-          &Media::thread,
-          &Media::uri,
-          &Media::type,
-          &Media::extras),
-      left_join<Media>(on(c(&Message::id) == &Media::container)),
-      order_by(&Message::id));
-
+  static std::string getAllMessagesSQL =
+      "SELECT * "
+      "FROM messages "
+      "LEFT JOIN media "
+      "   ON messages.id = media.container "
+      "ORDER BY messages.id;";
+  SQLiteStatementWrapper preparedSQL(
+      SQLiteQueryExecutor::getConnection(),
+      getAllMessagesSQL,
+      "Failed to retrieve all messages.");
+
+  std::string prevMsgIdx{};
   std::vector<std::pair<Message, std::vector<Media>>> allMessages;
-  allMessages.reserve(rows.size());
-
-  std::string prev_msg_idx{};
-  for (auto &row : rows) {
-    auto msg_id = std::get<0>(row);
-    if (msg_id == prev_msg_idx) {
-      allMessages.back().second.push_back(Media{
-          std::get<8>(row),
-          std::move(std::get<9>(row)),
-          std::move(std::get<10>(row)),
-          std::move(std::get<11>(row)),
-          std::move(std::get<12>(row)),
-          std::move(std::get<13>(row)),
-      });
+
+  for (int stepResult = sqlite3_step(preparedSQL); stepResult == SQLITE_ROW;
+       stepResult = sqlite3_step(preparedSQL)) {
+    Message message = Message::fromSQLResult(preparedSQL, 0);
+    if (message.id == prevMsgIdx) {
+      allMessages.back().second.push_back(Media::fromSQLResult(preparedSQL, 8));
     } else {
+      prevMsgIdx = message.id;
       std::vector<Media> mediaForMsg;
-      if (!std::get<8>(row).empty()) {
-        mediaForMsg.push_back(Media{
-            std::get<8>(row),
-            std::move(std::get<9>(row)),
-            std::move(std::get<10>(row)),
-            std::move(std::get<11>(row)),
-            std::move(std::get<12>(row)),
-            std::move(std::get<13>(row)),
-        });
+      if (sqlite3_column_type(preparedSQL, 8) != SQLITE_NULL) {
+        mediaForMsg.push_back(Media::fromSQLResult(preparedSQL, 8));
       }
-      allMessages.push_back(std::make_pair(
-          Message{
-              msg_id,
-              std::move(std::get<1>(row)),
-              std::move(std::get<2>(row)),
-              std::move(std::get<3>(row)),
-              std::get<4>(row),
-              std::move(std::get<5>(row)),
-              std::move(std::get<6>(row)),
-              std::get<7>(row)},
-          mediaForMsg));
-
-      prev_msg_idx = msg_id;
+      allMessages.push_back(std::make_pair(std::move(message), mediaForMsg));
     }
   }
-
   return allMessages;
 }
 
@@ -1305,11 +1275,19 @@
 
 std::vector<MessageStoreThread>
 SQLiteQueryExecutor::getAllMessageStoreThreads() const {
-  return SQLiteQueryExecutor::getStorage().get_all<MessageStoreThread>();
+  static std::string getAllMessageStoreThreadsSQL =
+      "SELECT * "
+      "FROM message_store_threads;";
+  return getAllEntities<MessageStoreThread>(
+      SQLiteQueryExecutor::getConnection(), getAllMessageStoreThreadsSQL);
 }
 
 std::vector<Thread> SQLiteQueryExecutor::getAllThreads() const {
-  return SQLiteQueryExecutor::getStorage().get_all<Thread>();
+  static std::string getAllThreadsSQL =
+      "SELECT * "
+      "FROM threads;";
+  return getAllEntities<Thread>(
+      SQLiteQueryExecutor::getConnection(), getAllThreadsSQL);
 };
 
 void SQLiteQueryExecutor::removeThreads(std::vector<std::string> ids) const {
@@ -1340,7 +1318,11 @@
 }
 
 std::vector<Report> SQLiteQueryExecutor::getAllReports() const {
-  return SQLiteQueryExecutor::getStorage().get_all<Report>();
+  static std::string getAllReportsSQL =
+      "SELECT * "
+      "FROM reports;";
+  return getAllEntities<Report>(
+      SQLiteQueryExecutor::getConnection(), getAllReportsSQL);
 }
 
 void SQLiteQueryExecutor::setPersistStorageItem(
@@ -1393,11 +1375,19 @@
 }
 
 std::vector<KeyserverInfo> SQLiteQueryExecutor::getAllKeyservers() const {
-  return SQLiteQueryExecutor::getStorage().get_all<KeyserverInfo>();
+  static std::string getAllKeyserversSQL =
+      "SELECT * "
+      "FROM keyservers;";
+  return getAllEntities<KeyserverInfo>(
+      SQLiteQueryExecutor::getConnection(), getAllKeyserversSQL);
 }
 
 std::vector<UserInfo> SQLiteQueryExecutor::getAllUsers() const {
-  return SQLiteQueryExecutor::getStorage().get_all<UserInfo>();
+  static std::string getAllUsersSQL =
+      "SELECT * "
+      "FROM users;";
+  return getAllEntities<UserInfo>(
+      SQLiteQueryExecutor::getConnection(), getAllUsersSQL);
 }
 
 void SQLiteQueryExecutor::beginTransaction() const {
@@ -1414,13 +1404,20 @@
 
 std::vector<OlmPersistSession>
 SQLiteQueryExecutor::getOlmPersistSessionsData() const {
-  return SQLiteQueryExecutor::getStorage().get_all<OlmPersistSession>();
+  static std::string getAllOlmPersistSessionsSQL =
+      "SELECT * "
+      "FROM olm_persist_sessions;";
+  return getAllEntities<OlmPersistSession>(
+      SQLiteQueryExecutor::getConnection(), getAllOlmPersistSessionsSQL);
 }
 
 std::optional<std::string>
 SQLiteQueryExecutor::getOlmPersistAccountData() const {
-  std::vector<OlmPersistAccount> result =
-      SQLiteQueryExecutor::getStorage().get_all<OlmPersistAccount>();
+  static std::string getAllOlmPersistAccountSQL =
+      "SELECT * "
+      "FROM olm_persist_account;";
+  std::vector<OlmPersistAccount> result = getAllEntities<OlmPersistAccount>(
+      SQLiteQueryExecutor::getConnection(), getAllOlmPersistAccountSQL);
   if (result.size() > 1) {
     throw std::system_error(
         ECANCELED,
@@ -1480,7 +1477,7 @@
 
 #ifdef EMSCRIPTEN
 std::vector<WebThread> SQLiteQueryExecutor::getAllThreadsWeb() const {
-  auto threads = SQLiteQueryExecutor::getStorage().get_all<Thread>();
+  auto threads = this->getAllThreads();
   std::vector<WebThread> webThreads;
   webThreads.reserve(threads.size());
   for (const auto &thread : threads) {
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/Draft.h b/native/cpp/CommonCpp/DatabaseManagers/entities/Draft.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/Draft.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/Draft.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -7,6 +9,11 @@
 struct Draft {
   std::string key;
   std::string text;
+
+  static Draft fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return Draft{
+        getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)};
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/EntityQueryHelpers.h b/native/cpp/CommonCpp/DatabaseManagers/entities/EntityQueryHelpers.h
new file mode 100644
--- /dev/null
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/EntityQueryHelpers.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "SQLiteStatementWrapper.h"
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+namespace comm {
+
+template <typename T>
+std::vector<T> getAllEntities(sqlite3 *db, std::string getAllEntitiesSQL) {
+  SQLiteStatementWrapper preparedSQL(
+      db, getAllEntitiesSQL, "Failed to retrieve entities.");
+  std::vector<T> allEntities;
+
+  for (int stepResult = sqlite3_step(preparedSQL); stepResult == SQLITE_ROW;
+       stepResult = sqlite3_step(preparedSQL)) {
+    allEntities.emplace_back(T::fromSQLResult(preparedSQL, 0));
+  }
+  return allEntities;
+}
+
+} // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/KeyserverInfo.h b/native/cpp/CommonCpp/DatabaseManagers/entities/KeyserverInfo.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/KeyserverInfo.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/KeyserverInfo.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -7,6 +9,11 @@
 struct KeyserverInfo {
   std::string id;
   std::string keyserver_info;
+
+  static KeyserverInfo fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return KeyserverInfo{
+        getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)};
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/Media.h b/native/cpp/CommonCpp/DatabaseManagers/entities/Media.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/Media.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/Media.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -11,6 +13,16 @@
   std::string uri;
   std::string type;
   std::string extras;
+
+  static Media fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return Media{
+        getStringFromSQLRow(sqlRow, idx),
+        getStringFromSQLRow(sqlRow, idx + 1),
+        getStringFromSQLRow(sqlRow, idx + 2),
+        getStringFromSQLRow(sqlRow, idx + 3),
+        getStringFromSQLRow(sqlRow, idx + 4),
+        getStringFromSQLRow(sqlRow, idx + 5)};
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/Message.h b/native/cpp/CommonCpp/DatabaseManagers/entities/Message.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/Message.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/Message.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <sqlite3.h>
 #include <memory>
 #include <string>
 
@@ -14,6 +15,18 @@
   std::unique_ptr<int> future_type;
   std::unique_ptr<std::string> content;
   int64_t time;
+
+  static Message fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return Message{
+        getStringFromSQLRow(sqlRow, idx),
+        getStringPtrFromSQLRow(sqlRow, idx + 1),
+        getStringFromSQLRow(sqlRow, idx + 2),
+        getStringFromSQLRow(sqlRow, idx + 3),
+        getIntFromSQLRow(sqlRow, idx + 4),
+        getIntPtrFromSQLRow(sqlRow, idx + 5),
+        getStringPtrFromSQLRow(sqlRow, idx + 6),
+        getInt64FromSQLRow(sqlRow, idx + 7)};
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/MessageStoreThread.h b/native/cpp/CommonCpp/DatabaseManagers/entities/MessageStoreThread.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/MessageStoreThread.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/MessageStoreThread.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -7,6 +9,13 @@
 struct MessageStoreThread {
   std::string id;
   int start_reached;
+
+  static MessageStoreThread fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return MessageStoreThread{
+        getStringFromSQLRow(sqlRow, idx),
+        getIntFromSQLRow(sqlRow, idx + 1),
+    };
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistAccount.h b/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistAccount.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistAccount.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistAccount.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -7,6 +9,11 @@
 struct OlmPersistAccount {
   int id;
   std::string account_data;
+
+  static OlmPersistAccount fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return OlmPersistAccount{
+        getIntFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)};
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistSession.h b/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistSession.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistSession.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/OlmPersistSession.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -7,6 +9,11 @@
 struct OlmPersistSession {
   std::string target_user_id;
   std::string session_data;
+
+  static OlmPersistSession fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return OlmPersistSession{
+        getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)};
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/Report.h b/native/cpp/CommonCpp/DatabaseManagers/entities/Report.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/Report.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/Report.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -7,6 +9,11 @@
 struct Report {
   std::string id;
   std::string report;
+
+  static Report fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return Report{
+        getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)};
+  }
 };
 
 } // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteDataConverters.h b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteDataConverters.h
new file mode 100644
--- /dev/null
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteDataConverters.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <sqlite3.h>
+#include <memory>
+#include <string>
+
+namespace comm {
+std::string getStringFromSQLRow(sqlite3_stmt *sqlRow, int idx);
+int getIntFromSQLRow(sqlite3_stmt *sqlRow, int idx);
+std::unique_ptr<std::string>
+getStringPtrFromSQLRow(sqlite3_stmt *sqlRow, int idx);
+std::unique_ptr<int> getIntPtrFromSQLRow(sqlite3_stmt *sqlRow, int idx);
+int64_t getInt64FromSQLRow(sqlite3_stmt *sqlRow, int idx);
+} // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteDataConverters.cpp b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteDataConverters.cpp
new file mode 100644
--- /dev/null
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteDataConverters.cpp
@@ -0,0 +1,32 @@
+#include "SQLiteDataConverters.h"
+
+namespace comm {
+std::string getStringFromSQLRow(sqlite3_stmt *sqlRow, int idx) {
+  return reinterpret_cast<const char *>(sqlite3_column_text(sqlRow, idx));
+}
+
+int getIntFromSQLRow(sqlite3_stmt *sqlRow, int idx) {
+  return sqlite3_column_int(sqlRow, idx);
+}
+
+std::unique_ptr<std::string>
+getStringPtrFromSQLRow(sqlite3_stmt *sqlRow, int idx) {
+  const char *maybeString =
+      reinterpret_cast<const char *>(sqlite3_column_text(sqlRow, idx));
+  if (!maybeString) {
+    return nullptr;
+  }
+  return std::make_unique<std::string>(maybeString);
+}
+
+std::unique_ptr<int> getIntPtrFromSQLRow(sqlite3_stmt *sqlRow, int idx) {
+  if (sqlite3_column_type(sqlRow, idx) == SQLITE_NULL) {
+    return nullptr;
+  }
+  return std::make_unique<int>(sqlite3_column_int(sqlRow, idx));
+}
+
+int64_t getInt64FromSQLRow(sqlite3_stmt *sqlRow, int idx) {
+  return sqlite3_column_int64(sqlRow, idx);
+}
+} // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteStatementWrapper.h b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteStatementWrapper.h
new file mode 100644
--- /dev/null
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteStatementWrapper.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <sqlite3.h>
+#include <string>
+
+namespace comm {
+class SQLiteStatementWrapper {
+private:
+  sqlite3_stmt *preparedSQLPtr;
+  std::string onLastStepFailureMessage;
+
+public:
+  SQLiteStatementWrapper(
+      sqlite3 *db,
+      std::string sql,
+      std::string onLastStepFailureMessage);
+  SQLiteStatementWrapper(const SQLiteStatementWrapper &) = delete;
+  ~SQLiteStatementWrapper();
+  operator sqlite3_stmt *();
+};
+} // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteStatementWrapper.cpp b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteStatementWrapper.cpp
new file mode 100644
--- /dev/null
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/SQLiteStatementWrapper.cpp
@@ -0,0 +1,38 @@
+#include "SQLiteStatementWrapper.h"
+
+#include <sstream>
+#include <stdexcept>
+#include <system_error>
+
+namespace comm {
+SQLiteStatementWrapper::SQLiteStatementWrapper(
+    sqlite3 *db,
+    std::string sql,
+    std::string onLastStepFailureMessage) {
+  int prepareSQLResult =
+      sqlite3_prepare_v2(db, sql.c_str(), -1, &preparedSQLPtr, nullptr);
+
+  if (prepareSQLResult != SQLITE_OK) {
+    std::stringstream error_message;
+    error_message << "Failed to prepare SQL statement. Details: "
+                  << sqlite3_errstr(prepareSQLResult) << std::endl;
+    throw std::runtime_error(error_message.str());
+  }
+  onLastStepFailureMessage = onLastStepFailureMessage;
+}
+
+SQLiteStatementWrapper::~SQLiteStatementWrapper() {
+  int lastStepResult = sqlite3_finalize(preparedSQLPtr);
+  if (lastStepResult != SQLITE_OK) {
+    std::stringstream error_message;
+    error_message << onLastStepFailureMessage
+                  << " Details: " << sqlite3_errstr(lastStepResult)
+                  << std::endl;
+    throw std::runtime_error(error_message.str());
+  }
+}
+
+SQLiteStatementWrapper::operator sqlite3_stmt *() {
+  return preparedSQLPtr;
+}
+} // namespace comm
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/Thread.h b/native/cpp/CommonCpp/DatabaseManagers/entities/Thread.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/Thread.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/Thread.h
@@ -1,9 +1,11 @@
 #pragma once
 
+#include <sqlite3.h>
 #include <memory>
 #include <string>
 
 #include "Nullable.h"
+#include "SQLiteDataConverters.h"
 
 namespace comm {
 
@@ -24,6 +26,27 @@
   int replies_count;
   std::unique_ptr<std::string> avatar;
   int pinned_count;
+
+  static Thread fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return Thread{
+        getStringFromSQLRow(sqlRow, idx),
+        getIntFromSQLRow(sqlRow, idx + 1),
+        getStringPtrFromSQLRow(sqlRow, idx + 2),
+        getStringPtrFromSQLRow(sqlRow, idx + 3),
+        getStringFromSQLRow(sqlRow, idx + 4),
+        getInt64FromSQLRow(sqlRow, idx + 5),
+        getStringPtrFromSQLRow(sqlRow, idx + 6),
+        getStringPtrFromSQLRow(sqlRow, idx + 7),
+        getStringPtrFromSQLRow(sqlRow, idx + 8),
+        getStringFromSQLRow(sqlRow, idx + 9),
+        getStringFromSQLRow(sqlRow, idx + 10),
+        getStringFromSQLRow(sqlRow, idx + 11),
+        getStringPtrFromSQLRow(sqlRow, idx + 12),
+        getIntFromSQLRow(sqlRow, idx + 13),
+        getStringPtrFromSQLRow(sqlRow, idx + 14),
+        getIntFromSQLRow(sqlRow, idx + 15),
+    };
+  }
 };
 
 struct WebThread {
diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/UserInfo.h b/native/cpp/CommonCpp/DatabaseManagers/entities/UserInfo.h
--- a/native/cpp/CommonCpp/DatabaseManagers/entities/UserInfo.h
+++ b/native/cpp/CommonCpp/DatabaseManagers/entities/UserInfo.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "SQLiteDataConverters.h"
+#include <sqlite3.h>
 #include <string>
 
 namespace comm {
@@ -7,6 +9,11 @@
 struct UserInfo {
   std::string id;
   std::string user_info;
+
+  static UserInfo fromSQLResult(sqlite3_stmt *sqlRow, int idx) {
+    return UserInfo{
+        getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)};
+  };
 };
 
 } // namespace comm
diff --git a/native/ios/Comm.xcodeproj/project.pbxproj b/native/ios/Comm.xcodeproj/project.pbxproj
--- a/native/ios/Comm.xcodeproj/project.pbxproj
+++ b/native/ios/Comm.xcodeproj/project.pbxproj
@@ -56,6 +56,8 @@
 		8EF7756E2A7513F40046A385 /* MessageStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8EF7756D2A7513F40046A385 /* MessageStore.cpp */; };
 		8EF775712A751B780046A385 /* ReportStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8EF7756F2A751B780046A385 /* ReportStore.cpp */; };
 		B71AFF1F265EDD8600B22352 /* IBMPlexSans-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B71AFF1E265EDD8600B22352 /* IBMPlexSans-Medium.ttf */; };
+		CB01F0C22B67EF5A0089E1F9 /* SQLiteDataConverters.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB01F0C12B67EF470089E1F9 /* SQLiteDataConverters.cpp */; };
+		CB01F0C42B67F3A10089E1F9 /* SQLiteStatementWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB01F0C32B67F3970089E1F9 /* SQLiteStatementWrapper.cpp */; };
 		CB1648AF27CFBE6A00394D9D /* CryptoModule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 71BF5B7B26BBDA6100EDE27D /* CryptoModule.cpp */; };
 		CB24361829A39A2500FEC4E1 /* NotificationsCryptoModule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB24361729A39A2500FEC4E1 /* NotificationsCryptoModule.cpp */; };
 		CB2689002A2DF58000EC7300 /* CommConstants.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB2688FF2A2DF56000EC7300 /* CommConstants.cpp */; };
@@ -241,6 +243,10 @@
 		B7906F6C27209091009BBBF5 /* Thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Thread.h; sourceTree = "<group>"; };
 		B7E937CA26F448E700022A7C /* Media.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Media.h; sourceTree = "<group>"; };
 		C562A7004903539402D988CE /* Pods-Comm.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Comm.release.xcconfig"; path = "Target Support Files/Pods-Comm/Pods-Comm.release.xcconfig"; sourceTree = "<group>"; };
+		CB01F0BF2B67CDC20089E1F9 /* SQLiteStatementWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLiteStatementWrapper.h; sourceTree = "<group>"; };
+		CB01F0C02B67CDC20089E1F9 /* SQLiteDataConverters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLiteDataConverters.h; sourceTree = "<group>"; };
+		CB01F0C12B67EF470089E1F9 /* SQLiteDataConverters.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SQLiteDataConverters.cpp; sourceTree = "<group>"; };
+		CB01F0C32B67F3970089E1F9 /* SQLiteStatementWrapper.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SQLiteStatementWrapper.cpp; sourceTree = "<group>"; };
 		CB24361629A397AB00FEC4E1 /* NotificationsCryptoModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NotificationsCryptoModule.h; path = Notifications/BackgroundDataStorage/NotificationsCryptoModule.h; sourceTree = "<group>"; };
 		CB24361729A39A2500FEC4E1 /* NotificationsCryptoModule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NotificationsCryptoModule.cpp; path = Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp; sourceTree = "<group>"; };
 		CB2688FE2A2DF55F00EC7300 /* CommConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommConstants.h; sourceTree = "<group>"; };
@@ -285,6 +291,7 @@
 		CBCF57AB2B05096F00EC4BC0 /* AESCryptoModuleObjCCompat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AESCryptoModuleObjCCompat.h; path = Comm/CommAESCryptoUtils/AESCryptoModuleObjCCompat.h; sourceTree = "<group>"; };
 		CBDEC69928ED859600C17588 /* GlobalDBSingleton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GlobalDBSingleton.h; sourceTree = "<group>"; };
 		CBDEC69A28ED867000C17588 /* GlobalDBSingleton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = GlobalDBSingleton.mm; path = Comm/GlobalDBSingleton.mm; sourceTree = "<group>"; };
+		CBF9DAE22B595934000EE771 /* EntityQueryHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EntityQueryHelpers.h; sourceTree = "<group>"; };
 		CBFBEEB82B4ED90600729F1D /* RustBackupExecutor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RustBackupExecutor.cpp; sourceTree = "<group>"; };
 		CBFBEEB92B4ED90600729F1D /* RustBackupExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RustBackupExecutor.h; sourceTree = "<group>"; };
 		CBFE58272885852B003B94C9 /* ThreadOperations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThreadOperations.h; path = PersistentStorageUtilities/ThreadOperationsUtilities/ThreadOperations.h; sourceTree = "<group>"; };
@@ -482,6 +489,11 @@
 		71BE84442636A944002849D2 /* entities */ = {
 			isa = PBXGroup;
 			children = (
+				CB01F0C32B67F3970089E1F9 /* SQLiteStatementWrapper.cpp */,
+				CB01F0C12B67EF470089E1F9 /* SQLiteDataConverters.cpp */,
+				CB01F0C02B67CDC20089E1F9 /* SQLiteDataConverters.h */,
+				CB01F0BF2B67CDC20089E1F9 /* SQLiteStatementWrapper.h */,
+				CBF9DAE22B595934000EE771 /* EntityQueryHelpers.h */,
 				B7906F6A27209091009BBBF5 /* OlmPersistAccount.h */,
 				B7906F6B27209091009BBBF5 /* OlmPersistSession.h */,
 				B7906F6C27209091009BBBF5 /* Thread.h */,
@@ -1103,6 +1115,8 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CB01F0C42B67F3A10089E1F9 /* SQLiteStatementWrapper.cpp in Sources */,
+				CB01F0C22B67EF5A0089E1F9 /* SQLiteDataConverters.cpp in Sources */,
 				CBAAA4702B459181007599DA /* BackupOperationsExecutor.cpp in Sources */,
 				CBCA09062A8E0E7400F75B3E /* StaffUtils.cpp in Sources */,
 				8EF7756B2A7433630046A385 /* ThreadStore.cpp in Sources */,
diff --git a/web/database/_generated/comm_query_executor.wasm b/web/database/_generated/comm_query_executor.wasm
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001

literal 0
Hc$@<O00001

diff --git a/web/scripts/run_emscripten.sh b/web/scripts/run_emscripten.sh
--- a/web/scripts/run_emscripten.sh
+++ b/web/scripts/run_emscripten.sh
@@ -11,6 +11,7 @@
 SCRIPT_DIR=$(cd "$(dirname "$0")"; pwd -P)
 NATIVE_CPP_DIR="${SCRIPT_DIR}/../../native/cpp/"
 INPUT_DIR="${NATIVE_CPP_DIR}CommonCpp/DatabaseManagers/"
+ENTITIES_DIR="${NATIVE_CPP_DIR}CommonCpp/DatabaseManagers/entities/"
 SQLITE_DIR="${SCRIPT_DIR}/../database/sqlite/"
 WEB_CPP_DIR="${SCRIPT_DIR}/../cpp/"
 OUTPUT_DIR="${SCRIPT_DIR}/../database/_generated/"
@@ -146,6 +147,8 @@
 INPUT_FILES=(
   "${WEB_CPP_DIR}SQLiteQueryExecutorBindings.cpp"
   "${WEB_CPP_DIR}Logger.cpp"
+  "${ENTITIES_DIR}SQLiteDataConverters.cpp"
+  "${ENTITIES_DIR}SQLiteStatementWrapper.cpp"
   "$SQLITE_BITCODE_FILE"
 )
 
@@ -159,7 +162,8 @@
   -o "${OUTPUT_FILE}" \
   -std=c++17
 
-sed -i.bak -e '1i\/\/ \@generated' "${OUTPUT_FILE}"
+GENERATED_TAG="generated"
+sed -i.bak -e "1i\/\/ \@${GENERATED_TAG}" "${OUTPUT_FILE}"
 
 mv -f "${OUTPUT_DIR}${OUTPUT_FILE_NAME}.wasm" "${OUTPUT_DIR}comm_query_executor.wasm"
 rm -f "${OUTPUT_FILE}.bak"