MongoDB replica set support (#5068)

* feature(MongoDB): Initial implementation of support for replica sets.

* enh(MongoDB): Add parsing of mongodb replica set URI.

* enh(MongoDB): Treat single-server as primary, fix parsing of URI.

* fix(MongoDB): Add missing example for parsing the URI.

* enh(MongoDB): simplified and modernised code.

* fix(MongoDB): Fixes based on static analysis report.

* enh(MongoDB): Remove unused includes.

* fix(MongoDB): ReplicaSetMonitor: handle wrong arguments more gracefully.

* enh(MongoDB) Use string literals and char constants where possible.

* enh(MongoDB) More tests.

* fix(MongoDB): Prevent duplicate entries in MongoDB::Document.

* enh(MongoDB): Update RS tests, remove support for pre 5.1 MongoDB.

* enh(MongoDB): Use C++ std mutex primitives instead of Poco.

* fix(MongoDB): correct replica set compile errors.

* fix(MongoDB): fix replica set tests to work with IPv6 addresses.

* enh(MongoDB): Remove confusing hosts from ServerDescription and use hosts from "hello" only to discover replica set hosts.

* enh(MongoDB): fix unused variables in tests.

* enh(MongoDB): Add read-preference validation to replica set connection and pool.

* chore(MongoDB): Add reserve to Document.

* enh(MongoDB): connect to server in pool activateObject.

* chore(MongoDB): use tried servers set only locally inside executeWithRetry.

* enh(MongoDB): Add support for ReplicaSetConnection to OpMsgCursor.

* enh(MongoDB): Replica set: More robust retry of failed MongoDB commands.

* enh(MongoDB): ReplicaSet remove redundant function, update documentation.

* enh(MongoDB): Update handling of URL parameters.

* enh(MongoDB): Updates to use SSL socket factory.

* enh(MongoDB): Log topology change when detected.

* enh(MongoDB): Move function to wait for the server availability to ReplicaSet.

* enh(MongoDB): Replica set: send notification on topology change.

* enh(MongoDB): Replica set: Remove internal logger for

* enh(MongoDB): Replica set: Remove internal logger for topology change. Use notification instead.

* enh(MongoDB): Update samples and replica set readme.

* enh(MongoDB): Fix parsing replica set URI.

* enh(MongoDB): Introduce ReplicaSetURI for handling custom URI.

* fix(MongoDB): heartbeatFrequency --> heartbeatFrequencyMS to match standard URL

* enh(MongoDB): Introduce constants in ReplicaSetURI.

* chore(MongoDB): Minor enhancements.

* enh(MongoDB): Correct logic to determine replica set topology from discovered servers.

* enh(MongoDB): Remove redundant code when parsing hello response.

* enh(MongoDB): Prevent servers from different replica set to be used in the topology.

* enh(MongoDB): Mark topology with incompatible servers as Unknown.

* enh(CI): Speed-up build by using cmake --build --parallel 4

* enh(CI): Upgrade codeql from v3 to v4

https://github.blog/changelog/2025-10-28-upcoming-deprecation-of-codeql-action-v3/

* enh(modules): Update MongoDB module to reflect latest state.

enh(MongoDB): Remove dead code and mark Message as implementation detail.

enh(CI): Fix build of C++ modules

* fix(C++ modules): Use C++20 features in samples, not C++23.

* docs(MongoDB): Clarify RTT limitation in Nearest read preference
This commit is contained in:
Matej Kenda
2025-12-16 07:49:36 +01:00
committed by GitHub
parent 227f84fb8b
commit 51f3749fa0
67 changed files with 10757 additions and 436 deletions

View File

@@ -33,7 +33,7 @@ jobs:
run: |
export CC=clang-20
export CXX=clang++-20
cmake -S . -B build -G Ninja -DPOCO_BUILD_MODULES=ON
cmake -S . -B build -G Ninja -DENABLE_MODULES=ON -DENABLE_TESTS=ON
- name: Build
run: cmake --build build --parallel 4
@@ -45,7 +45,9 @@ jobs:
with:
ndk-version: r25c
add-to-path: true
- run: cmake -S$GITHUB_WORKSPACE -B$HOME/android-build -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-21 -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake && cmake --build $HOME/android-build --target all
- run: |
cmake -S$GITHUB_WORKSPACE -B$HOME/android-build -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-21 -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake
cmake --build $HOME/android-build --target all --parallel 4
android-arm64-v8a-ndk-cmake:
runs-on: ubuntu-22.04
@@ -55,7 +57,9 @@ jobs:
with:
ndk-version: r25c
add-to-path: true
- run: cmake -S$GITHUB_WORKSPACE -B$HOME/android-build -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-21 -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake && cmake --build $HOME/android-build --target all
- run: |
cmake -S$GITHUB_WORKSPACE -B$HOME/android-build -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-21 -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
cmake --build $HOME/android-build --target all --parallel 4
android-armeabi-v7a-ndk-cmake:
runs-on: ubuntu-22.04
@@ -65,7 +69,9 @@ jobs:
with:
ndk-version: r25c
add-to-path: true
- run: cmake -S$GITHUB_WORKSPACE -B$HOME/android-build -DANDROID_ABI=armeabi-v7a -DANDROID_PLATFORM=android-21 -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake && cmake --build $HOME/android-build --target all
- run: |
cmake -S$GITHUB_WORKSPACE -B$HOME/android-build -DANDROID_ABI=armeabi-v7a -DANDROID_PLATFORM=android-21 -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
cmake --build $HOME/android-build --target all --parallel 4
linux-gcc-cmake-armv7l:
# Converted from a make job, is it OK?
@@ -107,7 +113,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: sudo apt -y update && sudo apt -y install cmake ninja-build libssl-dev unixodbc-dev libmysqlclient-dev redis-server
- run: cmake -S. -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_TESTS=ON && cmake --build cmake-build --target all --parallel 4
- run: |
cmake -S. -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_TESTS=ON
cmake --build cmake-build --target all --parallel 4
- uses: ./.github/actions/retry-action
with:
timeout_minutes: 90

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
sudo apt-get -y update && sudo apt-get -y install cmake ninja-build libssl-dev unixodbc-dev libmysqlclient-dev redis-server
cmake -H. -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_DNSSD=OFF -DENABLE_TESTS=ON && cmake --build cmake-build --target all
cmake -H. -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_DNSSD=OFF -DENABLE_TESTS=ON && cmake --build cmake-build --target all --parallel 4

View File

@@ -51,7 +51,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -78,7 +78,7 @@ jobs:
./.github/workflows/codeql-buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"
upload: false
@@ -108,7 +108,7 @@ jobs:
output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
- name: Upload CodeQL results to code scanning
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: ${{ steps.step1.outputs.sarif-output }}
category: "/language:${{matrix.language}}"

View File

@@ -82,7 +82,9 @@ jobs:
- run: sudo apt -y update && sudo apt -y install cmake ninja-build libssl-dev unixodbc-dev libmysqlclient-dev
- run: rm poco-*-all.tar.gz
- run: mkdir poco && cd poco && tar xfz ../poco-*.tar.gz --strip-components=1
- run: cmake -Spoco -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_TESTS=ON && cmake --build cmake-build --target all
- run: |
cmake -Spoco -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_TESTS=ON
cmake --build cmake-build --target all --parallel 4
linux-gcc-cmake-mkrelease-all:
runs-on: ubuntu-22.04
@@ -93,7 +95,9 @@ jobs:
name: posix-archives
- run: sudo apt -y update && sudo apt -y install cmake ninja-build libssl-dev unixodbc-dev libmysqlclient-dev
- run: mkdir poco && cd poco && tar xfz ../poco-*-all.tar.gz --strip-components=1
- run: cmake -Spoco -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_TESTS=ON && cmake --build cmake-build --target all
- run: |
cmake -Spoco -Bcmake-build -GNinja -DENABLE_PDF=OFF -DENABLE_TESTS=ON
cmake --build cmake-build --target all --parallel 4
windows-2022-msvc-cmake-mkrelease-all:
runs-on: windows-2022
@@ -106,7 +110,7 @@ jobs:
7z x poco-*-all.zip
cd poco-*-all
cmake -S. -Bcmake-build -DENABLE_NETSSL_WIN=ON -DENABLE_NETSSL=OFF -DENABLE_CRYPTO=OFF -DENABLE_JWT=OFF -DENABLE_DATA=ON -DENABLE_DATA_ODBC=ON -DENABLE_DATA_MYSQL=OFF -DENABLE_DATA_POSTGRESQL=OFF -DENABLE_TESTS=ON
cmake --build cmake-build --config Release
cmake --build cmake-build --config Release --parallel 4
windows-2022-msvc-cmake-mkrelease:
runs-on: windows-2022
@@ -120,4 +124,5 @@ jobs:
7z x poco-*.zip
cd poco-*
cmake -S. -Bcmake-build -DENABLE_TESTS=ON
cmake --build cmake-build --config Release
cmake --build cmake-build --config Release --parallel 4

View File

@@ -315,6 +315,12 @@ endif()
# - resolve dependencies
if(ENABLE_SAMPLES)
set(ENABLE_UTIL ON CACHE BOOL "Enable Util" FORCE)
set(ENABLE_JSON ON CACHE BOOL "Enable JSON" FORCE)
set(ENABLE_XML ON CACHE BOOL "Enable XML" FORCE)
endif()
if(ENABLE_ENCODINGS_COMPILER)
set(ENABLE_NET ON CACHE BOOL "Enable Net" FORCE)
set(ENABLE_UTIL ON CACHE BOOL "Enable Util" FORCE)
@@ -589,6 +595,7 @@ endif()
if(ENABLE_MODULES AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
add_subdirectory(modules)
list(APPEND Poco_COMPONENTS "C++ modules")
endif()

1560
MongoDB/README-ReplicaSet.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
//
// Definition of the Array class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -110,7 +110,6 @@ struct ElementTraits<Array::Ptr>
static std::string toString(const Array::Ptr& value, int indent = 0)
{
//TODO:
return value.isNull() ? "null" : value->toString(indent);
}
};
@@ -124,7 +123,7 @@ inline void BSONReader::read<Array::Ptr>(Array::Ptr& to)
template<>
inline void BSONWriter::write<Array::Ptr>(Array::Ptr& from)
inline void BSONWriter::write<Array::Ptr>(const Array::Ptr& from)
{
from->write(_writer);
}

View File

@@ -7,7 +7,7 @@
//
// Definition of the BSONReader class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -7,7 +7,7 @@
//
// Definition of the BSONWriter class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -42,7 +42,7 @@ public:
}
template<typename T>
void write(T& t)
void write(const T& t)
/// Writes the value to the writer. The default implementation uses
/// the << operator. Special types can write their own version.
{
@@ -64,7 +64,7 @@ private:
inline void BSONWriter::writeCString(const std::string& value)
{
_writer.writeRaw(value);
_writer << (unsigned char) 0x00;
_writer << static_cast<unsigned char>(0x00);
}

View File

@@ -7,7 +7,7 @@
//
// Definition of the Binary class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -58,15 +58,21 @@ public:
Binary(const UUID& uuid);
/// Creates a Binary containing an UUID.
Binary(const char* data, unsigned char subtype = 0);
/// Creates a Binary with the contents of the given C-string and the given subtype.
Binary(const std::string& data, unsigned char subtype = 0);
/// Creates a Binary with the contents of the given string and the given subtype.
Binary(const void* data, Poco::Int32 size, unsigned char subtype = 0);
Binary(const void* data, Poco::Int32 size, unsigned char subtype);
/// Creates a Binary with the contents of the given buffer and the given subtype.
virtual ~Binary();
/// Destroys the Binary.
const Buffer<unsigned char>& buffer() const;
/// Returns a reference to the internal buffer
Buffer<unsigned char>& buffer();
/// Returns a reference to the internal buffer
@@ -110,6 +116,12 @@ inline void Binary::subtype(unsigned char type)
}
inline const Buffer<unsigned char>& Binary::buffer() const
{
return _buffer;
}
inline Buffer<unsigned char>& Binary::buffer()
{
return _buffer;
@@ -148,16 +160,16 @@ inline void BSONReader::read<Binary::Ptr>(Binary::Ptr& to)
_reader >> subtype;
to->subtype(subtype);
_reader.readRaw((char*) to->buffer().begin(), size);
_reader.readRaw(reinterpret_cast<char*>(to->buffer().begin()), size);
}
template<>
inline void BSONWriter::write<Binary::Ptr>(Binary::Ptr& from)
inline void BSONWriter::write<Binary::Ptr>(const Binary::Ptr& from)
{
_writer << (Poco::Int32) from->buffer().size();
_writer << static_cast<Poco::Int32>(from->buffer().size());
_writer << from->subtype();
_writer.writeRaw(reinterpret_cast<char*>(from->buffer().begin()), from->buffer().size());
_writer.writeRaw(reinterpret_cast<const char*>(from->buffer().begin()), from->buffer().size());
}

View File

@@ -7,7 +7,7 @@
//
// Definition of the Connection class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -7,7 +7,7 @@
//
// Definition of the Database class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -7,7 +7,7 @@
//
// Definition of the Document class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -22,7 +22,6 @@
#include "Poco/BinaryWriter.h"
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Element.h"
#include <algorithm>
#include <cstdlib>
#include <unordered_map>
#include <type_traits>
@@ -33,23 +32,6 @@ namespace MongoDB {
class Array;
class MongoDB_API ElementFindByName
{
public:
ElementFindByName(const std::string& name):
_name(name)
{
}
bool operator()(const Element::Ptr& element)
{
return !element.isNull() && element->name() == _name;
}
private:
std::string _name;
};
class MongoDB_API Document
/// Represents a MongoDB (BSON) document.
@@ -98,6 +80,9 @@ public:
return addElement(new ConcreteElement<T>(std::move(name), value));
}
void reserve(std::size_t size);
/// Reserves space for elements.
Document& add(const std::string& name, const char* value)
/// Creates an element with the given name and value and
/// adds it to the document.
@@ -213,34 +198,26 @@ public:
[[nodiscard]] virtual std::string toString(int indent = 0) const;
/// Returns a String representation of the document.
void write(BinaryWriter& writer);
void write(BinaryWriter& writer) const;
/// Writes a document to the reader
protected:
const ElementSet& elements() const noexcept;
/// Returns const reference to elements for read-only access by derived classes.
const std::vector<std::string>& orderedNames() const noexcept;
/// Returns const reference to element names in insertion order for read-only access by derived classes.
/// Direct modification is not allowed to maintain synchronization with hash map.
private:
ElementSet _elements;
std::vector<std::string> _elementNames;
/// Vector of element names in insertion order.
std::unordered_map<std::string, Element::Ptr> _elementMap;
/// Hash map for O(1) element lookups by name.
/// Maintained in sync with _elements for fast access.
/// These are private to ensure derived classes cannot break synchronization.
/// Maintained in sync with _elementNames for ordered access.
};
//
// inlines
//
inline Document& Document::addElement(Element::Ptr element)
{
_elements.push_back(element);
_elementMap[element->name()] = element; // O(1) insert for fast lookups
return *this;
}
inline Document& Document::addNewDocument(const std::string& name)
{
Document::Ptr newDoc = new Document();
@@ -251,24 +228,21 @@ inline Document& Document::addNewDocument(const std::string& name)
inline void Document::clear() noexcept
{
_elements.clear();
_elementNames.clear();
_elementMap.clear();
}
inline bool Document::empty() const noexcept
{
return _elements.empty();
return _elementNames.empty();
}
inline void Document::elementNames(std::vector<std::string>& keys) const
{
keys.reserve(keys.size() + _elements.size()); // Pre-allocate to avoid reallocations
for (const auto & _element : _elements)
{
keys.push_back(_element->name());
}
keys.reserve(keys.size() + _elementNames.size()); // Pre-allocate to avoid reallocations
keys.insert(keys.end(), _elementNames.begin(), _elementNames.end());
}
@@ -279,33 +253,17 @@ inline bool Document::exists(const std::string& name) const noexcept
}
inline bool Document::remove(const std::string& name)
{
// Remove from hash map first (O(1))
auto mapIt = _elementMap.find(name);
if (mapIt == _elementMap.end())
return false;
_elementMap.erase(mapIt);
// Then remove from vector (O(n) but unavoidable for order preservation)
auto it = std::find_if(_elements.begin(), _elements.end(), ElementFindByName(name));
if (it != _elements.end())
_elements.erase(it);
return true;
}
inline std::size_t Document::size() const noexcept
{
return _elements.size();
return _elementNames.size();
}
inline const ElementSet& Document::elements() const noexcept
inline const std::vector<std::string>& Document::orderedNames() const noexcept
{
return _elements;
return _elementNames;
}
@@ -331,7 +289,7 @@ inline void BSONReader::read<Document::Ptr>(Document::Ptr& to)
template<>
inline void BSONWriter::write<Document::Ptr>(Document::Ptr& from)
inline void BSONWriter::write<Document::Ptr>(const Document::Ptr& from)
{
from->write(_writer);
}

View File

@@ -7,7 +7,7 @@
//
// Definition of the Element class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -64,7 +64,7 @@ public:
private:
virtual void read(BinaryReader& reader) = 0;
virtual void write(BinaryWriter& writer) = 0;
virtual void write(BinaryWriter& writer) const = 0;
friend class Document;
std::string _name;
@@ -80,9 +80,6 @@ inline const std::string& Element::name() const noexcept
}
using ElementSet = std::vector<Element::Ptr>;
template<typename T>
struct ElementTraits
{
@@ -198,9 +195,9 @@ inline void BSONReader::read<std::string>(std::string& to)
template<>
inline void BSONWriter::write<std::string>(std::string& from)
inline void BSONWriter::write<std::string>(const std::string& from)
{
_writer << (Poco::Int32) (from.length() + 1);
_writer << static_cast<Poco::Int32>(from.length() + 1);
writeCString(from);
}
@@ -229,7 +226,7 @@ inline void BSONReader::read<bool>(bool& to)
template<>
inline void BSONWriter::write<bool>(bool& from)
inline void BSONWriter::write<bool>(const bool& from)
{
unsigned char b = from ? 0x01 : 0x00;
_writer << b;
@@ -281,7 +278,7 @@ inline void BSONReader::read<Timestamp>(Timestamp& to)
template<>
inline void BSONWriter::write<Timestamp>(Timestamp& from)
inline void BSONWriter::write<Timestamp>(const Timestamp& from)
{
_writer << (from.epochMicroseconds() / 1000);
}
@@ -311,7 +308,7 @@ inline void BSONReader::read<NullValue>(NullValue& to)
template<>
inline void BSONWriter::write<NullValue>(NullValue& from)
inline void BSONWriter::write<NullValue>(const NullValue& from)
{
}
@@ -356,7 +353,7 @@ inline void BSONReader::read<BSONTimestamp>(BSONTimestamp& to)
template<>
inline void BSONWriter::write<BSONTimestamp>(BSONTimestamp& from)
inline void BSONWriter::write<BSONTimestamp>(const BSONTimestamp& from)
{
Poco::Int64 value = from.ts.epochMicroseconds() / 1000;
value <<= 32;
@@ -420,7 +417,7 @@ public:
BSONReader(reader).read(_value);
}
void write(BinaryWriter& writer) override
void write(BinaryWriter& writer) const override
{
BSONWriter(writer).write(_value);
}

View File

@@ -7,7 +7,7 @@
//
// Definition of the JavaScriptCode class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -92,7 +92,7 @@ inline void BSONReader::read<JavaScriptCode::Ptr>(JavaScriptCode::Ptr& to)
template<>
inline void BSONWriter::write<JavaScriptCode::Ptr>(JavaScriptCode::Ptr& from)
inline void BSONWriter::write<JavaScriptCode::Ptr>(const JavaScriptCode::Ptr& from)
{
std::string code = from->getCode();
BSONWriter(_writer).write(code);

View File

@@ -7,7 +7,7 @@
//
// Definition of the Message class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -32,6 +32,9 @@ namespace MongoDB {
class MongoDB_API Message
/// Base class for all messages send or retrieved from MongoDB server.
///
/// INTERNAL: This is a base class for OpMsgMessage and not intended
/// for direct use. Use OpMsgMessage instead.
{
public:
explicit Message(MessageHeader::OpCode opcode);

View File

@@ -7,7 +7,7 @@
//
// Definition of the MessageHeader class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -19,7 +19,6 @@
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/MessageHeader.h"
namespace Poco {
@@ -32,6 +31,9 @@ class Message; // Required to disambiguate friend declaration in MessageHeader.
class MongoDB_API MessageHeader
/// Represents the message header which is always prepended to a
/// MongoDB request or response message.
///
/// INTERNAL: This class is an implementation detail of the MongoDB
/// protocol and not intended for direct use.
{
public:
static constexpr Int32 MSG_HEADER_SIZE = 16;

View File

@@ -9,7 +9,7 @@
// This file must be the first file included by every other MongoDB
// header file.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -7,7 +7,7 @@
//
// Definition of the ObjectId class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -144,9 +144,9 @@ inline void BSONReader::read<ObjectId::Ptr>(ObjectId::Ptr& to)
template<>
inline void BSONWriter::write<ObjectId::Ptr>(ObjectId::Ptr& from)
inline void BSONWriter::write<ObjectId::Ptr>(const ObjectId::Ptr& from)
{
_writer.writeRaw(reinterpret_cast<char*>(from->_id), 12);
_writer.writeRaw(reinterpret_cast<const char*>(from->_id), 12);
}

View File

@@ -7,7 +7,7 @@
//
// Definition of the OpMsgCursor class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -25,11 +25,16 @@
namespace Poco {
namespace MongoDB {
class ReplicaSetConnection;
class MongoDB_API OpMsgCursor: public Document
/// OpMsgCursor is an helper class for querying multiple documents using OpMsgMessage.
/// OpMsgCursor is a helper class for querying multiple documents using OpMsgMessage.
/// Once all of the data is read with the cursor (see isActive()) it can't be reused.
///
/// USAGE:
/// Supports both Connection and ReplicaSetConnection. When using ReplicaSetConnection,
/// cursor operations benefit from automatic retry and failover on retriable errors.
///
/// RESOURCE MANAGEMENT:
/// When a cursor is no longer needed, you should call kill() to release server-side
/// resources. If kill() is not called explicitly, the server will keep the cursor
@@ -69,13 +74,36 @@ public:
///
/// The cursor must be killed (see kill()) when not all documents are needed.
OpMsgMessage& next(ReplicaSetConnection& connection);
/// Tries to get the next documents. As long as response message has a
/// cursor ID next can be called to retrieve the next bunch of documents.
///
/// The cursor must be killed (see kill()) when not all documents are needed.
///
/// This overload provides automatic retry and failover for replica set deployments.
OpMsgMessage& query();
/// Returns the associated query.
void kill(Connection& connection);
/// Kills the cursor and reset it so that it can be reused.
/// Kills the cursor and resets its internal state.
/// Call this method when you don't need all documents to release server resources.
void kill(ReplicaSetConnection& connection);
/// Kills the cursor and resets its internal state.
/// Call this method when you don't need all documents to release server resources.
///
/// This overload provides automatic retry and failover for replica set deployments.
private:
template<typename ConnType>
OpMsgMessage& nextImpl(ConnType& connection);
/// Template implementation for next() to avoid code duplication.
template<typename ConnType>
void killImpl(ConnType& connection);
/// Template implementation for kill() to avoid code duplication.
OpMsgMessage _query;
OpMsgMessage _response;

View File

@@ -7,7 +7,7 @@
//
// Definition of the PoolableConnectionFactory class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -0,0 +1,205 @@
//
// ReadPreference.h
//
// Library: MongoDB
// Package: MongoDB
// Module: ReadPreference
//
// Definition of the ReadPreference class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef MongoDB_ReadPreference_INCLUDED
#define MongoDB_ReadPreference_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Document.h"
#include "Poco/MongoDB/ServerDescription.h"
#include <vector>
namespace Poco {
namespace MongoDB {
class TopologyDescription; // Forward declaration
class MongoDB_API ReadPreference
/// Configures read preference mode and constraints for MongoDB operations.
///
/// Read preferences determine which replica set members receive read
/// operations. The five read preference modes are:
///
/// - Primary: Read from the primary only (default, strongest consistency)
/// - PrimaryPreferred: Read from primary, fallback to secondary
/// - Secondary: Read from secondary only (distributes load)
/// - SecondaryPreferred: Read from secondary, fallback to primary
/// - Nearest: Read from any available member (primary or secondary)
///
/// Additional constraints:
/// - Tag sets: Target specific replica set members by tags
/// - Max staleness: Limit how stale secondary data can be
///
/// LIMITATIONS:
/// The Nearest mode does NOT measure actual network round-trip time (RTT).
/// It simply allows selection of any available member. True latency-based
/// server selection is not implemented.
///
/// Examples:
/// ReadPreference primary(ReadPreference::Primary);
/// ReadPreference secondary(ReadPreference::Secondary);
/// ReadPreference nearest(ReadPreference::Nearest);
///
/// // With tag set (e.g., datacenter-aware routing)
/// Document tags;
/// tags.add("dc", "east");
/// tags.add("rack", "1");
/// ReadPreference geoPreference(ReadPreference::Nearest, tags);
///
/// THREAD SAFETY:
/// This class is immutable after construction and therefore thread-safe.
{
public:
enum Mode
/// Read preference mode enumeration
{
Primary, /// Read from primary only
PrimaryPreferred, /// Read from primary, fallback to secondary
Secondary, /// Read from secondary only
SecondaryPreferred, /// Read from secondary, fallback to primary
Nearest /// Read from any available member (no RTT measurement)
};
static const Poco::Int64 NO_MAX_STALENESS = -1;
/// Constant indicating no max staleness constraint
explicit ReadPreference(Mode mode = Primary);
/// Creates a ReadPreference with the specified mode.
ReadPreference(Mode mode, const Document& tags, Poco::Int64 maxStalenessSeconds = NO_MAX_STALENESS);
/// Creates a ReadPreference with mode, tag set, and optional max staleness.
/// maxStalenessSeconds: maximum replication lag in seconds (-1 for no limit)
ReadPreference(const ReadPreference& other);
/// Copy constructor.
ReadPreference(ReadPreference&& other) noexcept;
/// Move constructor.
~ReadPreference();
/// Destroys the ReadPreference.
ReadPreference& operator=(const ReadPreference& other);
/// Assignment operator.
ReadPreference& operator=(ReadPreference&& other) noexcept;
/// Move assignment operator.
[[nodiscard]] Mode mode() const;
/// Returns the read preference mode.
[[nodiscard]] const Document& tags() const;
/// Returns the tag set for server selection.
[[nodiscard]] Poco::Int64 maxStalenessSeconds() const;
/// Returns the max staleness in seconds, or NO_MAX_STALENESS if not set.
[[nodiscard]] std::vector<ServerDescription> selectServers(const TopologyDescription& topology) const;
/// Selects eligible servers from the topology based on this read preference.
/// Returns a vector of eligible servers.
/// If no servers match, returns an empty vector.
[[nodiscard]] std::string toString() const;
/// Returns a string representation of the read preference.
static ReadPreference primary();
/// Factory method for Primary read preference.
static ReadPreference primaryPreferred();
/// Factory method for PrimaryPreferred read preference.
static ReadPreference secondary();
/// Factory method for Secondary read preference.
static ReadPreference secondaryPreferred();
/// Factory method for SecondaryPreferred read preference.
static ReadPreference nearest();
/// Factory method for Nearest read preference.
private:
bool matchesTags(const ServerDescription& server) const;
std::vector<ServerDescription> filterByTags(const std::vector<ServerDescription>& servers) const;
std::vector<ServerDescription> filterByMaxStaleness(const std::vector<ServerDescription>& servers, const ServerDescription& primary) const;
std::vector<ServerDescription> selectByNearest(const std::vector<ServerDescription>& servers) const;
Mode _mode{Primary};
Document _tags;
Poco::Int64 _maxStalenessSeconds{NO_MAX_STALENESS};
};
//
// inlines
//
inline ReadPreference::Mode ReadPreference::mode() const
{
return _mode;
}
inline const Document& ReadPreference::tags() const
{
return _tags;
}
inline Poco::Int64 ReadPreference::maxStalenessSeconds() const
{
return _maxStalenessSeconds;
}
inline ReadPreference ReadPreference::primary()
{
return ReadPreference(Primary);
}
inline ReadPreference ReadPreference::primaryPreferred()
{
return ReadPreference(PrimaryPreferred);
}
inline ReadPreference ReadPreference::secondary()
{
return ReadPreference(Secondary);
}
inline ReadPreference ReadPreference::secondaryPreferred()
{
return ReadPreference(SecondaryPreferred);
}
inline ReadPreference ReadPreference::nearest()
{
return ReadPreference(Nearest);
}
} } // namespace Poco::MongoDB
#endif // MongoDB_ReadPreference_INCLUDED

View File

@@ -7,7 +7,7 @@
//
// Definition of the RegularExpression class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -160,7 +160,7 @@ inline void BSONReader::read<RegularExpression::Ptr>(RegularExpression::Ptr& to)
template<>
inline void BSONWriter::write<RegularExpression::Ptr>(RegularExpression::Ptr& from)
inline void BSONWriter::write<RegularExpression::Ptr>(const RegularExpression::Ptr& from)
{
writeCString(from->getPattern());
writeCString(from->getOptions());

View File

@@ -7,7 +7,7 @@
//
// Definition of the ReplicaSet class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -18,9 +18,18 @@
#define MongoDB_ReplicaSet_INCLUDED
#include "Poco/Net/SocketAddress.h"
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Connection.h"
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/MongoDB/TopologyDescription.h"
#include "Poco/MongoDB/ReplicaSetURI.h"
#include "Poco/Net/SocketAddress.h"
#include <vector>
#include <string>
#include <thread>
#include <atomic>
#include <mutex>
#include <chrono>
namespace Poco {
@@ -29,26 +38,232 @@ namespace MongoDB {
class MongoDB_API ReplicaSet
/// Class for working with a MongoDB replica set.
///
/// This class provides comprehensive replica set support including:
/// - Automatic topology discovery from seed servers
/// - Primary election detection
/// - Connection failover on errors
/// - Read preference routing (primary, secondary, nearest, etc.)
/// - Background topology monitoring
/// - Server health checking
///
/// Usage example:
/// ReplicaSet::Config config;
/// config.seeds = {
/// Net::SocketAddress("mongo1:27017"),
/// Net::SocketAddress("mongo2:27017"),
/// Net::SocketAddress("mongo3:27017")
/// };
/// config.setName = "rs0";
/// config.readPreference = ReadPreference::primaryPreferred();
///
/// ReplicaSet rs(config);
/// Connection::Ptr conn = rs.getPrimaryConnection();
/// // Use connection...
///
/// REQUIREMENTS:
/// Requires MongoDB 5.1 or later. Earlier versions using the legacy
/// isMaster command are not supported.
///
/// THREAD SAFETY:
/// The ReplicaSet class is thread-safe. Multiple threads can call
/// getConnection() and other methods concurrently. However, the
/// returned Connection objects are NOT thread-safe and must be used
/// by only one thread at a time, or protected by external synchronization.
///
/// For multi-threaded applications, use ReplicaSetConnection with
/// connection pooling (PoolableObjectFactory pattern).
{
public:
explicit ReplicaSet(const std::vector<Net::SocketAddress>& addresses);
/// Creates the ReplicaSet using the given server addresses.
struct Config
/// Replica set configuration
{
std::vector<Net::SocketAddress> seeds;
/// Seed servers for initial topology discovery.
/// At least one seed must be reachable.
std::string setName;
/// Expected replica set name.
/// If empty, will be discovered from servers.
ReadPreference readPreference{ReadPreference::Primary};
/// Default read preference for this replica set.
unsigned int connectTimeoutSeconds{10};
/// Connection timeout in seconds (default: 10)
///
/// NOTE: This value is currently unused by ReplicaSet itself. It is intended
/// for use by custom SocketFactory implementations. Custom factories can
/// access this value via ReplicaSet::configuration() and use it when creating
/// sockets. Use ReplicaSet::setSocketFactory() to set a custom factory that
/// utilizes this timeout value.
unsigned int socketTimeoutSeconds{30};
/// Socket send/receive timeout in seconds (default: 30)
///
/// NOTE: This value is currently unused by ReplicaSet itself. It is intended
/// for use by custom SocketFactory implementations. Custom factories can
/// access this value via ReplicaSet::configuration() and use it when creating
/// sockets. Use ReplicaSet::setSocketFactory() to set a custom factory that
/// utilizes this timeout value.
unsigned int heartbeatFrequencySeconds{10};
/// Topology monitoring interval in seconds (default: 10)
std::size_t serverReconnectRetries{10};
/// Number of connection retries to a server/replica set if no server is available temporarily
unsigned int serverReconnectDelaySeconds{1};
/// Delay in seconds between re-connects to a server/replica set if no server is available temporarily
bool enableMonitoring{true};
/// Enable background topology monitoring (default: true)
Connection::SocketFactory* socketFactory{nullptr};
/// Optional socket factory for SSL/TLS connections.
/// Can be set via config or later using setSocketFactory().
/// Custom factories can access timeout config via ReplicaSet::configuration().
};
explicit ReplicaSet(const Config& config);
/// Creates a ReplicaSet with the given configuration.
/// Performs initial topology discovery.
/// Throws Poco::IOException if initial discovery fails.
explicit ReplicaSet(const std::vector<Net::SocketAddress>& seeds);
/// Creates a ReplicaSet with default configuration and the given seed addresses.
/// Performs initial topology discovery.
/// Throws Poco::IOException if initial discovery fails.
explicit ReplicaSet(const std::string& uri);
/// Creates a ReplicaSet from a MongoDB URI string.
/// Format: mongodb://host1:port1,host2:port2,...?options
///
/// Supported URI options:
/// - replicaSet=name - Replica set name
/// - readPreference=mode - primary|primaryPreferred|secondary|secondaryPreferred|nearest
/// - connectTimeoutMS=ms - Connection timeout in milliseconds
/// - socketTimeoutMS=ms - Socket timeout in milliseconds
/// - heartbeatFrequencyMS=ms - Heartbeat frequency in milliseconds (default: 10000)
/// - reconnectRetries=n - Number of reconnection retries (default: 10)
/// - reconnectDelay=seconds - Delay between reconnection attempts in seconds (default: 1)
///
/// Example: mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=rs0&readPreference=primaryPreferred
///
/// Throws Poco::SyntaxException if URI is invalid.
/// Throws Poco::UnknownURISchemeException if scheme is not "mongodb".
explicit ReplicaSet(const ReplicaSetURI& uri);
/// Creates a ReplicaSet from a ReplicaSetURI object.
/// This allows for programmatic URI construction and modification before
/// creating the replica set connection.
///
/// The ReplicaSetURI stores servers as strings without DNS resolution.
/// This constructor resolves the server strings to SocketAddress objects.
/// Servers that cannot be resolved are skipped and will be marked as
/// unavailable during topology discovery.
///
/// Example:
/// ReplicaSetURI uri;
/// uri.addServer("host1:27017");
/// uri.addServer("host2:27017");
/// uri.setReplicaSet("rs0");
/// uri.setReadPreference("primaryPreferred");
/// ReplicaSet rs(uri);
///
/// Throws Poco::InvalidArgumentException if the URI contains no servers
/// or if no servers can be resolved.
/// Throws Poco::IOException if initial discovery fails.
virtual ~ReplicaSet();
/// Destroys the ReplicaSet.
/// Destroys the ReplicaSet and stops background monitoring.
[[nodiscard]] Connection::Ptr findMaster();
/// Tries to find the master MongoDB instance from the addresses
/// passed to the constructor.
Connection::Ptr getConnection(const ReadPreference& readPref);
/// Returns a connection to a server matching the read preference.
/// Returns null if no suitable server is available.
Connection::Ptr waitForServerAvailability(const ReadPreference& readPref);
/// Waits for a server to become available for the given read preference.
/// This method coordinates waiting between multiple threads - only one thread
/// performs the actual sleep and topology refresh, while others benefit from
/// the refresh done by the first thread.
/// Returns a connection if a server becomes available, or null if still unavailable.
/// Thread-safe: uses internal synchronization to prevent redundant refresh attempts.
Connection::Ptr getPrimaryConnection();
/// Returns a connection to the primary server.
/// Returns null if no primary is available.
Connection::Ptr getSecondaryConnection();
/// Returns a connection to a secondary server.
/// Returns null if no secondary is available.
[[nodiscard]] Config configuration() const;
// Returns a copy of replica set configuration.
[[nodiscard]] TopologyDescription topology() const;
/// Returns a copy of the current topology description.
void refreshTopology();
/// Forces an immediate topology refresh by querying all known servers.
void startMonitoring();
/// Starts the background monitoring thread if not already running.
void stopMonitoring();
/// Stops the background monitoring thread.
void setSocketFactory(Connection::SocketFactory* factory);
/// Sets the socket factory for creating connections.
/// The factory can access timeout configuration via configuration().connectTimeoutSeconds
/// and configuration().socketTimeoutSeconds.
///
/// Returns the Connection to the master, or null if no master
/// instance was found.
/// Example:
/// rs.setSocketFactory(&myCustomFactory);
protected:
Connection::Ptr isMaster(const Net::SocketAddress& host);
void setReadPreference(const ReadPreference& pref);
/// Sets the default read preference.
[[nodiscard]] ReadPreference readPreference() const;
/// Returns the default read preference.
[[nodiscard]] std::string setName() const;
/// Returns the replica set name, or empty if not discovered.
[[nodiscard]] bool hasPrimary() const;
/// Returns true if a primary server is known.
private:
std::vector<Net::SocketAddress> _addresses;
void monitor() noexcept;
/// Background monitoring thread function.
Connection::Ptr selectServer(const ReadPreference& readPref);
/// Selects a server based on read preference and creates a connection.
Connection::Ptr createConnection(const Net::SocketAddress& address);
/// Creates a new connection to the specified address.
void updateTopologyFromHello(const Net::SocketAddress& address) noexcept;
/// Queries a server with 'hello' command and updates topology.
void updateTopologyFromAllServers() noexcept;
/// Queries all known servers and updates topology.
void parseURI(const std::string& uri);
/// Parses a MongoDB URI into configuration.
/// Extracts hosts and query parameters into _config.
mutable std::mutex _mutex;
Config _config;
TopologyDescription _topology;
std::thread _monitorThread;
std::atomic<bool> _stopMonitoring{false};
std::atomic<bool> _monitoringActive{false};
std::mutex _serverAvailabilityRetryMutex;
std::chrono::steady_clock::time_point _topologyRefreshTime;
};

View File

@@ -0,0 +1,133 @@
//
// ReplicaSetConnection.h
//
// Library: MongoDB
// Package: MongoDB
// Module: ReplicaSetConnection
//
// Definition of the ReplicaSetConnection class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef MongoDB_ReplicaSetConnection_INCLUDED
#define MongoDB_ReplicaSetConnection_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Connection.h"
#include "Poco/MongoDB/ReplicaSet.h"
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/MongoDB/OpMsgMessage.h"
#include "Poco/SharedPtr.h"
#include <functional>
namespace Poco {
namespace MongoDB {
class MongoDB_API ReplicaSetConnection
/// Wrapper around Connection that provides automatic retry and failover
/// for MongoDB replica set operations.
///
/// This class wraps a Connection and automatically retries failed operations
/// on different replica set members. It detects retriable errors (network
/// failures, "not master" errors, etc.) and seamlessly fails over to another
/// suitable server.
///
/// Usage example:
/// ReplicaSet rs(config);
/// ReplicaSetConnection::Ptr conn = new ReplicaSetConnection(rs, ReadPreference::Primary);
///
/// OpMsgMessage request("mydb", "mycollection");
/// request.setCommandName(OpMsgMessage::CMD_FIND);
/// request.body().add("filter", filterDoc);
///
/// OpMsgMessage response;
/// conn->sendRequest(request, response); // Automatic retry on failure
///
/// THREAD SAFETY:
/// This class is NOT thread-safe, just like Connection. Each thread must
/// have its own ReplicaSetConnection instance, or use connection pooling
/// with external synchronization.
///
/// For multi-threaded applications, use ReplicaSetPoolableConnectionFactory
/// with Poco::ObjectPool.
{
public:
using Ptr = Poco::SharedPtr<ReplicaSetConnection>;
ReplicaSetConnection(ReplicaSet& replicaSet, const ReadPreference& readPref);
/// Creates a ReplicaSetConnection for the given replica set and read preference.
/// The connection is established lazily on first use.
~ReplicaSetConnection();
/// Destroys the ReplicaSetConnection.
void sendRequest(OpMsgMessage& request, OpMsgMessage& response);
/// Sends a request and reads the response.
/// Automatically retries on retriable errors with failover.
///
/// Throws Poco::IOException if all retry attempts fail.
void sendRequest(OpMsgMessage& request);
/// Sends a one-way request (fire-and-forget).
/// Sets MSG_MORE_TO_COME flag and acknowledged=false.
///
/// Note: One-way requests are not retried on failure.
void readResponse(OpMsgMessage& response);
/// Reads a response for a previously sent request.
[[nodiscard]] Net::SocketAddress address() const;
/// Returns the address of the currently connected server.
/// Throws Poco::NullPointerException if not connected.
[[nodiscard]] Connection& connection();
/// Returns a reference to the underlying Connection.
/// Throws Poco::NullPointerException if not connected.
void reconnect();
/// Forces reconnection by selecting a new server from the replica set.
/// Useful if you detect an error and want to explicitly retry.
[[nodiscard]] bool isConnected() const noexcept;
/// Returns true if currently connected to a server.
[[nodiscard]] bool matchesReadPreference() const noexcept;
/// Returns true if the currently connected server still matches the read preference.
/// Returns false if not connected or if the server no longer satisfies the read preference.
/// This is useful for connection pool validation to detect when a server role has changed
/// (e.g., primary became secondary).
private:
void ensureConnection();
/// Ensures we have an active connection, creating one if needed.
void executeWithRetry(std::function<void()> operation);
/// Executes an operation with automatic retry on retriable errors.
bool isRetriableError(const std::exception& e);
/// Returns true if the exception represents a retriable error.
bool isRetriableMongoDBError(const OpMsgMessage& response);
/// Returns true if the MongoDB response contains a retriable error code.
void markServerFailed();
/// Marks the current server as failed in the topology.
ReplicaSet& _replicaSet;
ReadPreference _readPreference;
Connection::Ptr _connection;
};
} } // namespace Poco::MongoDB
#endif // MongoDB_ReplicaSetConnection_INCLUDED

View File

@@ -0,0 +1,173 @@
//
// ReplicaSetPoolableConnectionFactory.h
//
// Library: MongoDB
// Package: MongoDB
// Module: ReplicaSetPoolableConnectionFactory
//
// Definition of the ReplicaSetPoolableConnectionFactory class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef MongoDB_ReplicaSetPoolableConnectionFactory_INCLUDED
#define MongoDB_ReplicaSetPoolableConnectionFactory_INCLUDED
#include "Poco/MongoDB/ReplicaSetConnection.h"
#include "Poco/MongoDB/ReplicaSet.h"
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/ObjectPool.h"
namespace Poco {
template<>
class PoolableObjectFactory<MongoDB::ReplicaSetConnection, MongoDB::ReplicaSetConnection::Ptr>
/// PoolableObjectFactory specialization for ReplicaSetConnection.
///
/// New connections are created from the given ReplicaSet with the
/// specified ReadPreference.
///
/// Usage example:
/// Poco::SharedPtr<ReplicaSet> rs(new ReplicaSet(config));
/// PoolableObjectFactory<ReplicaSetConnection, ReplicaSetConnection::Ptr> factory(*rs, ReadPreference::PrimaryPreferred);
/// Poco::ObjectPool<ReplicaSetConnection, ReplicaSetConnection::Ptr> pool(factory, 10, 20);
///
/// {
/// PooledReplicaSetConnection conn(pool);
/// conn->sendRequest(request, response);
/// } // Automatically returned to pool
///
/// IMPORTANT:
/// The ReplicaSet instance must outlive the PoolableObjectFactory and the ObjectPool.
/// Using a SharedPtr for the ReplicaSet is recommended.
{
public:
PoolableObjectFactory(MongoDB::ReplicaSet& replicaSet, const MongoDB::ReadPreference& readPref):
_replicaSet(replicaSet),
_readPreference(readPref)
{
}
MongoDB::ReplicaSetConnection::Ptr createObject()
{
return new MongoDB::ReplicaSetConnection(_replicaSet, _readPreference);
}
bool validateObject(MongoDB::ReplicaSetConnection::Ptr pObject)
{
// Check if the connection is still valid and matches the read preference.
// This ensures that if a server changes role (e.g., primary becomes secondary),
// the cached connection is invalidated and a new one is created.
return !pObject.isNull() && pObject->isConnected() && pObject->matchesReadPreference();
}
void activateObject(MongoDB::ReplicaSetConnection::Ptr pObject)
{
if (!pObject->isConnected())
{
try {
pObject->reconnect();
}
catch (const Poco::Exception& e)
{
// Ignore connect error. c->isConnected() can be used to determine if the connection is valid.
}
}
}
void deactivateObject(MongoDB::ReplicaSetConnection::Ptr pObject)
{
// No action needed - keep connection alive for reuse
}
void destroyObject(MongoDB::ReplicaSetConnection::Ptr pObject)
{
// Connection is destroyed automatically when Ptr goes out of scope
}
private:
MongoDB::ReplicaSet& _replicaSet;
MongoDB::ReadPreference _readPreference;
};
namespace MongoDB {
class PooledReplicaSetConnection
/// Helper class for borrowing and returning a ReplicaSetConnection
/// automatically from a pool.
///
/// This class uses RAII to automatically return the connection to the
/// pool when it goes out of scope.
///
/// Usage:
/// {
/// PooledReplicaSetConnection conn(pool);
/// conn->sendRequest(request, response);
/// } // Connection automatically returned to pool
///
/// Note: The connection pool must outlive the PooledReplicaSetConnection instance.
{
public:
PooledReplicaSetConnection(Poco::ObjectPool<ReplicaSetConnection, ReplicaSetConnection::Ptr>& pool):
_pool(&pool)
{
_connection = _pool->borrowObject();
}
virtual ~PooledReplicaSetConnection()
{
try
{
if (_connection != nullptr)
{
_pool->returnObject(_connection);
}
}
catch (...)
{
poco_unexpected();
}
}
operator ReplicaSetConnection::Ptr ()
{
return _connection;
}
ReplicaSetConnection::Ptr operator->()
{
return _connection;
}
ReplicaSetConnection& operator*()
{
return *_connection;
}
// Disable copy to prevent unwanted release of resources
PooledReplicaSetConnection(const PooledReplicaSetConnection&) = delete;
PooledReplicaSetConnection& operator=(const PooledReplicaSetConnection&) = delete;
// Enable move semantics
PooledReplicaSetConnection(PooledReplicaSetConnection&& other) noexcept = default;
PooledReplicaSetConnection& operator=(PooledReplicaSetConnection&& other) noexcept = default;
private:
Poco::ObjectPool<ReplicaSetConnection, ReplicaSetConnection::Ptr>* _pool;
ReplicaSetConnection::Ptr _connection;
};
} } // namespace Poco::MongoDB
#endif // MongoDB_ReplicaSetPoolableConnectionFactory_INCLUDED

View File

@@ -0,0 +1,208 @@
//
// ReplicaSetURI.h
//
// Library: MongoDB
// Package: MongoDB
// Module: ReplicaSetURI
//
// Definition of the ReplicaSetURI class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef MongoDB_ReplicaSetURI_INCLUDED
#define MongoDB_ReplicaSetURI_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/URI.h"
#include <vector>
#include <string>
namespace Poco {
namespace MongoDB {
class MongoDB_API ReplicaSetURI
/// Class for parsing and generating MongoDB replica set URIs.
///
/// This class handles parsing of MongoDB connection strings in the format:
/// mongodb://[username:password@]host1[:port1][,host2[:port2],...][/[database][?options]]
///
/// It also provides functionality to:
/// - Access and modify the list of servers
/// - Access and modify configuration options
/// - Generate a URI string from the current state
///
/// Usage example:
/// ReplicaSetURI uri("mongodb://host1:27017,host2:27017/?replicaSet=rs0");
///
/// // Access parsed data
/// std::vector<Net::SocketAddress> servers = uri.servers();
/// std::string setName = uri.replicaSet();
///
/// // Modify and regenerate
/// uri.addServer(Net::SocketAddress("host3:27017"));
/// uri.setReadPreference("secondaryPreferred");
/// std::string newUri = uri.toString();
{
public:
// Configuration constants (MongoDB specification compliance)
static constexpr unsigned int DEFAULT_CONNECT_TIMEOUT_MS = 10000;
/// Default connection timeout: 10 seconds
static constexpr unsigned int DEFAULT_SOCKET_TIMEOUT_MS = 30000;
/// Default socket timeout: 30 seconds
static constexpr unsigned int DEFAULT_HEARTBEAT_FREQUENCY_MS = 10000;
/// Default heartbeat frequency: 10 seconds
static constexpr unsigned int MIN_HEARTBEAT_FREQUENCY_MS = 500;
/// Minimum heartbeat frequency per MongoDB SDAM specification: 500 milliseconds
static constexpr unsigned int DEFAULT_RECONNECT_RETRIES = 10;
/// Default number of reconnect attempts
static constexpr unsigned int DEFAULT_RECONNECT_DELAY = 1;
/// Default reconnect delay: 1 second
public:
ReplicaSetURI();
/// Creates an empty ReplicaSetURI.
explicit ReplicaSetURI(const std::string& uri);
/// Creates a ReplicaSetURI by parsing the given MongoDB connection string.
///
/// Throws Poco::SyntaxException if the URI format is invalid.
/// Throws Poco::UnknownURISchemeException if the scheme is not "mongodb".
~ReplicaSetURI();
/// Destroys the ReplicaSetURI.
// Server management
[[nodiscard]] const std::vector<std::string>& servers() const;
/// Returns the list of server addresses as strings (host:port format).
/// Servers are NOT resolved - they remain as strings exactly as provided in the URI.
void setServers(const std::vector<std::string>& servers);
/// Sets the list of server addresses as strings (host:port format).
void addServer(const std::string& server);
/// Adds a server to the list as a string (host:port format).
void clearServers();
/// Clears the list of servers.
// Configuration options
[[nodiscard]] std::string replicaSet() const;
/// Returns the replica set name, or empty string if not set.
void setReplicaSet(const std::string& name);
/// Sets the replica set name.
[[nodiscard]] ReadPreference readPreference() const;
/// Returns the read preference.
void setReadPreference(const ReadPreference& pref);
/// Sets the read preference.
void setReadPreference(const std::string& mode);
/// Sets the read preference from a string mode.
/// Valid modes: primary, primaryPreferred, secondary, secondaryPreferred, nearest
[[nodiscard]] unsigned int connectTimeoutMS() const;
/// Returns the connection timeout in milliseconds.
void setConnectTimeoutMS(unsigned int timeoutMS);
/// Sets the connection timeout in milliseconds.
[[nodiscard]] unsigned int socketTimeoutMS() const;
/// Returns the socket timeout in milliseconds.
void setSocketTimeoutMS(unsigned int timeoutMS);
/// Sets the socket timeout in milliseconds.
[[nodiscard]] unsigned int heartbeatFrequencyMS() const;
/// Returns the heartbeat frequency in milliseconds.
void setHeartbeatFrequencyMS(unsigned int milliseconds);
/// Sets the heartbeat frequency in milliseconds.
/// Throws Poco::InvalidArgumentException if milliseconds < MIN_HEARTBEAT_FREQUENCY_MS (500).
/// Per MongoDB SDAM specification, minimum value is 500 milliseconds.
[[nodiscard]] unsigned int reconnectRetries() const;
/// Returns the number of reconnection retries.
void setReconnectRetries(unsigned int retries);
/// Sets the number of reconnection retries.
[[nodiscard]] unsigned int reconnectDelay() const;
/// Returns the reconnection delay in seconds.
void setReconnectDelay(unsigned int seconds);
/// Sets the reconnection delay in seconds.
[[nodiscard]] std::string database() const;
/// Returns the database name from the URI path, or empty string if not set.
void setDatabase(const std::string& database);
/// Sets the database name.
[[nodiscard]] std::string username() const;
/// Returns the username, or empty string if not set.
void setUsername(const std::string& username);
/// Sets the username.
[[nodiscard]] std::string password() const;
/// Returns the password, or empty string if not set.
void setPassword(const std::string& password);
/// Sets the password.
// URI generation
[[nodiscard]] std::string toString() const;
/// Generates a MongoDB connection string from the current configuration.
/// Format: mongodb://[username:password@]host1:port1[,host2:port2,...][/database][?options]
// Parsing
void parse(const std::string& uri);
/// Parses a MongoDB connection string and updates the configuration.
///
/// Throws Poco::SyntaxException if the URI format is invalid.
/// Throws Poco::UnknownURISchemeException if the scheme is not "mongodb".
private:
void parseOptions(const Poco::URI::QueryParameters& params);
/// Parses query parameters from a QueryParameters collection.
std::string buildQueryString() const;
/// Builds the query string from current configuration options.
std::vector<std::string> _servers;
/// Server addresses stored as strings (host:port format).
/// NOT resolved to avoid DNS errors for non-existent hosts.
std::string _replicaSet;
ReadPreference _readPreference{ReadPreference::Primary};
unsigned int _connectTimeoutMS{DEFAULT_CONNECT_TIMEOUT_MS};
unsigned int _socketTimeoutMS{DEFAULT_SOCKET_TIMEOUT_MS};
unsigned int _heartbeatFrequencyMS{DEFAULT_HEARTBEAT_FREQUENCY_MS};
unsigned int _reconnectRetries{DEFAULT_RECONNECT_RETRIES};
unsigned int _reconnectDelay{DEFAULT_RECONNECT_DELAY};
std::string _database;
std::string _username;
std::string _password;
};
} } // namespace Poco::MongoDB
#endif // MongoDB_ReplicaSetURI_INCLUDED

View File

@@ -0,0 +1,245 @@
//
// ServerDescription.h
//
// Library: MongoDB
// Package: MongoDB
// Module: ServerDescription
//
// Definition of the ServerDescription class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef MongoDB_ServerDescription_INCLUDED
#define MongoDB_ServerDescription_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Document.h"
#include "Poco/Net/SocketAddress.h"
#include "Poco/Timestamp.h"
#include <vector>
#include <string>
namespace Poco {
namespace MongoDB {
class MongoDB_API ServerDescription
/// Represents the state of a single MongoDB server in a replica set.
///
/// This class stores metadata about a MongoDB server obtained from
/// the 'hello' command response (requires MongoDB 5.1 or later), including:
/// - Server type (primary, secondary, arbiter, etc.)
/// - Replica set membership information
/// - Round-trip time for server selection
/// - Server tags for tag-based read preferences
///
/// THREAD SAFETY:
/// This class is NOT thread-safe. External synchronization is required
/// if instances are accessed from multiple threads.
{
public:
enum ServerType
/// MongoDB server type enumeration
{
Unknown, /// Server type not yet determined
Standalone, /// Standalone MongoDB instance (not in a replica set)
RsPrimary, /// Replica set primary (writable)
RsSecondary, /// Replica set secondary (read-only)
RsArbiter, /// Replica set arbiter (no data, votes only)
RsOther, /// Other replica set member type
RsGhost, /// Ghost member (removed or not yet initialized)
Mongos /// MongoDB sharding router
};
ServerDescription();
/// Creates an Unknown server description.
explicit ServerDescription(const Net::SocketAddress& address);
/// Creates an Unknown server description for the given address.
ServerDescription(const ServerDescription& other);
/// Copy constructor.
ServerDescription(ServerDescription&& other) noexcept;
/// Move constructor.
~ServerDescription();
/// Destroys the ServerDescription.
ServerDescription& operator=(const ServerDescription& other);
/// Assignment operator.
ServerDescription& operator=(ServerDescription&& other) noexcept;
/// Move assignment operator.
bool operator==(const ServerDescription& other) const;
/// Equality comparison operator.
/// Compares type, address, setName, and error state.
bool operator!=(const ServerDescription& other) const;
/// Inequality comparison operator.
[[nodiscard]] ServerType type() const;
/// Returns the server type.
[[nodiscard]] const Net::SocketAddress& address() const;
/// Returns the server address.
[[nodiscard]] Timestamp lastUpdateTime() const;
/// Returns the timestamp of the last successful update.
[[nodiscard]] Poco::Int64 roundTripTime() const;
/// Returns the round-trip time in microseconds.
/// This is used for "nearest" read preference selection.
[[nodiscard]] const std::string& setName() const;
/// Returns the replica set name, or empty string if not in a replica set.
[[nodiscard]] bool isWritable() const;
/// Returns true if this server can accept write operations.
/// Only primary servers are writable.
[[nodiscard]] const Document& tags() const;
/// Returns the server tags for tag-based read preferences.
/// Returns an empty document if no tags are configured.
[[nodiscard]] bool isPrimary() const;
/// Returns true if this is a primary server (RsPrimary) or a standalone server.
/// Standalone servers are treated as primaries for read preference purposes.
[[nodiscard]] bool isSecondary() const;
/// Returns true if this is a secondary server.
[[nodiscard]] bool hasError() const;
/// Returns true if the last update attempt resulted in an error.
[[nodiscard]] const std::string& error() const;
/// Returns the last error message, or empty string if no error.
[[nodiscard]] std::vector<Net::SocketAddress> updateFromHelloResponse(const Document& helloResponse, Poco::Int64 rttMicros);
/// Updates the server description from a 'hello' command response.
/// The rttMicros parameter should contain the round-trip time
/// of the hello command in microseconds.
/// Returns a list of all replica set members (hosts, passives, arbiters)
/// discovered in the hello response.
void markError(const std::string& errorMessage);
/// Marks this server as having an error.
/// This sets the type to Unknown and stores the error message.
void setAddress(const Net::SocketAddress& address);
/// Sets the server address.
void reset();
/// Resets the server description to Unknown state.
[[nodiscard]] static std::string typeToString(ServerType type);
/// Converts a server type enum to a human-readable string.
/// Returns "PRIMARY", "SECONDARY", "ARBITER", "STANDALONE",
/// "MONGOS", "OTHER", "GHOST", or "UNKNOWN".
private:
void parseServerType(const Document& doc);
std::vector<Net::SocketAddress> parseHosts(const Document& doc);
void parseTags(const Document& doc);
Net::SocketAddress _address;
ServerType _type{Unknown};
Timestamp _lastUpdateTime;
Poco::Int64 _roundTripTime{0};
std::string _setName;
Document _tags;
std::string _error;
bool _hasError{false};
};
//
// inlines
//
inline ServerDescription::ServerType ServerDescription::type() const
{
return _type;
}
inline const Net::SocketAddress& ServerDescription::address() const
{
return _address;
}
inline Timestamp ServerDescription::lastUpdateTime() const
{
return _lastUpdateTime;
}
inline Poco::Int64 ServerDescription::roundTripTime() const
{
return _roundTripTime;
}
inline const std::string& ServerDescription::setName() const
{
return _setName;
}
inline bool ServerDescription::isWritable() const
{
return _type == RsPrimary || _type == Standalone;
}
inline const Document& ServerDescription::tags() const
{
return _tags;
}
inline bool ServerDescription::isPrimary() const
{
// Standalone servers should be treated as primary for read preference purposes
return _type == RsPrimary || _type == Standalone;
}
inline bool ServerDescription::isSecondary() const
{
return _type == RsSecondary;
}
inline bool ServerDescription::hasError() const
{
return _hasError;
}
inline const std::string& ServerDescription::error() const
{
return _error;
}
inline void ServerDescription::setAddress(const Net::SocketAddress& address)
{
_address = address;
}
} } // namespace Poco::MongoDB
#endif // MongoDB_ServerDescription_INCLUDED

View File

@@ -0,0 +1,123 @@
//
// TopologyChangeNotification.h
//
// Library: MongoDB
// Package: MongoDB
// Module: TopologyChangeNotification
//
// Definition of the TopologyChangeNotification class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef MongoDB_TopologyChangeNotification_INCLUDED
#define MongoDB_TopologyChangeNotification_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/Notification.h"
#include "Poco/Dynamic/Struct.h"
#include <string>
namespace Poco {
namespace MongoDB {
class MongoDB_API TopologyChangeNotification : public Notification
/// Notification sent when MongoDB replica set topology changes.
///
/// This notification is posted to Poco::NotificationCenter::defaultCenter()
/// whenever a topology change is detected during topology refresh.
///
/// The notification contains a Dynamic::Struct with the following members:
/// - replicaSet (std::string): The replica set name
/// - timestamp (Poco::Int64): Timestamp in seconds since Unix epoch
/// - topologyType (std::string): Human-readable topology type
/// (e.g., "Replica Set (with Primary)", "Single Server", etc.)
/// - changeDescription (std::string): Brief description of what changed
/// (e.g., "Primary elected: mongo1:27017", "Servers: 2 -> 3")
///
/// Example usage:
/// class MyClass
/// {
/// public:
/// MyClass()
/// {
/// // Register observer
/// NotificationCenter::defaultCenter().addNObserver(
/// *this,
/// &MyClass::handleTopologyChange
/// );
/// }
///
/// ~MyClass()
/// {
/// // Unregister observer
/// NotificationCenter::defaultCenter().removeNObserver(
/// *this,
/// &MyClass::handleTopologyChange
/// );
/// }
///
/// void handleTopologyChange(const AutoPtr<TopologyChangeNotification>& pNf)
/// {
/// const auto& data = pNf->data();
///
/// std::string rsName = data["replicaSet"];
/// Poco::Int64 timestamp = data["timestamp"];
/// std::string topologyType = data["topologyType"];
/// std::string changeDesc = data["changeDescription"];
///
/// // Handle topology change...
/// }
/// };
{
public:
using Ptr = AutoPtr<TopologyChangeNotification>;
TopologyChangeNotification(const Dynamic::Struct<std::string>& data);
/// Creates a TopologyChangeNotification with the given data.
const Dynamic::Struct<std::string>& data() const;
/// Returns the topology change data.
std::string name() const override;
/// Returns the notification name.
private:
Dynamic::Struct<std::string> _data;
};
//
// inlines
//
inline TopologyChangeNotification::TopologyChangeNotification(const Dynamic::Struct<std::string>& data):
_data(data)
{
}
inline const Dynamic::Struct<std::string>& TopologyChangeNotification::data() const
{
return _data;
}
inline std::string TopologyChangeNotification::name() const
{
return "TopologyChangeNotification";
}
} } // namespace Poco::MongoDB
#endif // MongoDB_TopologyChangeNotification_INCLUDED

View File

@@ -0,0 +1,168 @@
//
// TopologyDescription.h
//
// Library: MongoDB
// Package: MongoDB
// Module: TopologyDescription
//
// Definition of the TopologyDescription class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef MongoDB_TopologyDescription_INCLUDED
#define MongoDB_TopologyDescription_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/ServerDescription.h"
#include "Poco/MongoDB/Document.h"
#include "Poco/Net/SocketAddress.h"
#include <vector>
#include <map>
#include <string>
#include <mutex>
namespace Poco {
namespace MongoDB {
class MongoDB_API TopologyDescription
/// Maintains the complete MongoDB replica set topology state.
///
/// This class tracks all known servers in a replica set and their
/// current state. It is updated based on 'hello' command responses
/// from MongoDB servers.
///
/// The topology type automatically transitions based on server
/// discovery:
/// - Unknown: Initial state, no servers contacted
/// - Single: Single standalone server
/// - ReplicaSetNoPrimary: Replica set without a primary
/// - ReplicaSetWithPrimary: Replica set with a primary
/// - Sharded: Sharded cluster (mongos routers)
///
/// THREAD SAFETY:
/// This class is thread-safe. All public methods use internal
/// synchronization to protect the topology state.
{
public:
enum TopologyType
/// MongoDB topology type enumeration
{
Unknown, /// Topology not yet determined
Single, /// Single server (standalone)
ReplicaSetNoPrimary, /// Replica set without primary
ReplicaSetWithPrimary, /// Replica set with primary
Sharded /// Sharded cluster
};
TopologyDescription();
/// Creates an empty topology description.
explicit TopologyDescription(const std::string& setName);
/// Creates a topology description for a replica set with the given name.
TopologyDescription(const TopologyDescription& other);
/// Copy constructor.
TopologyDescription(TopologyDescription&& other) noexcept;
/// Move constructor.
~TopologyDescription();
/// Destroys the TopologyDescription.
TopologyDescription& operator=(const TopologyDescription& other);
/// Assignment operator.
TopologyDescription& operator=(TopologyDescription&& other) noexcept;
/// Move assignment operator.
bool operator==(const TopologyDescription& other) const;
/// Equality comparison operator.
/// Compares topology type, set name, and all servers.
bool operator!=(const TopologyDescription& other) const;
/// Inequality comparison operator.
[[nodiscard]] TopologyType type() const;
/// Returns the current topology type.
[[nodiscard]] std::string setName() const;
/// Returns the replica set name, or empty string if not a replica set.
void setName(const std::string& name);
/// Sets the replica set name.
[[nodiscard]] std::vector<ServerDescription> servers() const;
/// Returns a copy of all server descriptions.
/// This is thread-safe but creates a copy.
[[nodiscard]] ServerDescription findPrimary() const;
/// Finds and returns the primary server.
/// Returns an Unknown server description if no primary exists.
[[nodiscard]] std::vector<ServerDescription> findSecondaries() const;
/// Finds and returns all secondary servers.
[[nodiscard]] bool hasPrimary() const;
/// Returns true if a primary server exists in the topology.
[[nodiscard]] bool hasServer(const Net::SocketAddress& address) const;
/// Returns true if the server with the given address is in the topology.
[[nodiscard]] ServerDescription getServer(const Net::SocketAddress& address) const;
/// Returns the server description for the given address.
/// Returns an Unknown server description if not found.
const ServerDescription& updateServer(const Net::SocketAddress& address, const Document& helloResponse, Poco::Int64 rttMicros);
/// Updates a server's description from a 'hello' command response.
/// If the server doesn't exist in the topology, it is added.
/// This may also trigger topology type transitions.
///
/// Returns a const reference to the updated server description.
void markServerUnknown(const Net::SocketAddress& address, const std::string& error = "");
/// Marks a server as Unknown (typically after an error).
/// This may trigger topology type transitions.
void addServer(const Net::SocketAddress& address);
/// Adds a server to the topology in Unknown state.
/// If the server already exists, this is a no-op.
void removeServer(const Net::SocketAddress& address);
/// Removes a server from the topology.
/// This may trigger topology type transitions.
void clear();
/// Removes all servers and resets to Unknown topology type.
[[nodiscard]] std::size_t serverCount() const;
/// Returns the number of servers in the topology.
[[nodiscard]] static std::string typeToString(TopologyType type);
/// Converts a topology type enum to a human-readable string.
/// Returns "Unknown", "Single Server", "Replica Set (with Primary)",
/// "Replica Set (no Primary)", or "Sharded Cluster".
private:
void updateTopologyType();
/// Updates the topology type based on current server states.
/// Must be called while holding the mutex.
mutable std::mutex _mutex;
TopologyType _type{Unknown};
std::string _setName;
std::map<Net::SocketAddress, ServerDescription> _servers;
};
} } // namespace Poco::MongoDB
#endif // MongoDB_TopologyDescription_INCLUDED

View File

@@ -1 +1,2 @@
add_subdirectory(SQLToMongo)
add_subdirectory(ReplicaSet)

View File

@@ -0,0 +1,17 @@
# ReplicaSet sample - demonstrates replica set features
set(SAMPLE_NAME "ReplicaSet")
set(SRCS src/ReplicaSet.cpp)
add_executable(${SAMPLE_NAME} ${SRCS})
target_link_libraries(${SAMPLE_NAME} Poco::MongoDB Poco::Net Poco::Foundation)
# ReplicaSetMonitor sample - monitoring and health check tool
set(MONITOR_NAME "ReplicaSetMonitor")
set(MONITOR_SRCS src/ReplicaSetMonitor.cpp)
add_executable(${MONITOR_NAME} ${MONITOR_SRCS})
target_link_libraries(${MONITOR_NAME} Poco::MongoDB Poco::Net Poco::Foundation)
# URIExample sample - demonstrates URI parsing
set(URI_NAME "URIExample")
set(URI_SRCS src/URIExample.cpp)
add_executable(${URI_NAME} ${URI_SRCS})
target_link_libraries(${URI_NAME} Poco::MongoDB Poco::Net Poco::Foundation)

View File

@@ -0,0 +1,429 @@
# MongoDB Replica Set Examples
This directory contains comprehensive examples demonstrating Poco::MongoDB replica set support with automatic failover, read preferences, and connection pooling.
**Minimum MongoDB Version**: MongoDB 5.1 or later (for replica set features)
## Examples Overview
| Sample | Description |
|--------|-------------|
| **ReplicaSetMonitor** | Production-ready monitoring tool for deployment verification and continuous health monitoring |
| **ReplicaSet** | Feature demonstrations with multiple commands (basic, readpref, failover, pool, topology) |
| **URIExample** | MongoDB URI parsing and connection demonstration |
---
## ReplicaSetMonitor - Deployment Health Check Tool
A continuous monitoring tool that performs regular read/write operations and displays replica set topology status. Ideal for deployment verification and health monitoring.
### Features
- **Continuous Health Checks**: Performs writes to primary and reads from replica set at configurable intervals
- **Real-time Topology Display**: Shows current replica set status, server roles, and round-trip times
- **Statistics Tracking**: Monitors success rates for read and write operations
- **Deployment Verification**: Quickly verify replica set is functioning correctly
- **Automatic Failover Testing**: Continues working even during primary elections
### Usage
```bash
# Basic usage with defaults
./ReplicaSetMonitor
# Using MongoDB URI (recommended)
./ReplicaSetMonitor -u 'mongodb://mongo1:27017,mongo2:27017/?replicaSet=rs0'
# Specify replica set and hosts (traditional method)
./ReplicaSetMonitor -s rs0 -H mongo1:27017,mongo2:27017,mongo3:27017
# Run with 10-second intervals for 100 iterations
./ReplicaSetMonitor -i 10 -n 100
# Verbose mode for detailed operation output
./ReplicaSetMonitor -v
# Full example with URI
./ReplicaSetMonitor \
--uri 'mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=rs0&readPreference=primaryPreferred' \
--interval 5 \
--database test \
--collection health_check \
--verbose
# Full example with traditional options
./ReplicaSetMonitor \
--set rs0 \
--hosts mongo1:27017,mongo2:27017,mongo3:27017 \
--interval 5 \
--database test \
--collection health_check \
--verbose
```
### Command-Line Options
| Option | Description | Default |
|--------|-------------|---------|
| `-h, --help` | Show help message | - |
| `-u, --uri URI` | MongoDB connection URI (takes precedence) | - |
| `-s, --set NAME` | Replica set name | `rs0` |
| `-H, --hosts HOSTS` | Comma-separated host:port list | `localhost:27017,localhost:27018,localhost:27019` |
| `-i, --interval SECONDS` | Check interval in seconds | `5` |
| `-d, --database NAME` | Database name | `test` |
| `-c, --collection NAME` | Collection name | `poco_monitor` |
| `-v, --verbose` | Verbose output | `false` |
| `-n, --iterations N` | Number of iterations | unlimited |
**Note:** The `--uri` option takes precedence over `--set` and `--hosts` options.
### Environment Variables
- `MONGODB_URI`: MongoDB connection URI (takes precedence)
- `MONGODB_REPLICA_SET`: Replica set name
- `MONGODB_HOSTS`: Comma-separated host:port list
### Example Output
```
Connecting to replica set: rs0
Seed servers: mongo1:27017, mongo2:27017, mongo3:27017
Check interval: 5 seconds
Replica set connected successfully!
Background monitoring active.
================================================================================
TOPOLOGY STATUS
================================================================================
Replica Set: rs0
Type: Replica Set (with Primary)
Has Primary: Yes
Servers: 3
--------------------------------------------------------------------------------
Address Type RTT (ms) Status
--------------------------------------------------------------------------------
mongo1:27017 PRIMARY 2.34 OK
mongo2:27017 SECONDARY 3.12 OK
mongo3:27017 SECONDARY 2.89 OK
================================================================================
[2025-11-26T21:15:00Z] Check #1
Write (Primary): ✓ OK (12 ms)
Read (PrimaryPreferred): ✓ OK (8 ms)
Statistics: Writes: 1/1 (100.0%), Reads: 1/1 (100.0%)
--------------------------------------------------------------------------------
[2025-11-26T21:15:05Z] Check #2
Write (Primary): ✓ OK (9 ms)
Read (PrimaryPreferred): ✓ OK (7 ms)
Statistics: Writes: 2/2 (100.0%), Reads: 2/2 (100.0%)
--------------------------------------------------------------------------------
```
### Use Cases
#### 1. Deployment Verification
Run the monitor immediately after deploying a replica set:
```bash
# Run for 60 iterations (5 minutes with 5-second intervals)
./ReplicaSetMonitor -s production-rs -H prod1:27017,prod2:27017,prod3:27017 -n 60
```
#### 2. Load Testing
Combine with multiple instances to generate load:
```bash
# Run multiple monitors in parallel
for i in {1..10}; do
./ReplicaSetMonitor -i 1 &
done
```
#### 3. Failover Testing
Run the monitor while performing failover operations:
```bash
# Start monitor
./ReplicaSetMonitor -v
# In another terminal, step down the primary:
# mongo --eval "rs.stepDown()"
# Monitor will automatically failover and continue operations
```
#### 4. Continuous Monitoring
Run as a long-term health check:
```bash
# Run indefinitely with 30-second intervals
./ReplicaSetMonitor -i 30 > replica_set_health.log 2>&1
```
### Docker Compose Testing
Create a local replica set for testing:
```yaml
# docker-compose.yml
version: '3'
services:
mongo1:
image: mongo:7.0
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports: ["27017:27017"]
mongo2:
image: mongo:7.0
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports: ["27018:27017"]
mongo3:
image: mongo:7.0
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports: ["27019:27017"]
```
Initialize the replica set:
```bash
docker-compose up -d
docker exec -it $(docker ps -q -f name=mongo1) mongosh --eval "
rs.initiate({
_id: 'rs0',
members: [
{ _id: 0, host: 'localhost:27017' },
{ _id: 1, host: 'localhost:27018' },
{ _id: 2, host: 'localhost:27019' }
]
})"
# Wait a few seconds for election
sleep 5
# Run the monitor
./ReplicaSetMonitor -s rs0 -H localhost:27017,localhost:27018,localhost:27019
```
---
## ReplicaSet - Feature Examples
Demonstrates various replica set features with multiple commands.
### Usage
```bash
./ReplicaSet <command>
```
### Commands
| Command | Description |
|---------|-------------|
| `basic` | Basic replica set connection and operations |
| `readpref` | Read preference examples (primary, secondary, nearest) |
| `failover` | Automatic failover demonstration |
| `pool` | Connection pooling example |
| `topology` | Topology discovery and monitoring |
### Examples
```bash
# Basic connection
./ReplicaSet basic
# Try different read preferences
./ReplicaSet readpref
# Demonstrate automatic failover
./ReplicaSet failover
# Show connection pooling
./ReplicaSet pool
# Display topology information
./ReplicaSet topology
```
---
## URIExample - URI Parsing Demonstration
Demonstrates MongoDB URI parsing and connection to replica sets.
### Features
- Parse MongoDB connection URIs
- Display parsed configuration (hosts, replica set name, read preference, timeouts)
- Connect to replica set and show topology
- Query server information
- Validate URI format
### Usage
```bash
./URIExample <uri>
```
### Examples
```bash
# Basic replica set URI
./URIExample 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0'
# With read preference
./URIExample 'mongodb://mongo1:27017,mongo2:27017/?replicaSet=rs0&readPreference=primaryPreferred'
# With custom timeouts and heartbeat
./URIExample 'mongodb://host1:27017,host2:27017/?replicaSet=rs0&connectTimeoutMS=5000&socketTimeoutMS=30000&heartbeatFrequencyMS=5000'
```
### Supported URI Options
- `replicaSet=name` - Replica set name
- `readPreference=mode` - Read preference (primary|primaryPreferred|secondary|secondaryPreferred|nearest)
- `connectTimeoutMS=ms` - Connection timeout in milliseconds (for custom SocketFactory implementations)
- `socketTimeoutMS=ms` - Socket timeout in milliseconds (for custom SocketFactory implementations)
- `heartbeatFrequencyMS=milliseconds` - Heartbeat frequency in milliseconds (default: 10000)
- `reconnectRetries=n` - Number of reconnection retries (default: 10)
- `reconnectDelay=seconds` - Delay between reconnection attempts in seconds (default: 1)
### Example Output
```
Parsing MongoDB Replica Set URI
================================================================================
URI: mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0
✓ URI parsed successfully!
Configuration:
--------------------------------------------------------------------------------
Replica Set Name: rs0
Read Preference: primary
Seed Servers: localhost:27017, localhost:27018, localhost:27019
Monitoring: Active
Connecting to replica set...
✓ Connected to primary: localhost:27017
Server Information:
--------------------------------------------------------------------------------
MongoDB Version: 7.0.5
Git Version: 7809d71e84e314b497f282ea52598668b08b84dd
Replica Set Topology:
--------------------------------------------------------------------------------
Set Name: rs0
Has Primary: Yes
Servers: 3
localhost:27017 [PRIMARY] RTT: 2.34 ms
localhost:27018 [SECONDARY] RTT: 3.12 ms
localhost:27019 [SECONDARY] RTT: 2.89 ms
✓ Success!
```
---
## Building the Examples
### With CMake
```bash
cd poco
mkdir build && cd build
cmake .. -DENABLE_MONGODB=ON -DENABLE_SAMPLES=ON
cmake --build . --target ReplicaSetMonitor
cmake --build . --target ReplicaSet
cmake --build . --target URIExample
# Executables are in bin/
./bin/ReplicaSetMonitor --help
./bin/ReplicaSet basic
./bin/URIExample 'mongodb://localhost:27017/?replicaSet=rs0'
```
### With Make
```bash
cd MongoDB/samples/ReplicaSet
make
./ReplicaSetMonitor --help
```
---
## Troubleshooting
### "No suitable server found in replica set"
- Verify MongoDB servers are running: `nc -zv localhost 27017`
- Check replica set is initialized: `mongosh --eval "rs.status()"`
- Verify network connectivity between hosts
- Check replica set name matches: `-s` option should match `rs.status()._id`
### "Connection failed" errors
- Ensure MongoDB is binding to the correct interface (`--bind_ip_all`)
- Check firewall rules allow connections to MongoDB ports
- Verify authentication is disabled or credentials are provided
### High latency or timeouts
- Check network conditions between client and MongoDB servers
- Increase timeout values in replica set configuration
- Verify MongoDB servers are not overloaded
---
## Advanced Configuration
### Custom Read Preference with Tags
```cpp
// Target servers in specific datacenter
Document tags;
tags.add("dc", "east");
tags.add("rack", "1");
ReadPreference pref(ReadPreference::Nearest, tags);
```
### Custom Heartbeat Frequency
```cpp
ReplicaSet::Config config;
config.heartbeatFrequencySeconds = 30; // 30 seconds (default: 10)
```
### Disable Background Monitoring
```cpp
ReplicaSet::Config config;
config.enableMonitoring = false; // Manual topology refresh only
```
### Custom Reconnection Settings
```cpp
ReplicaSet::Config config;
config.serverReconnectRetries = 5; // Number of retries (default: 10)
config.serverReconnectDelaySeconds = 2; // Delay between retries in seconds (default: 1)
```
---
## Additional Resources
- [MongoDB Replica Set Documentation](https://www.mongodb.com/docs/manual/replication/)
- [Server Discovery and Monitoring Spec](https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst)
- [Poco Documentation](https://pocoproject.org/docs/)

View File

@@ -0,0 +1,462 @@
//
// ReplicaSet.cpp
//
// This sample demonstrates how to use MongoDB replica set features in Poco::MongoDB.
//
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Connection.h"
#include "Poco/MongoDB/Database.h"
#include "Poco/MongoDB/ReplicaSet.h"
#include "Poco/MongoDB/ReplicaSetConnection.h"
#include "Poco/MongoDB/ReplicaSetPoolableConnectionFactory.h"
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/MongoDB/Document.h"
#include "Poco/MongoDB/Array.h"
#include "Poco/MongoDB/ObjectId.h"
#include "Poco/MongoDB/OpMsgMessage.h"
#include "Poco/SharedPtr.h"
#include "Poco/ObjectPool.h"
#include "Poco/Exception.h"
#include <iostream>
#include <chrono>
#include <thread>
using namespace Poco::MongoDB;
using namespace Poco;
void printUsage()
{
std::cout << "Usage: ReplicaSet <command> [options]" << std::endl;
std::cout << std::endl;
std::cout << "Commands:" << std::endl;
std::cout << " basic - Basic replica set connection example" << std::endl;
std::cout << " readpref - Read preference examples" << std::endl;
std::cout << " failover - Automatic failover demonstration" << std::endl;
std::cout << " pool - Connection pooling example" << std::endl;
std::cout << " topology - Topology discovery and monitoring" << std::endl;
std::cout << std::endl;
std::cout << "Environment variables:" << std::endl;
std::cout << " MONGODB_REPLICA_SET - Replica set name (default: rs0)" << std::endl;
std::cout << " MONGODB_HOSTS - Comma-separated host:port list (default: localhost:27017,localhost:27018,localhost:27019)" << std::endl;
std::cout << std::endl;
}
std::vector<Net::SocketAddress> parseHosts(const std::string& hostsStr)
{
std::vector<Net::SocketAddress> hosts;
std::size_t start = 0;
std::size_t end = hostsStr.find(',');
while (end != std::string::npos)
{
hosts.push_back(Net::SocketAddress(hostsStr.substr(start, end - start)));
start = end + 1;
end = hostsStr.find(',', start);
}
hosts.push_back(Net::SocketAddress(hostsStr.substr(start)));
return hosts;
}
void basicExample()
{
std::cout << "=== Basic Replica Set Connection ===" << std::endl;
std::cout << std::endl;
try
{
// Get replica set configuration from environment
std::string setName = std::getenv("MONGODB_REPLICA_SET") ? std::getenv("MONGODB_REPLICA_SET") : "rs0";
std::string hostsStr = std::getenv("MONGODB_HOSTS") ? std::getenv("MONGODB_HOSTS") : "localhost:27017,localhost:27018,localhost:27019";
std::cout << "Connecting to replica set: " << setName << std::endl;
std::cout << "Seed hosts: " << hostsStr << std::endl;
std::cout << std::endl;
// Configure replica set
ReplicaSet::Config config;
config.setName = setName;
config.seeds = parseHosts(hostsStr);
config.readPreference = ReadPreference(ReadPreference::Primary);
config.enableMonitoring = true;
// Create replica set
ReplicaSet rs(config);
std::cout << "Replica set discovered successfully!" << std::endl;
std::cout << "Replica set name: " << rs.setName() << std::endl;
std::cout << "Has primary: " << (rs.hasPrimary() ? "Yes" : "No") << std::endl;
std::cout << std::endl;
// Get primary connection
Connection::Ptr conn = rs.getPrimaryConnection();
if (conn.isNull())
{
std::cerr << "ERROR: No primary server available" << std::endl;
return;
}
std::cout << "Connected to primary: " << conn->address().toString() << std::endl;
std::cout << std::endl;
// Perform a simple operation
Database db("test");
Document::Ptr buildInfo = db.queryBuildInfo(*conn);
std::cout << "MongoDB version: " << buildInfo->get<std::string>("version") << std::endl;
std::cout << std::endl;
// Insert a test document
std::cout << "Inserting test document..." << std::endl;
Document::Ptr doc = new Document();
doc->add("name", "Replica Set Example");
doc->add("timestamp", static_cast<Poco::Int64>(time(nullptr)));
doc->add("message", "Hello from Poco::MongoDB replica set!");
OpMsgMessage insertRequest("test", "poco_samples");
insertRequest.setCommandName(OpMsgMessage::CMD_INSERT);
insertRequest.documents().push_back(doc);
OpMsgMessage response;
conn->sendRequest(insertRequest, response);
if (response.responseOk())
{
std::cout << "Document inserted successfully!" << std::endl;
std::cout << "Response: " << response.body().toString() << std::endl;
}
else
{
std::cerr << "ERROR: Insert failed: " << response.body().toString() << std::endl;
}
}
catch (const Exception& e)
{
std::cerr << "ERROR: " << e.displayText() << std::endl;
}
std::cout << std::endl;
}
void readPreferenceExample()
{
std::cout << "=== Read Preference Examples ===" << std::endl;
std::cout << std::endl;
try
{
std::string setName = std::getenv("MONGODB_REPLICA_SET") ? std::getenv("MONGODB_REPLICA_SET") : "rs0";
std::string hostsStr = std::getenv("MONGODB_HOSTS") ? std::getenv("MONGODB_HOSTS") : "localhost:27017,localhost:27018,localhost:27019";
ReplicaSet::Config config;
config.setName = setName;
config.seeds = parseHosts(hostsStr);
ReplicaSet rs(config);
// Primary read preference
std::cout << "1. Primary read preference:" << std::endl;
Connection::Ptr primaryConn = rs.getConnection(ReadPreference(ReadPreference::Primary));
if (!primaryConn.isNull())
{
std::cout << " Connected to: " << primaryConn->address().toString() << std::endl;
}
std::cout << std::endl;
// Secondary read preference
std::cout << "2. Secondary read preference:" << std::endl;
Connection::Ptr secondaryConn = rs.getConnection(ReadPreference(ReadPreference::Secondary));
if (!secondaryConn.isNull())
{
std::cout << " Connected to: " << secondaryConn->address().toString() << std::endl;
}
else
{
std::cout << " No secondary available" << std::endl;
}
std::cout << std::endl;
// PrimaryPreferred read preference
std::cout << "3. PrimaryPreferred read preference:" << std::endl;
Connection::Ptr prefConn = rs.getConnection(ReadPreference(ReadPreference::PrimaryPreferred));
if (!prefConn.isNull())
{
std::cout << " Connected to: " << prefConn->address().toString() << std::endl;
}
std::cout << std::endl;
// Nearest read preference (lowest latency)
std::cout << "4. Nearest read preference (lowest latency):" << std::endl;
Connection::Ptr nearestConn = rs.getConnection(ReadPreference(ReadPreference::Nearest));
if (!nearestConn.isNull())
{
std::cout << " Connected to: " << nearestConn->address().toString() << std::endl;
}
std::cout << std::endl;
}
catch (const Exception& e)
{
std::cerr << "ERROR: " << e.displayText() << std::endl;
}
}
void failoverExample()
{
std::cout << "=== Automatic Failover Demonstration ===" << std::endl;
std::cout << std::endl;
try
{
std::string setName = std::getenv("MONGODB_REPLICA_SET") ? std::getenv("MONGODB_REPLICA_SET") : "rs0";
std::string hostsStr = std::getenv("MONGODB_HOSTS") ? std::getenv("MONGODB_HOSTS") : "localhost:27017,localhost:27018,localhost:27019";
ReplicaSet::Config config;
config.setName = setName;
config.seeds = parseHosts(hostsStr);
config.readPreference = ReadPreference(ReadPreference::PrimaryPreferred);
ReplicaSet rs(config);
// Create a replica set connection with automatic failover
ReplicaSetConnection::Ptr rsConn = new ReplicaSetConnection(rs, ReadPreference(ReadPreference::PrimaryPreferred));
std::cout << "Using ReplicaSetConnection for automatic failover" << std::endl;
std::cout << "Initial connection: " << rsConn->address().toString() << std::endl;
std::cout << std::endl;
// Perform multiple operations
// If a server fails, operations will automatically retry on another server
for (int i = 0; i < 5; ++i)
{
std::cout << "Operation " << (i + 1) << ": ";
try
{
Document::Ptr doc = new Document();
doc->add("iteration", i);
doc->add("timestamp", static_cast<Poco::Int64>(time(nullptr)));
OpMsgMessage insertRequest("test", "poco_failover");
insertRequest.setCommandName(OpMsgMessage::CMD_INSERT);
insertRequest.documents().push_back(doc);
OpMsgMessage response;
rsConn->sendRequest(insertRequest, response);
if (response.responseOk())
{
std::cout << "Success (server: " << rsConn->address().toString() << ")" << std::endl;
}
else
{
std::cout << "Failed: " << response.body().toString() << std::endl;
}
}
catch (const Exception& e)
{
std::cout << "Failed: " << e.displayText() << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Sleep 1 second between operations
}
std::cout << std::endl;
std::cout << "Note: If a server fails during these operations, the connection" << std::endl;
std::cout << "will automatically fail over to another replica set member." << std::endl;
}
catch (const Exception& e)
{
std::cerr << "ERROR: " << e.displayText() << std::endl;
}
std::cout << std::endl;
}
void poolExample()
{
std::cout << "=== Connection Pooling Example ===" << std::endl;
std::cout << std::endl;
try
{
std::string setName = std::getenv("MONGODB_REPLICA_SET") ? std::getenv("MONGODB_REPLICA_SET") : "rs0";
std::string hostsStr = std::getenv("MONGODB_HOSTS") ? std::getenv("MONGODB_HOSTS") : "localhost:27017,localhost:27018,localhost:27019";
ReplicaSet::Config config;
config.setName = setName;
config.seeds = parseHosts(hostsStr);
SharedPtr<ReplicaSet> rs(new ReplicaSet(config));
std::cout << "Creating connection pool..." << std::endl;
std::cout << " Capacity: 5 connections" << std::endl;
std::cout << " Peak reserve: 10 connections" << std::endl;
std::cout << std::endl;
// Create connection pool
PoolableObjectFactory<ReplicaSetConnection, ReplicaSetConnection::Ptr> factory(*rs, ReadPreference(ReadPreference::PrimaryPreferred));
ObjectPool<ReplicaSetConnection, ReplicaSetConnection::Ptr> pool(factory, 5, 10);
// Use pooled connections
std::cout << "Performing operations with pooled connections..." << std::endl;
for (int i = 0; i < 10; ++i)
{
// Borrow connection from pool (RAII pattern)
PooledReplicaSetConnection conn(pool);
std::cout << " Operation " << (i + 1) << ": ";
Document::Ptr doc = new Document();
doc->add("pool_test", i);
doc->add("timestamp", static_cast<Poco::Int64>(time(nullptr)));
OpMsgMessage insertRequest("test", "poco_pool");
insertRequest.setCommandName(OpMsgMessage::CMD_INSERT);
insertRequest.documents().push_back(doc);
OpMsgMessage response;
conn->sendRequest(insertRequest, response);
if (response.responseOk())
{
std::cout << "Success" << std::endl;
}
else
{
std::cout << "Failed" << std::endl;
}
// Connection is automatically returned to pool when conn goes out of scope
}
std::cout << std::endl;
std::cout << "All operations completed using pooled connections." << std::endl;
}
catch (const Exception& e)
{
std::cerr << "ERROR: " << e.displayText() << std::endl;
}
std::cout << std::endl;
}
void topologyExample()
{
std::cout << "=== Topology Discovery and Monitoring ===" << std::endl;
std::cout << std::endl;
try
{
std::string setName = std::getenv("MONGODB_REPLICA_SET") ? std::getenv("MONGODB_REPLICA_SET") : "rs0";
std::string hostsStr = std::getenv("MONGODB_HOSTS") ? std::getenv("MONGODB_HOSTS") : "localhost:27017,localhost:27018,localhost:27019";
ReplicaSet::Config config;
config.setName = setName;
config.seeds = parseHosts(hostsStr);
config.heartbeatFrequencySeconds = 5; // 5 seconds
config.enableMonitoring = true;
ReplicaSet rs(config);
std::cout << "Replica Set Topology:" << std::endl;
std::cout << " Set name: " << rs.setName() << std::endl;
std::cout << std::endl;
TopologyDescription topology = rs.topology();
std::vector<ServerDescription> servers = topology.servers();
std::cout << " Servers (" << servers.size() << "):" << std::endl;
for (const auto& server : servers)
{
std::cout << " - " << server.address().toString() << std::endl;
std::cout << " Type: ";
switch (server.type())
{
case ServerDescription::RsPrimary:
std::cout << "Primary";
break;
case ServerDescription::RsSecondary:
std::cout << "Secondary";
break;
case ServerDescription::RsArbiter:
std::cout << "Arbiter";
break;
case ServerDescription::Unknown:
std::cout << "Unknown";
break;
default:
std::cout << "Other";
}
std::cout << std::endl;
std::cout << " RTT: " << (server.roundTripTime() / 1000.0) << " ms" << std::endl;
if (server.hasError())
{
std::cout << " Error: " << server.error() << std::endl;
}
}
std::cout << std::endl;
std::cout << "Background monitoring is active (heartbeat every " << config.heartbeatFrequencySeconds << "s)" << std::endl;
std::cout << "Topology will be automatically updated as servers change state." << std::endl;
}
catch (const Exception& e)
{
std::cerr << "ERROR: " << e.displayText() << std::endl;
}
std::cout << std::endl;
}
int main(int argc, char** argv)
{
if (argc < 2)
{
printUsage();
return 1;
}
std::string command(argv[1]);
if (command == "basic")
{
basicExample();
}
else if (command == "readpref")
{
readPreferenceExample();
}
else if (command == "failover")
{
failoverExample();
}
else if (command == "pool")
{
poolExample();
}
else if (command == "topology")
{
topologyExample();
}
else
{
std::cerr << "Unknown command: " << command << std::endl;
std::cout << std::endl;
printUsage();
return 1;
}
return 0;
}

View File

@@ -0,0 +1,547 @@
//
// ReplicaSetMonitor.cpp
//
// MongoDB Replica Set Monitoring and Health Check Tool
//
// This tool continuously monitors a MongoDB replica set by performing
// regular read/write operations and displaying the topology status.
// Useful for deployment verification and replica set health monitoring.
//
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Connection.h"
#include "Poco/MongoDB/Database.h"
#include "Poco/MongoDB/ReplicaSet.h"
#include "Poco/MongoDB/ReplicaSetConnection.h"
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/MongoDB/Document.h"
#include "Poco/MongoDB/ObjectId.h"
#include "Poco/MongoDB/OpMsgMessage.h"
#include "Poco/SharedPtr.h"
#include "Poco/Exception.h"
#include "Poco/Thread.h"
#include "Poco/Stopwatch.h"
#include "Poco/DateTime.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
using namespace Poco::MongoDB;
using namespace Poco;
struct MonitorConfig
{
std::string uri;
std::string setName;
std::vector<Net::SocketAddress> seeds;
int intervalSeconds;
std::string database;
std::string collection;
bool verbose;
int maxIterations;
MonitorConfig():
uri(),
setName("rs0"),
intervalSeconds(5),
database("test"),
collection("poco_monitor"),
verbose(false),
maxIterations(-1) // -1 means run forever
{
}
};
std::vector<Net::SocketAddress> parseHosts(const std::string& hostsStr)
{
std::vector<Net::SocketAddress> hosts;
std::size_t start = 0;
std::size_t end = hostsStr.find(',');
while (end != std::string::npos)
{
std::string hostStr = hostsStr.substr(start, end - start);
try
{
hosts.push_back(Net::SocketAddress(hostStr));
}
catch (const Poco::Exception& e)
{
throw Poco::InvalidArgumentException("Invalid host address '"s + hostStr + "': "s + e.displayText() );
}
start = end + 1;
end = hostsStr.find(',', start);
}
std::string lastHost = hostsStr.substr(start);
try
{
hosts.push_back(Net::SocketAddress(lastHost));
}
catch (const Poco::Exception& e)
{
throw Poco::InvalidArgumentException("Invalid host address '"s + lastHost + "': "s + e.displayText() );
}
return hosts;
}
void printUsage()
{
std::cout << "MongoDB Replica Set Monitor and Health Check Tool" << std::endl;
std::cout << std::endl;
std::cout << "Usage: ReplicaSetMonitor [options]" << std::endl;
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " -h, --help Show this help message" << std::endl;
std::cout << " -u, --uri URI MongoDB connection URI" << std::endl;
std::cout << " (e.g., mongodb://host1:27017,host2:27017/?replicaSet=rs0)" << std::endl;
std::cout << " -s, --set NAME Replica set name (default: rs0)" << std::endl;
std::cout << " -H, --hosts HOSTS Comma-separated host:port list" << std::endl;
std::cout << " (default: localhost:27017,localhost:27018,localhost:27019)" << std::endl;
std::cout << " -i, --interval SECONDS Check interval in seconds (default: 5)" << std::endl;
std::cout << " -d, --database NAME Database name (default: test)" << std::endl;
std::cout << " -c, --collection NAME Collection name (default: poco_monitor)" << std::endl;
std::cout << " -v, --verbose Verbose output" << std::endl;
std::cout << " -n, --iterations N Number of iterations (default: unlimited)" << std::endl;
std::cout << std::endl;
std::cout << "Note: --uri takes precedence over --set and --hosts options." << std::endl;
std::cout << std::endl;
std::cout << "Environment variables:" << std::endl;
std::cout << " MONGODB_URI MongoDB connection URI" << std::endl;
std::cout << " MONGODB_REPLICA_SET Replica set name" << std::endl;
std::cout << " MONGODB_HOSTS Comma-separated host:port list" << std::endl;
std::cout << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " # Using URI" << std::endl;
std::cout << " ReplicaSetMonitor -u 'mongodb://mongo1:27017,mongo2:27017/?replicaSet=rs0'" << std::endl;
std::cout << std::endl;
std::cout << " # Using separate options" << std::endl;
std::cout << " ReplicaSetMonitor -s rs0 -H mongo1:27017,mongo2:27017,mongo3:27017 -i 10" << std::endl;
std::cout << std::endl;
}
void printTopology(const TopologyDescription& topology, bool detailed = false)
{
std::cout << std::string(80, '=') << std::endl;
std::cout << "TOPOLOGY STATUS" << std::endl;
std::cout << std::string(80, '=') << std::endl;
std::cout << "Replica Set: " << (topology.setName().empty() ? "(not set)" : topology.setName()) << std::endl;
std::cout << "Type: " << TopologyDescription::typeToString(topology.type()) << std::endl;
std::cout << "Has Primary: " << (topology.hasPrimary() ? "Yes" : "No") << std::endl;
std::cout << std::endl;
std::vector<ServerDescription> servers = topology.servers();
std::cout << "Servers: " << servers.size() << std::endl;
std::cout << std::string(80, '-') << std::endl;
// Print header
std::cout << std::left
<< std::setw(30) << "Address"
<< std::setw(12) << "Type"
<< std::setw(10) << "RTT (ms)"
<< std::setw(20) << "Status"
<< std::endl;
std::cout << std::string(80, '-') << std::endl;
// Sort servers: primary first, then secondaries, then others
std::vector<ServerDescription> primaries, secondaries, others;
for (const auto& server : servers)
{
if (server.isPrimary())
primaries.push_back(server);
else if (server.isSecondary())
secondaries.push_back(server);
else
others.push_back(server);
}
auto printServer = [&](const ServerDescription& server) {
std::cout << std::left
<< std::setw(30) << server.address().toString()
<< std::setw(12) << ServerDescription::typeToString(server.type())
<< std::setw(10) << std::fixed << std::setprecision(2) << (server.roundTripTime() / 1000.0);
if (server.hasError())
{
std::cout << std::setw(20) << ("ERROR: " + server.error().substr(0, 30));
}
else
{
std::cout << std::setw(20) << "OK";
}
std::cout << std::endl;
if (detailed && !server.tags().empty())
{
std::cout << " Tags: " << server.tags().toString() << std::endl;
}
};
for (const auto& server : primaries) printServer(server);
for (const auto& server : secondaries) printServer(server);
for (const auto& server : others) printServer(server);
std::cout << std::string(80, '=') << std::endl;
std::cout << std::endl;
}
bool performWrite(ReplicaSetConnection& conn, const std::string& database,
const std::string& collection, int iteration,
Stopwatch& timer, bool verbose)
{
try
{
timer.restart();
Document::Ptr doc = new Document();
doc->add("iteration", iteration);
doc->add("timestamp", static_cast<Poco::Int64>(std::time(nullptr)));
doc->add("hostname", Poco::Environment::nodeName());
doc->add("message", "Health check from Poco::MongoDB monitor");
OpMsgMessage request(database, collection);
request.setCommandName(OpMsgMessage::CMD_INSERT);
request.documents().push_back(doc);
OpMsgMessage response;
conn.sendRequest(request, response);
timer.stop();
if (response.responseOk())
{
if (verbose)
{
std::cout << " ✓ Write successful to " << conn.address().toString()
<< " (" << timer.elapsed() / 1000 << " ms)" << std::endl;
}
return true;
}
else
{
std::cerr << " ✗ Write failed: " << response.body().toString() << std::endl;
return false;
}
}
catch (const Exception& e)
{
std::cerr << " ✗ Write exception: " << e.displayText() << std::endl;
return false;
}
}
bool performRead(ReplicaSetConnection& conn, const std::string& database,
const std::string& collection, Stopwatch& timer, bool verbose)
{
try
{
timer.restart();
OpMsgMessage request(database, collection);
request.setCommandName(OpMsgMessage::CMD_FIND);
request.body().add("limit", 1);
// Sort by timestamp descending to get latest document
Document::Ptr sort = new Document();
sort->add("timestamp", -1);
request.body().add("sort", sort);
OpMsgMessage response;
conn.sendRequest(request, response);
timer.stop();
if (response.responseOk())
{
if (verbose)
{
int docCount = response.documents().size();
std::cout << " ✓ Read successful from " << conn.address().toString()
<< " (" << timer.elapsed() / 1000 << " ms, "
<< docCount << " doc" << (docCount != 1 ? "s" : "") << ")" << std::endl;
}
return true;
}
else
{
std::cerr << " ✗ Read failed: " << response.body().toString() << std::endl;
return false;
}
}
catch (const Exception& e)
{
std::cerr << " ✗ Read exception: " << e.displayText() << std::endl;
return false;
}
}
void runMonitor(const MonitorConfig& config)
{
try
{
// Create replica set - use URI if provided, otherwise use Config
SharedPtr<ReplicaSet> rs;
if (!config.uri.empty())
{
// Use URI constructor
std::cout << "Connecting to replica set via URI" << std::endl;
std::cout << "URI: " << config.uri << std::endl;
std::cout << "Check interval: " << config.intervalSeconds << " seconds" << std::endl;
std::cout << std::endl;
rs = new ReplicaSet(config.uri);
}
else
{
// Use Config constructor
ReplicaSet::Config rsConfig;
rsConfig.setName = config.setName;
rsConfig.seeds = config.seeds;
rsConfig.readPreference = ReadPreference(ReadPreference::PrimaryPreferred);
rsConfig.enableMonitoring = true;
rsConfig.heartbeatFrequencySeconds = 5; // 5 seconds
std::cout << "Connecting to replica set: " << config.setName << std::endl;
std::cout << "Seed servers: ";
for (size_t i = 0; i < config.seeds.size(); ++i)
{
if (i > 0) std::cout << ", ";
std::cout << config.seeds[i].toString();
}
std::cout << std::endl;
std::cout << "Check interval: " << config.intervalSeconds << " seconds" << std::endl;
std::cout << std::endl;
rs = new ReplicaSet(rsConfig);
}
std::cout << "Replica set connected successfully!" << std::endl;
std::cout << "Background monitoring active." << std::endl;
std::cout << std::endl;
// Print initial topology
printTopology(rs->topology(), config.verbose);
// Create replica set connections for reads and writes
ReplicaSetConnection::Ptr writeConn = new ReplicaSetConnection(*rs, ReadPreference(ReadPreference::Primary));
ReplicaSetConnection::Ptr readConn = new ReplicaSetConnection(*rs, ReadPreference(ReadPreference::PrimaryPreferred));
// Monitoring loop
int iteration = 0;
Stopwatch writeTimer, readTimer;
int successfulWrites = 0;
int successfulReads = 0;
int failedWrites = 0;
int failedReads = 0;
while (config.maxIterations == -1 || iteration < config.maxIterations)
{
iteration++;
std::string timestamp = DateTimeFormatter::format(
DateTime(),
DateTimeFormat::ISO8601_FORMAT
);
std::cout << "[" << timestamp << "] Check #" << iteration << std::endl;
// Perform write to primary
std::cout << "Write (Primary): ";
if (performWrite(*writeConn, config.database, config.collection, iteration, writeTimer, config.verbose))
{
successfulWrites++;
if (!config.verbose)
{
std::cout << "✓ OK (" << writeTimer.elapsed() / 1000 << " ms)" << std::endl;
}
}
else
{
failedWrites++;
}
// Perform read
std::cout << "Read (PrimaryPreferred): ";
if (performRead(*readConn, config.database, config.collection, readTimer, config.verbose))
{
successfulReads++;
if (!config.verbose)
{
std::cout << "✓ OK (" << readTimer.elapsed() / 1000 << " ms)" << std::endl;
}
}
else
{
failedReads++;
}
// Print statistics
double writeSuccessRate = successfulWrites * 100.0 / (successfulWrites + failedWrites);
double readSuccessRate = successfulReads * 100.0 / (successfulReads + failedReads);
std::cout << "Statistics: "
<< "Writes: " << successfulWrites << "/" << (successfulWrites + failedWrites)
<< " (" << std::fixed << std::setprecision(1) << writeSuccessRate << "%), "
<< "Reads: " << successfulReads << "/" << (successfulReads + failedReads)
<< " (" << std::fixed << std::setprecision(1) << readSuccessRate << "%)"
<< std::endl;
// Print topology every 10 iterations or on first iteration
if (iteration % 10 == 0 || iteration == 1)
{
std::cout << std::endl;
printTopology(rs->topology(), config.verbose);
}
else
{
std::cout << std::string(80, '-') << std::endl;
}
// Sleep before next iteration
if (config.maxIterations == -1 || iteration < config.maxIterations)
{
Thread::sleep(config.intervalSeconds * 1000);
}
}
// Final summary
std::cout << std::endl;
std::cout << std::string(80, '=') << std::endl;
std::cout << "FINAL SUMMARY" << std::endl;
std::cout << std::string(80, '=') << std::endl;
std::cout << "Total iterations: " << iteration << std::endl;
std::cout << "Successful writes: " << successfulWrites << " / " << (successfulWrites + failedWrites) << std::endl;
std::cout << "Successful reads: " << successfulReads << " / " << (successfulReads + failedReads) << std::endl;
std::cout << std::string(80, '=') << std::endl;
}
catch (const Exception& e)
{
std::cerr << "ERROR: " << e.displayText() << std::endl;
throw;
}
}
int main(int argc, char** argv)
{
MonitorConfig config;
// Parse environment variables
const char* envUri = std::getenv("MONGODB_URI");
if (envUri)
{
config.uri = envUri;
}
const char* envSet = std::getenv("MONGODB_REPLICA_SET");
if (envSet)
{
config.setName = envSet;
}
const char* envHosts = std::getenv("MONGODB_HOSTS");
if (envHosts)
{
config.seeds = parseHosts(envHosts);
}
// Parse command line arguments
for (int i = 1; i < argc; ++i)
{
std::string arg(argv[i]);
if (arg == "-h" || arg == "--help")
{
printUsage();
return 0;
}
else if ((arg == "-u" || arg == "--uri") && i + 1 < argc)
{
config.uri = argv[++i];
}
else if ((arg == "-s" || arg == "--set") && i + 1 < argc)
{
config.setName = argv[++i];
}
else if ((arg == "-H" || arg == "--hosts") && i + 1 < argc)
{
config.seeds = parseHosts(argv[++i]);
}
else if ((arg == "-i" || arg == "--interval") && i + 1 < argc)
{
config.intervalSeconds = std::stoi(argv[++i]);
}
else if ((arg == "-d" || arg == "--database") && i + 1 < argc)
{
config.database = argv[++i];
}
else if ((arg == "-c" || arg == "--collection") && i + 1 < argc)
{
config.collection = argv[++i];
}
else if (arg == "-v" || arg == "--verbose")
{
config.verbose = true;
}
else if ((arg == "-n" || arg == "--iterations") && i + 1 < argc)
{
config.maxIterations = std::stoi(argv[++i]);
}
else
{
std::cerr << "Unknown option: " << arg << std::endl;
std::cout << std::endl;
printUsage();
return 1;
}
}
// Use defaults if not configured and no URI provided
if (config.uri.empty() && config.seeds.empty())
{
config.seeds = parseHosts("localhost:27017,localhost:27018,localhost:27019");
}
try
{
runMonitor(config);
return 0;
}
catch (const Poco::Exception& e)
{
std::cerr << "Error: " << e.displayText() << std::endl;
return 1;
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
catch (...)
{
std::cerr << "Unknown error occurred" << std::endl;
return 1;
}
}

View File

@@ -0,0 +1,184 @@
//
// URIExample.cpp
//
// Demonstrates MongoDB replica set URI parsing
//
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/ReplicaSet.h"
#include "Poco/MongoDB/Connection.h"
#include "Poco/MongoDB/Database.h"
#include "Poco/MongoDB/OpMsgMessage.h"
#include "Poco/Exception.h"
#include <iostream>
using namespace Poco::MongoDB;
using namespace Poco;
void printUsage()
{
std::cout << "MongoDB Replica Set URI Example" << std::endl;
std::cout << std::endl;
std::cout << "Usage: URIExample <uri>" << std::endl;
std::cout << std::endl;
std::cout << "URI Format:" << std::endl;
std::cout << " mongodb://host1:port1,host2:port2,.../?options" << std::endl;
std::cout << std::endl;
std::cout << "Supported Options:" << std::endl;
std::cout << " replicaSet=name Replica set name" << std::endl;
std::cout << " readPreference=mode primary|primaryPreferred|secondary|secondaryPreferred|nearest" << std::endl;
std::cout << " connectTimeoutMS=ms Connection timeout in milliseconds" << std::endl;
std::cout << " socketTimeoutMS=ms Socket timeout in milliseconds" << std::endl;
std::cout << " heartbeatFrequencyMS=ms Heartbeat frequency in milliseconds" << std::endl;
std::cout << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " URIExample 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0'" << std::endl;
std::cout << " URIExample 'mongodb://mongo1:27017,mongo2:27017/?replicaSet=rs0&readPreference=primaryPreferred'" << std::endl;
std::cout << " URIExample 'mongodb://host1:27017,host2:27017/?replicaSet=rs0&heartbeatFrequencyMS=5000'" << std::endl;
std::cout << std::endl;
}
int main(int argc, char** argv)
{
if (argc < 2)
{
printUsage();
return 1;
}
std::string uri = argv[1];
std::cout << "Parsing MongoDB Replica Set URI" << std::endl;
std::cout << std::string(80, '=') << std::endl;
std::cout << "URI: " << uri << std::endl;
std::cout << std::endl;
try
{
// Create replica set from URI
ReplicaSet rs(uri);
std::cout << "✓ URI parsed successfully!" << std::endl;
std::cout << std::endl;
// Display configuration
std::cout << "Configuration:" << std::endl;
std::cout << std::string(80, '-') << std::endl;
std::cout << "Replica Set Name: "
<< (rs.setName().empty() ? "(auto-discover)" : rs.setName())
<< std::endl;
std::cout << "Read Preference: " << rs.readPreference().toString() << std::endl;
std::cout << "Seed Servers: ";
TopologyDescription topology = rs.topology();
std::vector<ServerDescription> servers = topology.servers();
for (size_t i = 0; i < servers.size(); ++i)
{
if (i > 0) std::cout << ", ";
std::cout << servers[i].address().toString();
}
std::cout << std::endl;
std::cout << "Monitoring: " << (rs.hasPrimary() ? "Active" : "Starting...") << std::endl;
std::cout << std::endl;
// Try to connect to the replica set
std::cout << "Connecting to replica set..." << std::endl;
Connection::Ptr conn = rs.getPrimaryConnection();
if (conn.isNull())
{
std::cerr << "✗ Failed: No primary server available" << std::endl;
return 1;
}
std::cout << "✓ Connected to primary: " << conn->address().toString() << std::endl;
std::cout << std::endl;
// Query server information
Database db("admin");
Document::Ptr buildInfo = db.queryBuildInfo(*conn);
std::cout << "Server Information:" << std::endl;
std::cout << std::string(80, '-') << std::endl;
std::cout << "MongoDB Version: " << buildInfo->get<std::string>("version") << std::endl;
if (buildInfo->exists("gitVersion"))
{
std::cout << "Git Version: " << buildInfo->get<std::string>("gitVersion") << std::endl;
}
std::cout << std::endl;
// Display full topology
std::cout << "Replica Set Topology:" << std::endl;
std::cout << std::string(80, '-') << std::endl;
std::cout << "Set Name: " << topology.setName() << std::endl;
std::cout << "Has Primary: " << (topology.hasPrimary() ? "Yes" : "No") << std::endl;
std::cout << "Servers: " << topology.serverCount() << std::endl;
std::cout << std::endl;
for (const auto& server : servers)
{
std::cout << " " << server.address().toString();
if (server.isPrimary())
{
std::cout << " [PRIMARY]";
}
else if (server.isSecondary())
{
std::cout << " [SECONDARY]";
}
else
{
std::cout << " [" << (server.hasError() ? "ERROR" : "UNKNOWN") << "]";
}
std::cout << " RTT: " << (server.roundTripTime() / 1000.0) << " ms";
if (server.hasError())
{
std::cout << " (" << server.error() << ")";
}
std::cout << std::endl;
}
std::cout << std::endl;
std::cout << "✓ Success!" << std::endl;
return 0;
}
catch (const Poco::SyntaxException& e)
{
std::cerr << "✗ URI Syntax Error: " << e.displayText() << std::endl;
std::cerr << std::endl;
std::cerr << "Please check your URI format." << std::endl;
return 1;
}
catch (const Poco::UnknownURISchemeException& e)
{
std::cerr << "✗ Invalid URI Scheme: " << e.displayText() << std::endl;
std::cerr << std::endl;
std::cerr << "URI must start with 'mongodb://'" << std::endl;
return 1;
}
catch (const Exception& e)
{
std::cerr << "✗ Error: " << e.displayText() << std::endl;
return 1;
}
}

View File

@@ -1,9 +1,19 @@
//
// main.cpp
// SQLToMongo.cpp
//
// This sample shows SQL to MongoDB Shell to C++ examples using OP_MSG wire protocol.
// This sample demonstrates basic MongoDB operations using the Poco::MongoDB library
// with the OP_MSG wire protocol. Each function shows how to translate common SQL
// operations to MongoDB commands.
//
// Copyright (c) 2013, Applied Informatics Software Engineering GmbH.
// The examples use a "players" collection to demonstrate:
// - INSERT operations (inserting documents)
// - SELECT operations (querying with filters, projections, sorting, limits)
// - UPDATE operations (modifying documents)
// - DELETE operations (removing documents)
// - Creating indexes
// - Distinct queries and aggregations
//
// Copyright (c) 2013-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -18,11 +28,13 @@
#include "Poco/MongoDB/Array.h"
// INSERT INTO players
// VALUES( "Messi", "Lionel", 1987)
// SQL: INSERT INTO players VALUES ("Valdes", "Victor", 1982), ...
// MongoDB: db.players.insertMany([{...}, {...}])
//
// This sample demonstrates inserting multiple documents at once.
void sample1(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 1 ***" << std::endl;
std::cout << "*** SAMPLE 1: INSERT multiple documents ***" << std::endl;
Poco::MongoDB::Database db("sample");
Poco::SharedPtr<Poco::MongoDB::OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -131,7 +143,7 @@ void sample1(Poco::MongoDB::Connection& connection)
doc->add("lastname", "Tello").add("firstname", "Cristian").add("birthyear", 1991);
docs.push_back(doc);
std::cout << request->documents().size() << std::endl;
std::cout << "Inserting " << request->documents().size() << " player documents..." << std::endl;
Poco::MongoDB::OpMsgMessage response;
connection.sendRequest(*request, response);
@@ -139,13 +151,21 @@ void sample1(Poco::MongoDB::Connection& connection)
{
std::cout << "Error: " << response.body().toString(2) << std::endl;
}
else
{
std::cout << "Successfully inserted " << request->documents().size() << " documents" << std::endl;
}
std::cout << std::endl;
}
// SELECT lastname, birthyear FROM players
// SQL: SELECT lastname, birthyear FROM players
// MongoDB: db.players.find({}, {lastname: 1, birthyear: 1})
//
// This sample demonstrates projection (selecting specific fields).
void sample2(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 2 ***" << std::endl;
std::cout << "*** SAMPLE 2: SELECT with projection (specific fields) ***" << std::endl;
Poco::MongoDB::OpMsgCursor cursor("sample", "players");
cursor.query().setCommandName(Poco::MongoDB::OpMsgMessage::CMD_FIND);
@@ -163,13 +183,17 @@ void sample2(Poco::MongoDB::Connection& connection)
}
response = cursor.next(connection);
}
std::cout << std::endl;
}
// SELECT * FROM players
// SQL: SELECT * FROM players
// MongoDB: db.players.find({})
//
// This sample demonstrates querying all fields from all documents.
void sample3(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 3 ***" << std::endl;
std::cout << "*** SAMPLE 3: SELECT * (all fields) ***" << std::endl;
Poco::MongoDB::OpMsgCursor cursor("sample", "players");
cursor.query().setCommandName(Poco::MongoDB::OpMsgMessage::CMD_FIND);
@@ -183,13 +207,17 @@ void sample3(Poco::MongoDB::Connection& connection)
}
response = cursor.next(connection);
}
std::cout << std::endl;
}
// SELECT * FROM players WHERE birthyear = 1978
// SQL: SELECT * FROM players WHERE birthyear = 1978
// MongoDB: db.players.find({birthyear: 1978})
//
// This sample demonstrates filtering with a WHERE clause (exact match).
void sample4(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 4 ***" << std::endl;
std::cout << "*** SAMPLE 4: SELECT with WHERE clause (filter) ***" << std::endl;
Poco::MongoDB::OpMsgCursor cursor("sample", "players");
cursor.query().setCommandName(Poco::MongoDB::OpMsgMessage::CMD_FIND);
@@ -204,13 +232,17 @@ void sample4(Poco::MongoDB::Connection& connection)
}
response = cursor.next(connection);
}
std::cout << std::endl;
}
// SELECT * FROM players WHERE birthyear = 1987 ORDER BY name
// SQL: SELECT * FROM players WHERE birthyear = 1987 ORDER BY lastname
// MongoDB: db.players.find({birthyear: 1987}).sort({lastname: 1})
//
// This sample demonstrates sorting results with ORDER BY.
void sample5(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 5 ***" << std::endl;
std::cout << "*** SAMPLE 5: SELECT with ORDER BY (sorting) ***" << std::endl;
Poco::MongoDB::OpMsgCursor cursor("sample", "players");
cursor.query().setCommandName(Poco::MongoDB::OpMsgMessage::CMD_FIND);
@@ -226,13 +258,17 @@ void sample5(Poco::MongoDB::Connection& connection)
}
response = cursor.next(connection);
}
std::cout << std::endl;
}
// SELECT * FROM players WHERE birthyear > 1969 and birthyear <= 1980
// SQL: SELECT * FROM players WHERE birthyear > 1969 AND birthyear <= 1980
// MongoDB: db.players.find({birthyear: {$gt: 1969, $lte: 1980}})
//
// This sample demonstrates range queries with comparison operators.
void sample6(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 6 ***" << std::endl;
std::cout << "*** SAMPLE 6: SELECT with range query ($gt, $lte) ***" << std::endl;
Poco::MongoDB::OpMsgCursor cursor("sample", "players");
cursor.query().setCommandName(Poco::MongoDB::OpMsgMessage::CMD_FIND);
@@ -250,28 +286,43 @@ void sample6(Poco::MongoDB::Connection& connection)
}
response = cursor.next(connection);
}
std::cout << std::endl;
}
// CREATE INDEX playername
// ON players(lastname)
// SQL: CREATE INDEX playername ON players(lastname)
// MongoDB: db.players.createIndex({lastname: 1})
//
// This sample demonstrates creating an index on a collection.
void sample7(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 7 ***" << std::endl;
std::cout << "*** SAMPLE 7: CREATE INDEX ***" << std::endl;
Poco::MongoDB::Database db("sample");
Poco::MongoDB::Document::Ptr keys = new Poco::MongoDB::Document();
keys->add("lastname", 1);
Poco::MongoDB::Document::Ptr resultDoc = db.ensureIndex(connection, "players", "lastname", keys);
std::cout << resultDoc->toString(2);
// Create index on lastname field (ascending: true)
Poco::MongoDB::Database::IndexedFields indexedFields;
indexedFields.push_back(std::make_tuple("lastname", true)); // true = ascending
Poco::MongoDB::Document::Ptr resultDoc = db.createIndex(
connection,
"players", // collection name
indexedFields, // fields to index
"lastname_idx" // index name
);
std::cout << "Index created: " << resultDoc->toString(2) << std::endl;
std::cout << std::endl;
}
// SELECT * FROM players LIMIT 10 SKIP 20
// SQL: SELECT * FROM players LIMIT 10 OFFSET 20
// MongoDB: db.players.find({}).skip(20).limit(10)
//
// This sample demonstrates pagination with LIMIT and SKIP.
void sample8(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 8 ***" << std::endl;
std::cout << "*** SAMPLE 8: SELECT with LIMIT and SKIP (pagination) ***" << std::endl;
Poco::MongoDB::OpMsgCursor cursor("sample", "players");
cursor.query().setCommandName(Poco::MongoDB::OpMsgMessage::CMD_FIND);
@@ -288,13 +339,17 @@ void sample8(Poco::MongoDB::Connection& connection)
}
response = cursor.next(connection);
}
std::cout << std::endl;
}
// SELECT * FROM players LIMIT 1
// SQL: SELECT * FROM players LIMIT 1
// MongoDB: db.players.findOne({})
//
// This sample demonstrates fetching a single document.
void sample9(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 9 ***" << std::endl;
std::cout << "*** SAMPLE 9: SELECT LIMIT 1 (single document) ***" << std::endl;
Poco::MongoDB::Database db("sample");
Poco::SharedPtr<Poco::MongoDB::OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -305,15 +360,20 @@ void sample9(Poco::MongoDB::Connection& connection)
connection.sendRequest(*request, response);
if (!response.documents().empty())
{
std::cout << "First document:" << std::endl;
std::cout << response.documents()[0]->toString(2) << std::endl;
}
std::cout << std::endl;
}
// SELECT DISTINCT birthyear FROM players WHERE birthyear > 1980
// SQL: SELECT DISTINCT birthyear FROM players WHERE birthyear > 1980
// MongoDB: db.players.distinct("birthyear", {birthyear: {$gt: 1980}})
//
// This sample demonstrates the distinct command with a filter.
void sample10(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 10 ***" << std::endl;
std::cout << "*** SAMPLE 10: SELECT DISTINCT with WHERE ***" << std::endl;
Poco::MongoDB::Database db("sample");
Poco::SharedPtr<Poco::MongoDB::OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -329,18 +389,25 @@ void sample10(Poco::MongoDB::Connection& connection)
if (response.responseOk())
{
Poco::MongoDB::Array::Ptr values = response.body().get<Poco::MongoDB::Array::Ptr>("values");
std::cout << "Distinct birthyears (> 1980): ";
for (std::size_t i = 0; i < values->size(); ++i)
{
std::cout << values->get<int>(i) << std::endl;
if (i > 0) std::cout << ", ";
std::cout << values->get<int>(i);
}
std::cout << std::endl;
}
std::cout << std::endl;
}
// SELECT COUNT(*) FROM players WHERE birthyear > 1980
// SQL: SELECT COUNT(*) FROM players WHERE birthyear > 1980
// MongoDB: db.players.countDocuments({birthyear: {$gt: 1980}})
//
// This sample demonstrates the count command with a filter.
void sample11(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 11 ***" << std::endl;
std::cout << "*** SAMPLE 11: SELECT COUNT with WHERE ***" << std::endl;
Poco::MongoDB::Database db("sample");
Poco::SharedPtr<Poco::MongoDB::OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -355,15 +422,19 @@ void sample11(Poco::MongoDB::Connection& connection)
if (response.responseOk())
{
std::cout << "Count: " << response.body().getInteger("n") << std::endl;
std::cout << "Count of players born after 1980: " << response.body().getInteger("n") << std::endl;
}
std::cout << std::endl;
}
// UPDATE players SET birthyear = birthyear + 1 WHERE firstname = 'Victor'
// SQL: UPDATE players SET birthyear = birthyear + 1 WHERE firstname = 'Victor'
// MongoDB: db.players.updateMany({firstname: "Victor"}, {$inc: {birthyear: 1}})
//
// This sample demonstrates the update command with the $inc operator.
void sample12(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 12 ***" << std::endl;
std::cout << "*** SAMPLE 12: UPDATE with increment operator ***" << std::endl;
Poco::MongoDB::Database db("sample");
Poco::SharedPtr<Poco::MongoDB::OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -377,14 +448,18 @@ void sample12(Poco::MongoDB::Connection& connection)
Poco::MongoDB::OpMsgMessage response;
connection.sendRequest(*request, response);
std::cout << "Response: " << response.body().toString(2) << std::endl;
std::cout << "Update response: " << response.body().toString(2) << std::endl;
std::cout << std::endl;
}
// DELETE players WHERE firstname = 'Victor'
// SQL: DELETE FROM players WHERE firstname = 'Victor'
// MongoDB: db.players.deleteMany({firstname: "Victor"})
//
// This sample demonstrates the delete command.
void sample13(Poco::MongoDB::Connection& connection)
{
std::cout << "*** SAMPLE 13 ***" << std::endl;
std::cout << "*** SAMPLE 13: DELETE ***" << std::endl;
Poco::MongoDB::Database db("sample");
Poco::SharedPtr<Poco::MongoDB::OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -398,33 +473,54 @@ void sample13(Poco::MongoDB::Connection& connection)
Poco::MongoDB::OpMsgMessage response;
connection.sendRequest(*request, response);
std::cout << "Response: " << response.body().toString(2) << std::endl;
std::cout << "Delete response: " << response.body().toString(2) << std::endl;
std::cout << std::endl;
}
int main(int argc, char** argv)
{
// Connect to MongoDB server
// For replica set connections, see the ReplicaSet samples
Poco::MongoDB::Connection connection("localhost", 27017);
try
{
sample1(connection);
sample2(connection);
sample3(connection);
sample4(connection);
sample5(connection);
sample6(connection);
sample7(connection);
sample8(connection);
sample9(connection);
sample10(connection);
sample11(connection);
sample12(connection);
sample13(connection);
std::cout << "==================================================" << std::endl;
std::cout << "Poco::MongoDB SQL to MongoDB Examples" << std::endl;
std::cout << "==================================================" << std::endl;
std::cout << std::endl;
std::cout << "This sample demonstrates how to translate SQL" << std::endl;
std::cout << "operations to MongoDB using the OP_MSG protocol." << std::endl;
std::cout << std::endl;
std::cout << "Connected to: localhost:27017" << std::endl;
std::cout << "Database: sample" << std::endl;
std::cout << "Collection: players" << std::endl;
std::cout << std::endl;
sample1(connection); // INSERT multiple documents
sample2(connection); // SELECT with projection (specific fields)
sample3(connection); // SELECT * (all fields)
sample4(connection); // SELECT with WHERE clause (filter)
sample5(connection); // SELECT with ORDER BY (sorting)
sample6(connection); // SELECT with range query ($gt, $lte)
sample7(connection); // CREATE INDEX
sample8(connection); // SELECT with LIMIT and SKIP (pagination)
sample9(connection); // SELECT LIMIT 1 (single document)
sample10(connection); // SELECT DISTINCT with WHERE
sample11(connection); // SELECT COUNT with WHERE
sample12(connection); // UPDATE with increment operator
sample13(connection); // DELETE
std::cout << std::endl;
std::cout << "==================================================" << std::endl;
std::cout << "All samples completed successfully!" << std::endl;
std::cout << "==================================================" << std::endl;
}
catch (Poco::Exception& exc)
{
std::cerr << exc.displayText() << std::endl;
std::cerr << "ERROR: " << exc.displayText() << std::endl;
return 1;
}
return 0;

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: Array
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -42,23 +42,24 @@ std::string Array::toString(int indent) const
{
std::ostringstream oss;
oss << "[";
oss << '[';
if (indent > 0) oss << std::endl;
// Use protected accessor instead of direct _elements access to maintain encapsulation
const ElementSet& elems = elements();
for (auto it = elems.begin(), total = elems.end(); it != total; ++it)
// Use protected accessor to get ordered names
const auto& names = orderedNames();
for (auto it = names.begin(), total = names.end(); it != total; ++it)
{
if (it != elems.begin())
if (it != names.begin())
{
oss << ",";
oss << ',';
if (indent > 0) oss << std::endl;
}
for (int i = 0; i < indent; ++i) oss << ' ';
oss << (*it)->toString(indent > 0 ? indent + 2 : 0);
Element::Ptr element = Document::get(*it);
oss << element->toString(indent > 0 ? indent + 2 : 0);
}
if (indent > 0)
@@ -68,7 +69,7 @@ std::string Array::toString(int indent) const
for (int i = 0; i < indent; ++i) oss << ' ';
}
oss << "]";
oss << ']';
return oss.str();
}

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: Binary
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -18,6 +18,8 @@
#include "Poco/MemoryStream.h"
#include <sstream>
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
@@ -42,11 +44,17 @@ Binary::Binary(const UUID& uuid):
_subtype(SUBTYPE_UUID)
{
unsigned char szUUID[16];
uuid.copyTo((char*) szUUID);
uuid.copyTo(reinterpret_cast<char*>(szUUID));
_buffer.assign(szUUID, 16);
}
Binary::Binary(const char* data, unsigned char subtype):
_buffer(reinterpret_cast<const unsigned char*>(data), std::strlen(data)),
_subtype(subtype)
{
}
Binary::Binary(const std::string& data, unsigned char subtype):
_buffer(reinterpret_cast<const unsigned char*>(data.data()), data.size()),
@@ -74,7 +82,7 @@ std::string Binary::toString(int indent) const
{
try
{
return "UUID(\"" + uuid().toString() + "\")";
return "UUID(\""s + uuid().toString() + "\")"s;
}
catch (...)
{
@@ -85,7 +93,7 @@ std::string Binary::toString(int indent) const
// Default: Base64 encode the binary data
std::ostringstream oss;
Base64Encoder encoder(oss);
MemoryInputStream mis((const char*) _buffer.begin(), _buffer.size());
MemoryInputStream mis(reinterpret_cast<const char*>(_buffer.begin()), _buffer.size());
StreamCopier::copyStream(mis, encoder);
encoder.close();
return oss.str();
@@ -97,7 +105,7 @@ UUID Binary::uuid() const
if (_subtype == SUBTYPE_UUID && _buffer.size() == 16)
{
UUID uuid;
uuid.copyFrom((const char*) _buffer.begin());
uuid.copyFrom(reinterpret_cast<const char*>(_buffer.begin()));
return uuid;
}
throw BadCastException("Invalid subtype");

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: Connection
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -20,19 +20,17 @@
#include "Poco/NumberParser.h"
#include "Poco/Exception.h"
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
Connection::SocketFactory::SocketFactory()
{
}
Connection::SocketFactory::SocketFactory() = default;
Connection::SocketFactory::~SocketFactory()
{
}
Connection::SocketFactory::~SocketFactory() = default;
Poco::Net::StreamSocket Connection::SocketFactory::createSocket(const std::string& host, int port, Poco::Timespan connectTimeout, bool secure)
@@ -51,11 +49,7 @@ Poco::Net::StreamSocket Connection::SocketFactory::createSocket(const std::strin
}
Connection::Connection():
_address(),
_socket()
{
}
Connection::Connection() = default;
Connection::Connection(const std::string& hostAndPort):
@@ -165,19 +159,19 @@ void Connection::connect(const std::string& uri, SocketFactory& socketFactory)
Poco::URI::QueryParameters params = theURI.getQueryParameters();
for (Poco::URI::QueryParameters::const_iterator it = params.begin(); it != params.end(); ++it)
{
if (it->first == "ssl")
if (it->first == "ssl"s)
{
ssl = (it->second == "true");
ssl = (it->second == "true"s);
}
else if (it->first == "connectTimeoutMS")
else if (it->first == "connectTimeoutMS"s)
{
connectTimeout = static_cast<Poco::Timespan::TimeDiff>(1000)*Poco::NumberParser::parse(it->second);
}
else if (it->first == "socketTimeoutMS")
else if (it->first == "socketTimeoutMS"s)
{
socketTimeout = static_cast<Poco::Timespan::TimeDiff>(1000)*Poco::NumberParser::parse(it->second);
}
else if (it->first == "authMechanism")
else if (it->first == "authMechanism"s)
{
authMechanism = it->second;
}

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: Database
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -31,6 +31,8 @@
#include <sstream>
#include <map>
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
@@ -337,26 +339,26 @@ Poco::MongoDB::Document::Ptr Database::createIndex(
MongoDB::Document::Ptr index = new MongoDB::Document();
if (!indexName.empty())
{
index->add("name", indexName);
index->add("name"s, indexName);
}
index->add("key", keys);
index->add("ns", _dbname + "." + collection);
index->add("name", indexName);
index->add("key"s, keys);
index->add("ns"s, _dbname + '.' + collection);
index->add("name"s, indexName);
if (options & INDEX_UNIQUE) {
index->add("unique", true);
index->add("unique"s, true);
}
if (options & INDEX_BACKGROUND) {
index->add("background", true);
index->add("background"s, true);
}
if (options & INDEX_SPARSE) {
index->add("sparse", true);
index->add("sparse"s, true);
}
if (expirationSeconds > 0) {
index->add("expireAfterSeconds", static_cast<Poco::Int32>(expirationSeconds));
index->add("expireAfterSeconds"s, static_cast<Poco::Int32>(expirationSeconds));
}
if (version > 0) {
index->add("version", static_cast<Poco::Int32>(version));
index->add("version"s, static_cast<Poco::Int32>(version));
}
MongoDB::Array::Ptr indexes = new MongoDB::Array();

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: Document
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -18,6 +18,7 @@
#include "Poco/MongoDB/Array.h"
#include "Poco/MongoDB/RegularExpression.h"
#include "Poco/MongoDB/JavaScriptCode.h"
#include <algorithm>
#include <sstream>
@@ -142,7 +143,7 @@ void Document::read(BinaryReader& reader)
default:
{
std::stringstream ss;
ss << "Element " << name << " contains an unsupported type 0x" << std::hex << (int) type;
ss << "Element " << name << " contains an unsupported type 0x" << std::hex << static_cast<int>(type);
throw Poco::NotImplementedException(ss.str());
}
//TODO: x0F -> JavaScript code with scope
@@ -151,7 +152,7 @@ void Document::read(BinaryReader& reader)
}
element->read(reader);
_elements.push_back(element);
_elementNames.push_back(element->name());
_elementMap[element->name()] = element; // Populate hash map for O(1) lookups
reader >> type;
@@ -169,9 +170,9 @@ std::string Document::toString(int indent) const
if (indent > 0) oss << std::endl;
for (auto it = _elements.begin(), total = _elements.end(); it != total; ++it)
for (auto it = _elementNames.begin(), total = _elementNames.end(); it != total; ++it)
{
if (it != _elements.begin())
if (it != _elementNames.begin())
{
oss << ',';
if (indent > 0) oss << std::endl;
@@ -183,10 +184,11 @@ std::string Document::toString(int indent) const
oss << indentStr;
}
oss << '"' << (*it)->name() << '"';
const auto& element = _elementMap.at(*it);
oss << '"' << *it << '"';
oss << (indent > 0 ? " : " : ":");
oss << (*it)->toString(indent > 0 ? indent + 2 : 0);
oss << element->toString(indent > 0 ? indent + 2 : 0);
}
if (indent > 0)
@@ -204,9 +206,9 @@ std::string Document::toString(int indent) const
}
void Document::write(BinaryWriter& writer)
void Document::write(BinaryWriter& writer) const
{
if (_elements.empty())
if (_elementNames.empty())
{
writer << 5;
}
@@ -214,11 +216,11 @@ void Document::write(BinaryWriter& writer)
{
std::stringstream sstream;
Poco::BinaryWriter tempWriter(sstream, BinaryWriter::LITTLE_ENDIAN_BYTE_ORDER);
for (ElementSet::iterator it = _elements.begin(); it != _elements.end(); ++it)
for (const auto& name : _elementNames)
{
tempWriter << static_cast<unsigned char>((*it)->type());
BSONWriter(tempWriter).writeCString((*it)->name());
Element::Ptr element = *it;
const auto& element = _elementMap.at(name);
tempWriter << static_cast<unsigned char>(element->type());
BSONWriter(tempWriter).writeCString(element->name());
element->write(tempWriter);
}
tempWriter.flush();
@@ -231,4 +233,48 @@ void Document::write(BinaryWriter& writer)
}
void Document::reserve(std::size_t size)
{
_elementNames.reserve(size);
_elementMap.reserve(size);
}
Document& Document::addElement(Element::Ptr element)
{
// Check if element with this name already exists
auto it = _elementMap.find(element->name());
if (it == _elementMap.end())
{
// New element - add name to vector and element to map
_elementNames.push_back(element->name());
_elementMap[element->name()] = element;
}
else
{
// Element exists - only update the map (replace existing)
_elementMap[element->name()] = element;
}
return *this;
}
bool Document::remove(const std::string& name)
{
// Remove from hash map first (O(1))
auto mapIt = _elementMap.find(name);
if (mapIt == _elementMap.end())
return false;
_elementMap.erase(mapIt);
// Then remove from vector (O(n) but unavoidable for order preservation)
auto it = std::find(_elementNames.begin(), _elementNames.end(), name);
if (it != _elementNames.end())
_elementNames.erase(it);
return true;
}
} } // namespace Poco::MongoDB

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: Element
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: JavaScriptCode
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: Message
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: MessageHeader
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: ObjectId
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -14,7 +14,9 @@
#include "Poco/MongoDB/ObjectId.h"
#include "Poco/Format.h"
#include "Poco/Exception.h"
#include <cstring>
#include <cctype>
namespace Poco {
@@ -29,11 +31,18 @@ ObjectId::ObjectId()
ObjectId::ObjectId(const std::string& id)
{
poco_assert_dbg(id.size() == 24);
if (id.size() != 24)
throw Poco::InvalidArgumentException("ObjectId string must be exactly 24 hexadecimal characters");
const char* p = id.c_str();
for (std::size_t i = 0; i < 12; ++i)
{
// Validate that both characters are valid hex digits
if (!std::isxdigit(static_cast<unsigned char>(p[0])) ||
!std::isxdigit(static_cast<unsigned char>(p[1])))
{
throw Poco::InvalidArgumentException("ObjectId string contains invalid hexadecimal characters");
}
_id[i] = fromHex(p);
p += 2;
}

View File

@@ -13,6 +13,7 @@
#include "Poco/MongoDB/OpMsgCursor.h"
#include "Poco/MongoDB/ReplicaSetConnection.h"
#include "Poco/MongoDB/Array.h"
//
@@ -39,6 +40,8 @@
#define _MONGODB_EXHAUST_ALLOWED_WORKS false
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
@@ -104,7 +107,8 @@ bool OpMsgCursor::isActive() const noexcept
}
OpMsgMessage& OpMsgCursor::next(Connection& connection)
template<typename ConnType>
OpMsgMessage& OpMsgCursor::nextImpl(ConnType& connection)
{
if (_cursorID == 0)
{
@@ -153,7 +157,7 @@ OpMsgMessage& OpMsgCursor::next(Connection& connection)
connection.readResponse(_response);
}
else
#endif
#endif
{
_response.clear();
_query.setCursor(_cursorID, _batchSize);
@@ -168,7 +172,20 @@ OpMsgMessage& OpMsgCursor::next(Connection& connection)
}
void OpMsgCursor::kill(Connection& connection)
OpMsgMessage& OpMsgCursor::next(Connection& connection)
{
return nextImpl(connection);
}
OpMsgMessage& OpMsgCursor::next(ReplicaSetConnection& connection)
{
return nextImpl(connection);
}
template<typename ConnType>
void OpMsgCursor::killImpl(ConnType& connection)
{
_response.clear();
if (_cursorID != 0)
@@ -184,7 +201,7 @@ void OpMsgCursor::kill(Connection& connection)
const auto killed = _response.body().get<MongoDB::Array::Ptr>(keyCursorsKilled, nullptr);
if (!killed || killed->size() != 1 || killed->get<Poco::Int64>(0, -1) != _cursorID)
{
throw Poco::ProtocolException("Cursor not killed as expected: " + std::to_string(_cursorID));
throw Poco::ProtocolException("Cursor not killed as expected: "s + std::to_string(_cursorID));
}
_cursorID = 0;
@@ -194,6 +211,18 @@ void OpMsgCursor::kill(Connection& connection)
}
void OpMsgCursor::kill(Connection& connection)
{
killImpl(connection);
}
void OpMsgCursor::kill(ReplicaSetConnection& connection)
{
killImpl(connection);
}
Poco::Int64 cursorIdFromResponse(const MongoDB::Document& doc)
{
Poco::Int64 id {0};
@@ -206,4 +235,11 @@ Poco::Int64 cursorIdFromResponse(const MongoDB::Document& doc)
}
// Explicit template instantiation
template OpMsgMessage& OpMsgCursor::nextImpl<Connection>(Connection& connection);
template OpMsgMessage& OpMsgCursor::nextImpl<ReplicaSetConnection>(ReplicaSetConnection& connection);
template void OpMsgCursor::killImpl<Connection>(Connection& connection);
template void OpMsgCursor::killImpl<ReplicaSetConnection>(ReplicaSetConnection& connection);
} } // Namespace Poco::MongoDB

View File

@@ -14,8 +14,7 @@
#include "Poco/MongoDB/OpMsgMessage.h"
#include "Poco/MongoDB/MessageHeader.h"
#include "Poco/MongoDB/Array.h"
#include "Poco/StreamCopier.h"
#include "Poco/Logger.h"
#include <map>
#define POCO_MONGODB_DUMP false
@@ -297,8 +296,8 @@ void OpMsgMessage::read(std::istream& istr)
#if POCO_MONGODB_DUMP
std::cout
<< "Message hdr: " << _header.getMessageLength() << " " << remainingSize << " "
<< _header.opCode() << " " << _header.getRequestID() << " " << _header.responseTo()
<< "Message hdr: " << _header.getMessageLength() << ' ' << remainingSize << ' '
<< _header.opCode() << ' ' << _header.getRequestID() << ' ' << _header.responseTo()
<< std::endl;
#endif
@@ -358,7 +357,7 @@ void OpMsgMessage::read(std::istream& istr)
while (msgss.tellg() < endOfSection)
{
#if POCO_MONGODB_DUMP
std::cout << "section doc: " << msgss.tellg() << " " << endOfSection << std::endl;
std::cout << "section doc: " << msgss.tellg() << ' ' << endOfSection << std::endl;
#endif
Document::Ptr doc = new Document();
doc->read(reader);

View File

@@ -0,0 +1,328 @@
//
// ReadPreference.cpp
//
// Library: MongoDB
// Package: MongoDB
// Module: ReadPreference
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/MongoDB/TopologyDescription.h"
#include "Poco/Format.h"
#include <limits>
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
const Poco::Int64 ReadPreference::NO_MAX_STALENESS;
ReadPreference::ReadPreference(Mode mode):
_mode(mode)
{
}
ReadPreference::ReadPreference(Mode mode, const Document& tags, Poco::Int64 maxStalenessSeconds):
_mode(mode),
_tags(tags),
_maxStalenessSeconds(maxStalenessSeconds)
{
}
ReadPreference::ReadPreference(const ReadPreference& other) = default;
ReadPreference::ReadPreference(ReadPreference&& other) noexcept = default;
ReadPreference::~ReadPreference() = default;
ReadPreference& ReadPreference::operator=(const ReadPreference& other) = default;
ReadPreference& ReadPreference::operator=(ReadPreference&& other) noexcept = default;
std::vector<ServerDescription> ReadPreference::selectServers(const TopologyDescription& topology) const
{
std::vector<ServerDescription> eligible;
switch (_mode)
{
case Primary:
{
ServerDescription primary = topology.findPrimary();
if (primary.type() != ServerDescription::Unknown)
{
eligible.push_back(primary);
}
}
break;
case PrimaryPreferred:
{
ServerDescription primary = topology.findPrimary();
if (primary.type() != ServerDescription::Unknown)
{
eligible.push_back(primary);
}
else
{
// Fallback to secondaries
std::vector<ServerDescription> secondaries = topology.findSecondaries();
eligible = filterByTags(secondaries);
if (_maxStalenessSeconds != NO_MAX_STALENESS)
{
eligible = filterByMaxStaleness(eligible, primary);
}
}
}
break;
case Secondary:
{
std::vector<ServerDescription> secondaries = topology.findSecondaries();
eligible = filterByTags(secondaries);
if (_maxStalenessSeconds != NO_MAX_STALENESS)
{
ServerDescription primary = topology.findPrimary();
eligible = filterByMaxStaleness(eligible, primary);
}
}
break;
case SecondaryPreferred:
{
std::vector<ServerDescription> secondaries = topology.findSecondaries();
eligible = filterByTags(secondaries);
if (_maxStalenessSeconds != NO_MAX_STALENESS)
{
ServerDescription primary = topology.findPrimary();
eligible = filterByMaxStaleness(eligible, primary);
}
if (eligible.empty())
{
// Fallback to primary
ServerDescription primary = topology.findPrimary();
if (primary.type() != ServerDescription::Unknown)
{
eligible.push_back(primary);
}
}
}
break;
case Nearest:
{
// Combine primary and secondaries
std::vector<ServerDescription> all = topology.servers();
std::vector<ServerDescription> candidates;
for (const auto& server : all)
{
if (server.isPrimary() || server.isSecondary())
{
candidates.push_back(server);
}
}
eligible = filterByTags(candidates);
if (_maxStalenessSeconds != NO_MAX_STALENESS)
{
ServerDescription primary = topology.findPrimary();
eligible = filterByMaxStaleness(eligible, primary);
}
// Select by nearest (lowest RTT)
eligible = selectByNearest(eligible);
}
break;
}
return eligible;
}
std::string ReadPreference::toString() const
{
std::string result;
switch (_mode)
{
case Primary:
result = "primary"s;
break;
case PrimaryPreferred:
result = "primaryPreferred"s;
break;
case Secondary:
result = "secondary"s;
break;
case SecondaryPreferred:
result = "secondaryPreferred"s;
break;
case Nearest:
result = "nearest"s;
break;
}
if (!_tags.empty())
{
result += " (tags: "s + _tags.toString() + ')';
}
if (_maxStalenessSeconds != NO_MAX_STALENESS)
{
result += Poco::format(" (maxStaleness: %?d seconds)"s, _maxStalenessSeconds);
}
return result;
}
bool ReadPreference::matchesTags(const ServerDescription& server) const
{
if (_tags.empty())
{
return true; // No tag constraints
}
const Document& serverTags = server.tags();
// Get tag names and check if all required tags match
std::vector<std::string> tagNames;
_tags.elementNames(tagNames);
for (const auto& key : tagNames)
{
if (!serverTags.exists(key))
{
return false;
}
// Get both values as strings for comparison
const auto& requiredValue = _tags.get<std::string>(key);
const auto& serverValue = serverTags.get<std::string>(key);
if (requiredValue != serverValue)
{
return false;
}
}
return true;
}
std::vector<ServerDescription> ReadPreference::filterByTags(const std::vector<ServerDescription>& servers) const
{
if (_tags.empty())
{
return servers; // No filtering needed
}
std::vector<ServerDescription> result;
result.reserve(servers.size());
for (const auto& server : servers)
{
if (matchesTags(server))
{
result.push_back(server);
}
}
return result;
}
std::vector<ServerDescription> ReadPreference::filterByMaxStaleness(
const std::vector<ServerDescription>& servers,
const ServerDescription& primary) const
{
if (_maxStalenessSeconds == NO_MAX_STALENESS)
{
return servers; // No filtering needed
}
// Note: Proper staleness calculation requires lastWriteDate from server responses
// For now, we implement a simplified version based on lastUpdateTime
// A full implementation would compare lastWriteDate timestamps
std::vector<ServerDescription> result;
result.reserve(servers.size());
const Poco::Int64 maxStalenessMs = _maxStalenessSeconds * 1000000; // Convert to microseconds
for (const auto& server : servers)
{
// Calculate staleness as the difference between primary and secondary update times
// This is a simplified approximation
if (primary.type() != ServerDescription::Unknown)
{
Poco::Int64 staleness = std::abs(
primary.lastUpdateTime().epochMicroseconds() -
server.lastUpdateTime().epochMicroseconds()
);
if (staleness <= maxStalenessMs)
{
result.push_back(server);
}
}
else
{
// No primary available, include all secondaries
result.push_back(server);
}
}
return result;
}
std::vector<ServerDescription> ReadPreference::selectByNearest(const std::vector<ServerDescription>& servers) const
{
if (servers.empty())
{
return servers;
}
// Find minimum RTT
Poco::Int64 minRTT = std::numeric_limits<Poco::Int64>::max();
for (const auto& server : servers)
{
if (server.roundTripTime() < minRTT)
{
minRTT = server.roundTripTime();
}
}
// Select servers within 15ms of minimum RTT (MongoDB spec)
const Poco::Int64 localThresholdMs = 15000; // 15ms in microseconds
std::vector<ServerDescription> result;
result.reserve(servers.size());
for (const auto& server : servers)
{
if (server.roundTripTime() <= minRTT + localThresholdMs)
{
result.emplace_back(server);
}
}
return result;
}
} } // namespace Poco::MongoDB

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: RegularExpression
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -13,7 +13,6 @@
#include "Poco/MongoDB/RegularExpression.h"
#include <sstream>
namespace Poco {

View File

@@ -5,7 +5,7 @@
// Package: MongoDB
// Module: ReplicaSet
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
@@ -13,74 +13,602 @@
#include "Poco/MongoDB/ReplicaSet.h"
#include "Poco/MongoDB/ReplicaSetURI.h"
#include "Poco/MongoDB/OpMsgMessage.h"
#include "Poco/MongoDB/TopologyChangeNotification.h"
#include "Poco/Exception.h"
#include "Poco/Random.h"
#include "Poco/NotificationCenter.h"
#include <chrono>
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
ReplicaSet::ReplicaSet(const std::vector<Net::SocketAddress> &addresses):
_addresses(addresses)
//
// ReplicaSet
//
ReplicaSet::ReplicaSet(const Config& config):
_config(config),
_topology(config.setName)
{
if (_config.seeds.empty())
{
throw Poco::InvalidArgumentException("Replica set configuration must have at least one seed server");
}
// Add seed servers to topology
for (const auto& seed : _config.seeds)
{
_topology.addServer(seed);
}
// Perform initial discovery
updateTopologyFromAllServers();
// Start monitoring if enabled
if (_config.enableMonitoring)
{
startMonitoring();
}
}
ReplicaSet::ReplicaSet(const std::vector<Net::SocketAddress>& seeds):
ReplicaSet(Config())
{
_config.seeds = seeds;
if (_config.seeds.empty())
{
throw Poco::InvalidArgumentException("Replica set must have at least one seed server");
}
// Add seed servers to topology
for (const auto& seed : _config.seeds)
{
_topology.addServer(seed);
}
// Perform initial discovery
updateTopologyFromAllServers();
// Start monitoring if enabled
if (_config.enableMonitoring)
{
startMonitoring();
}
}
ReplicaSet::ReplicaSet(const std::string& uri)
{
// Parse URI using ReplicaSetURI
ReplicaSetURI parsedURI(uri);
// Extract configuration from parsed URI
// Resolve server strings to SocketAddress objects here
_config.seeds.clear();
for (const auto& serverStr : parsedURI.servers())
{
try
{
_config.seeds.emplace_back(serverStr);
}
catch (const std::exception& e)
{
// Skip servers that cannot be resolved via DNS
// Note: URI parsing already succeeded - ReplicaSetURI stores servers as strings.
// Servers that fail DNS resolution are not added to the seed list.
// Only resolvable servers will be used for topology discovery.
}
}
_config.setName = parsedURI.replicaSet();
_config.readPreference = parsedURI.readPreference();
_config.connectTimeoutSeconds = (parsedURI.connectTimeoutMS() + 999) / 1000; // Convert ms to seconds (round up)
_config.socketTimeoutSeconds = (parsedURI.socketTimeoutMS() + 999) / 1000; // Convert ms to seconds (round up)
_config.heartbeatFrequencySeconds = (parsedURI.heartbeatFrequencyMS() + 999) / 1000; // Convert ms to seconds (round up)
_config.serverReconnectRetries = parsedURI.reconnectRetries();
_config.serverReconnectDelaySeconds = parsedURI.reconnectDelay();
if (_config.seeds.empty())
{
throw Poco::InvalidArgumentException("Replica set URI must contain at least one host");
}
// Update topology with set name from config
_topology.setName(_config.setName);
// Add seed servers to topology
for (const auto& seed : _config.seeds)
{
_topology.addServer(seed);
}
// Perform initial discovery
updateTopologyFromAllServers();
// Start monitoring if enabled
if (_config.enableMonitoring)
{
startMonitoring();
}
}
ReplicaSet::ReplicaSet(const ReplicaSetURI& uri)
{
// Extract configuration from ReplicaSetURI object
// Resolve server strings to SocketAddress objects here
_config.seeds.clear();
for (const auto& serverStr : uri.servers())
{
try
{
_config.seeds.emplace_back(serverStr);
}
catch (const std::exception& e)
{
// Skip servers that cannot be resolved via DNS
// Note: URI parsing already succeeded - ReplicaSetURI stores servers as strings.
// Servers that fail DNS resolution are not added to the seed list.
// Only resolvable servers will be used for topology discovery.
}
}
_config.setName = uri.replicaSet();
_config.readPreference = uri.readPreference();
_config.connectTimeoutSeconds = (uri.connectTimeoutMS() + 999) / 1000; // Convert ms to seconds (round up)
_config.socketTimeoutSeconds = (uri.socketTimeoutMS() + 999) / 1000; // Convert ms to seconds (round up)
_config.heartbeatFrequencySeconds = (uri.heartbeatFrequencyMS() + 999) / 1000; // Convert ms to seconds (round up)
_config.serverReconnectRetries = uri.reconnectRetries();
_config.serverReconnectDelaySeconds = uri.reconnectDelay();
if (_config.seeds.empty())
{
throw Poco::InvalidArgumentException("Replica set URI must contain at least one host");
}
// Update topology with set name from config
_topology.setName(_config.setName);
// Add seed servers to topology
for (const auto& seed : _config.seeds)
{
_topology.addServer(seed);
}
// Perform initial discovery
updateTopologyFromAllServers();
// Start monitoring if enabled
if (_config.enableMonitoring)
{
startMonitoring();
}
}
ReplicaSet::~ReplicaSet()
{
stopMonitoring();
}
Connection::Ptr ReplicaSet::findMaster()
Connection::Ptr ReplicaSet::getConnection(const ReadPreference& readPref)
{
Connection::Ptr master;
return selectServer(readPref);
}
for (std::vector<Net::SocketAddress>::iterator it = _addresses.begin(); it != _addresses.end(); ++it)
Connection::Ptr ReplicaSet::getPrimaryConnection()
{
return selectServer(ReadPreference(ReadPreference::Primary));
}
Connection::Ptr ReplicaSet::getSecondaryConnection()
{
return selectServer(ReadPreference(ReadPreference::Secondary));
}
Connection::Ptr ReplicaSet::waitForServerAvailability(const ReadPreference& readPref)
{
// Timeopoint when this thread started to wait for the server to become available
const auto waitStartTime = std::chrono::steady_clock::now();
// Coordinate waiting between threads to avoid redundant refresh attempts
std::lock_guard<std::mutex> lock(_serverAvailabilityRetryMutex);
if (waitStartTime <= _topologyRefreshTime)
{
master = isMaster(*it);
if (!master.isNull())
// Another thread recently refreshed, try getting connection without waiting
return getConnection(readPref);
}
// Retry up to serverReconnectRetries times to wait for servers to become available
for (std::size_t i = 0; i < _config.serverReconnectRetries; ++i)
{
// Sleep before refreshing topology
std::this_thread::sleep_for(std::chrono::seconds(_config.serverReconnectDelaySeconds));
// Refresh topology to discover available servers
refreshTopology();
// Try to get a connection after refresh
Connection::Ptr conn = getConnection(readPref);
if (!conn.isNull())
{
break;
return conn;
}
}
return master;
// All retries exhausted, no servers available
return nullptr;
}
Connection::Ptr ReplicaSet::isMaster(const Net::SocketAddress& address)
TopologyDescription ReplicaSet::topology() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _topology;
}
ReplicaSet::Config ReplicaSet::configuration() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _config;
}
void ReplicaSet::refreshTopology()
{
// Simply delegate to updateTopologyFromAllServers which handles
// change detection and notification sending
updateTopologyFromAllServers();
}
void ReplicaSet::startMonitoring()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_monitoringActive.load())
{
return; // Already running
}
_stopMonitoring.store(false);
_monitoringActive.store(true);
_monitorThread = std::thread([this]() {
monitor();
});
}
void ReplicaSet::stopMonitoring()
{
_stopMonitoring.store(true);
if (_monitorThread.joinable())
{
_monitorThread.join();
}
_monitoringActive.store(false);
}
void ReplicaSet::setSocketFactory(Connection::SocketFactory* factory)
{
std::lock_guard<std::mutex> lock(_mutex);
_config.socketFactory = factory;
}
void ReplicaSet::setReadPreference(const ReadPreference& pref)
{
std::lock_guard<std::mutex> lock(_mutex);
_config.readPreference = pref;
}
ReadPreference ReplicaSet::readPreference() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _config.readPreference;
}
std::string ReplicaSet::setName() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _topology.setName();
}
bool ReplicaSet::hasPrimary() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _topology.hasPrimary();
}
void ReplicaSet::monitor() noexcept
{
while (!_stopMonitoring.load())
{
try
{
// Update topology from all known servers
updateTopologyFromAllServers();
}
catch (...)
{
// Ignore errors during monitoring
}
// Sleep for heartbeat frequency
auto sleepUntil = std::chrono::steady_clock::now() +
std::chrono::seconds(_config.heartbeatFrequencySeconds);
// Check stop flag periodically during sleep
while (std::chrono::steady_clock::now() < sleepUntil)
{
if (_stopMonitoring.load())
{
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
}
Connection::Ptr ReplicaSet::selectServer(const ReadPreference& readPref)
{
Net::SocketAddress selectedAddress;
{
std::lock_guard<std::mutex> lock(_mutex);
// Select servers based on read preference
std::vector<ServerDescription> eligible = readPref.selectServers(_topology);
if (eligible.empty())
{
return nullptr; // No suitable server found
}
// Randomly select from eligible servers for load balancing
Poco::Random random;
int index = random.next(static_cast<int>(eligible.size()));
selectedAddress = eligible[index].address();
}
// Create connection outside the lock
return createConnection(selectedAddress);
}
Connection::Ptr ReplicaSet::createConnection(const Net::SocketAddress& address)
{
Connection::Ptr conn = new Connection();
try
{
conn->connect(address);
if (_config.socketFactory != nullptr)
{
// Use custom socket factory (e.g., for SSL/TLS)
// Custom factories can be set via Config or using setSocketFactory().
// They can access timeout values via configuration().connectTimeoutSeconds
// and configuration().socketTimeoutSeconds to properly configure sockets.
conn->connect(address.toString(), *_config.socketFactory);
}
else
{
conn->connect(address);
}
OpMsgMessage request("admin", "");
// Note: Connection class doesn't expose socket() accessor, so socket timeouts
// must be configured during socket creation via custom SocketFactory.
return conn;
}
catch (...)
{
// Mark server as unknown on connection failure
std::lock_guard<std::mutex> lock(_mutex);
_topology.markServerUnknown(address, "Connection failed");
throw;
}
}
void ReplicaSet::updateTopologyFromHello(const Net::SocketAddress& address) noexcept
{
Connection::Ptr conn = new Connection();
try
{
// Measure RTT
auto startTime = std::chrono::high_resolution_clock::now();
if (_config.socketFactory != nullptr)
{
// Custom factories can be set via Config or using setSocketFactory().
// They can access timeout values via configuration() to configure sockets.
conn->connect(address.toString(), *_config.socketFactory);
}
else
{
conn->connect(address);
}
// Note: Connection class doesn't expose socket() accessor, so socket timeouts
// must be configured during socket creation via custom SocketFactory.
// Send hello command
OpMsgMessage request("admin"s, ""s);
request.setCommandName(OpMsgMessage::CMD_HELLO);
OpMsgMessage response;
conn->sendRequest(request, response);
auto endTime = std::chrono::high_resolution_clock::now();
auto rttMicros = std::chrono::duration_cast<std::chrono::microseconds>(
endTime - startTime).count();
if (response.responseOk())
{
const Document& doc = response.body();
if (doc.get<bool>("isWritablePrimary", false) || doc.get<bool>("ismaster", false))
// Update topology
std::lock_guard<std::mutex> lock(_mutex);
const ServerDescription& server = _topology.updateServer(address, doc, rttMicros);
// Update replica set name if not set
// Get set name from the updated server instead of re-querying the document
if (_config.setName.empty() && !server.setName().empty())
{
return conn;
}
else if (doc.exists("primary"))
{
return isMaster(Net::SocketAddress(doc.get<std::string>("primary")));
_config.setName = server.setName();
_topology.setName(_config.setName);
}
}
else
{
// Mark server as unknown
std::lock_guard<std::mutex> lock(_mutex);
_topology.markServerUnknown(address, "Hello command failed"s);
}
}
catch (const std::exception& e)
{
// Mark server as unknown
std::lock_guard<std::mutex> lock(_mutex);
_topology.markServerUnknown(address, e.what());
}
catch (...)
{
conn = nullptr;
// Mark server as unknown
std::lock_guard<std::mutex> lock(_mutex);
_topology.markServerUnknown(address, "Unknown error"s);
}
}
void ReplicaSet::updateTopologyFromAllServers() noexcept
{
// Capture current topology before refresh
TopologyDescription oldTopology;
std::vector<ServerDescription> servers;
{
std::lock_guard<std::mutex> lock(_mutex);
oldTopology = _topology;
servers = _topology.servers();
}
return nullptr;
// Update each server (sequentially for now, could be parallelized)
for (const auto& server : servers)
{
if (_stopMonitoring.load())
{
break; // Stop if monitoring is being shut down
}
try
{
updateTopologyFromHello(server.address());
}
catch (...)
{
// Continue to next server
}
}
// Update timestamp after refreshing topology
_topologyRefreshTime = std::chrono::steady_clock::now();
// Get new topology and compare using comparison operator
TopologyDescription newTopology;
{
std::lock_guard<std::mutex> lock(_mutex);
newTopology = _topology;
}
// Check if topology changed using comparison operator
if (oldTopology == newTopology)
{
// No change detected, nothing to notify
return;
}
// Topology changed - build brief change description
// Note: Build notification data outside mutex to allow handlers to access ReplicaSet methods
std::string changeDescription;
// Check topology type change
if (oldTopology.type() != newTopology.type())
{
changeDescription = "Type: " + TopologyDescription::typeToString(oldTopology.type()) +
" -> " + TopologyDescription::typeToString(newTopology.type());
}
// Check primary change
auto oldPrimary = oldTopology.findPrimary();
auto newPrimary = newTopology.findPrimary();
bool oldHadPrimary = oldPrimary.type() != ServerDescription::Unknown;
bool newHasPrimary = newPrimary.type() != ServerDescription::Unknown;
if (oldHadPrimary != newHasPrimary)
{
if (!changeDescription.empty()) changeDescription += "; ";
if (newHasPrimary)
changeDescription += "Primary elected: " + newPrimary.address().toString();
else
changeDescription += "Primary lost: " + oldPrimary.address().toString();
}
else if (oldHadPrimary && newHasPrimary && oldPrimary != newPrimary)
{
if (!changeDescription.empty()) changeDescription += "; ";
changeDescription += "Primary: " + oldPrimary.address().toString() +
" -> " + newPrimary.address().toString();
}
// Check server count change
if (oldTopology.serverCount() != newTopology.serverCount())
{
if (!changeDescription.empty()) changeDescription += "; ";
changeDescription += "Servers: " + std::to_string(oldTopology.serverCount()) +
" -> " + std::to_string(newTopology.serverCount());
}
// If no specific changes identified, use generic message
if (changeDescription.empty())
{
changeDescription = "Topology updated";
}
// Send notification outside mutex to allow handlers to access ReplicaSet methods
Poco::DynamicStruct notificationData;
notificationData["replicaSet"s] = newTopology.setName();
notificationData["timestamp"s] =
std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
notificationData["topologyType"s] = TopologyDescription::typeToString(newTopology.type());
notificationData["changeDescription"s] = changeDescription;
Poco::NotificationCenter::defaultCenter().postNotification(
new TopologyChangeNotification(notificationData)
);
}

View File

@@ -0,0 +1,334 @@
//
// ReplicaSetConnection.cpp
//
// Library: MongoDB
// Package: MongoDB
// Module: ReplicaSetConnection
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/ReplicaSetConnection.h"
#include "Poco/MongoDB/TopologyDescription.h"
#include "Poco/Net/NetException.h"
#include "Poco/Exception.h"
#include <set>
#include <thread>
using namespace std::literals;
namespace Poco {
namespace MongoDB {
// MongoDB error codes that indicate retriable errors
enum class ErrorCode
{
NotMaster = 10107,
NotMasterNoSlaveOk = 13435,
NotMasterOrSecondary = 13436,
InterruptedAtShutdown = 11600,
InterruptedDueToReplStateChange = 11602,
PrimarySteppedDown = 189,
ShutdownInProgress = 91,
HostNotFound = 7,
HostUnreachable = 6,
NetworkTimeout = 89,
SocketException = 9001
};
// Minimum retry count to run the MongoDB command.
static constexpr std::size_t lowExecuteRetryThreshold { 5 };
ReplicaSetConnection::ReplicaSetConnection(ReplicaSet& replicaSet, const ReadPreference& readPref):
_replicaSet(replicaSet),
_readPreference(readPref)
{
}
ReplicaSetConnection::~ReplicaSetConnection() = default;
void ReplicaSetConnection::sendRequest(OpMsgMessage& request, OpMsgMessage& response)
{
executeWithRetry([&]() {
_connection->sendRequest(request, response);
// Check if response contains a retriable error
if (!response.responseOk() && isRetriableMongoDBError(response))
{
markServerFailed();
throw Poco::IOException("MongoDB retriable error: "s + response.body().toString());
}
});
}
void ReplicaSetConnection::sendRequest(OpMsgMessage& request)
{
// One-way requests are not retried
ensureConnection();
_connection->sendRequest(request);
}
void ReplicaSetConnection::readResponse(OpMsgMessage& response)
{
ensureConnection();
_connection->readResponse(response);
}
Net::SocketAddress ReplicaSetConnection::address() const
{
if (_connection.isNull())
{
throw Poco::NullPointerException("Not connected to any server: address not available.");
}
return _connection->address();
}
Connection& ReplicaSetConnection::connection()
{
if (_connection.isNull())
{
throw Poco::NullPointerException("Not connected to any server: connection not available.");
}
return *_connection;
}
void ReplicaSetConnection::reconnect()
{
_connection = nullptr;
ensureConnection();
}
bool ReplicaSetConnection::isConnected() const noexcept
{
return !_connection.isNull();
}
bool ReplicaSetConnection::matchesReadPreference() const noexcept
{
if (!isConnected())
{
return false;
}
// Get the current topology
TopologyDescription topology = _replicaSet.topology();
// Get the server description for the currently connected server
ServerDescription server = topology.getServer(_connection->address());
// Check if the server is Unknown or has an error
if (server.type() == ServerDescription::Unknown || server.hasError())
{
return false;
}
// Use ReadPreference::selectServers to check if our current server
// would be selected with the current read preference
std::vector<ServerDescription> eligibleServers = _readPreference.selectServers(topology);
// Check if our current server is in the list of eligible servers
for (const auto& eligible : eligibleServers)
{
if (eligible.address() == _connection->address())
{
return true;
}
}
return false;
}
void ReplicaSetConnection::ensureConnection()
{
if (_connection.isNull())
{
_connection = _replicaSet.getConnection(_readPreference);
if (_connection.isNull())
{
throw Poco::IOException("No suitable server found in replica set");
}
}
}
void ReplicaSetConnection::executeWithRetry(std::function<void()> operation)
{
std::exception_ptr lastException;
std::set<Net::SocketAddress> triedServers;
// Retry with different servers until we've tried all available servers with a minimum
// retry threshold to cover situations when single server topology or complete replica set
// is not available temporarily.
const auto topology = _replicaSet.topology();
const auto rsConfig = _replicaSet.configuration();
const std::size_t maxAttempts = std::max(topology.serverCount(), lowExecuteRetryThreshold);
std::size_t attempt = 0;
while (attempt < maxAttempts)
{
try
{
ensureConnection();
triedServers.insert(_connection->address());
operation();
return; // Success
}
catch (const std::exception& e)
{
if (!isRetriableError(e))
{
throw;
}
// Retriable error.
lastException = std::current_exception();
}
catch (...)
{
throw; // Non-retriable error
}
// Mark current server as failed
markServerFailed();
// Try to get a new connection to a different server
_connection = nullptr;
// Get new connection, avoiding servers we've already tried
Connection::Ptr newConn = _replicaSet.getConnection(_readPreference);
if (newConn.isNull())
{
// No servers currently available - use coordinated retry logic.
// This ensures only one thread performs the sleep/refresh cycle.
newConn = _replicaSet.waitForServerAvailability(_readPreference);
// Clear tried servers since we're starting fresh after waiting
triedServers.clear();
if (newConn.isNull())
{
// No servers available even after retries
break;
}
}
Net::SocketAddress addr = newConn->address();
if (triedServers.find(addr) == triedServers.end())
{
_connection = newConn;
++attempt;
}
}
// All retries failed, rethrow the last exception
if (lastException)
{
std::rethrow_exception(lastException);
}
else
{
throw Poco::IOException("Failed to execute operation on any replica set member");
}
}
bool ReplicaSetConnection::isRetriableError(const std::exception& e)
{
// Network exceptions are generally retriable
if (dynamic_cast<const Poco::Net::NetException*>(&e) != nullptr)
{
return true;
}
// Timeout exceptions are retriable
if (dynamic_cast<const Poco::TimeoutException*>(&e) != nullptr)
{
return true;
}
// I/O exceptions are retriable
if (dynamic_cast<const Poco::IOException*>(&e) != nullptr)
{
return true;
}
return false;
}
bool ReplicaSetConnection::isRetriableMongoDBError(const OpMsgMessage& response)
{
if (response.responseOk())
{
return false;
}
const Document& body = response.body();
// Check for error code
if (body.exists("code"s))
{
ErrorCode code = static_cast<ErrorCode>(body.get<int>("code"s));
switch (code)
{
case ErrorCode::NotMaster:
case ErrorCode::NotMasterNoSlaveOk:
case ErrorCode::NotMasterOrSecondary:
case ErrorCode::InterruptedAtShutdown:
case ErrorCode::InterruptedDueToReplStateChange:
case ErrorCode::PrimarySteppedDown:
case ErrorCode::ShutdownInProgress:
case ErrorCode::HostNotFound:
case ErrorCode::HostUnreachable:
case ErrorCode::NetworkTimeout:
case ErrorCode::SocketException:
return true;
default:
return false;
}
}
// Check for error message patterns
if (body.exists("errmsg"s))
{
const auto& errmsg = body.get<std::string>("errmsg"s);
if (errmsg.find("not master"s) != std::string::npos ||
errmsg.find("NotMaster"s) != std::string::npos)
{
return true;
}
}
return false;
}
void ReplicaSetConnection::markServerFailed()
{
if (!_connection.isNull())
{
// Refresh topology to detect changes
_replicaSet.refreshTopology();
}
}
} } // namespace Poco::MongoDB

View File

@@ -0,0 +1,495 @@
//
// ReplicaSetURI.cpp
//
// Library: MongoDB
// Package: MongoDB
// Module: ReplicaSetURI
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/ReplicaSetURI.h"
#include "Poco/URI.h"
#include "Poco/NumberParser.h"
#include "Poco/Exception.h"
#include "Poco/String.h"
#include <sstream>
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
ReplicaSetURI::ReplicaSetURI()
{
}
ReplicaSetURI::ReplicaSetURI(const std::string& uri)
{
parse(uri);
}
ReplicaSetURI::~ReplicaSetURI()
{
}
const std::vector<std::string>& ReplicaSetURI::servers() const
{
return _servers;
}
void ReplicaSetURI::setServers(const std::vector<std::string>& servers)
{
_servers = servers;
}
void ReplicaSetURI::addServer(const std::string& server)
{
_servers.push_back(server);
}
void ReplicaSetURI::clearServers()
{
_servers.clear();
}
std::string ReplicaSetURI::replicaSet() const
{
return _replicaSet;
}
void ReplicaSetURI::setReplicaSet(const std::string& name)
{
_replicaSet = name;
}
ReadPreference ReplicaSetURI::readPreference() const
{
return _readPreference;
}
void ReplicaSetURI::setReadPreference(const ReadPreference& pref)
{
_readPreference = pref;
}
void ReplicaSetURI::setReadPreference(const std::string& mode)
{
std::string lowerMode = Poco::toLower(mode);
if (lowerMode == "primary"s)
{
_readPreference = ReadPreference(ReadPreference::Primary);
}
else if (lowerMode == "primarypreferred"s)
{
_readPreference = ReadPreference(ReadPreference::PrimaryPreferred);
}
else if (lowerMode == "secondary"s)
{
_readPreference = ReadPreference(ReadPreference::Secondary);
}
else if (lowerMode == "secondarypreferred"s)
{
_readPreference = ReadPreference(ReadPreference::SecondaryPreferred);
}
else if (lowerMode == "nearest"s)
{
_readPreference = ReadPreference(ReadPreference::Nearest);
}
else
{
throw Poco::InvalidArgumentException("Invalid read preference mode: " + mode);
}
}
unsigned int ReplicaSetURI::connectTimeoutMS() const
{
return _connectTimeoutMS;
}
void ReplicaSetURI::setConnectTimeoutMS(unsigned int timeoutMS)
{
_connectTimeoutMS = timeoutMS;
}
unsigned int ReplicaSetURI::socketTimeoutMS() const
{
return _socketTimeoutMS;
}
void ReplicaSetURI::setSocketTimeoutMS(unsigned int timeoutMS)
{
_socketTimeoutMS = timeoutMS;
}
unsigned int ReplicaSetURI::heartbeatFrequencyMS() const
{
return _heartbeatFrequencyMS;
}
void ReplicaSetURI::setHeartbeatFrequencyMS(unsigned int milliseconds)
{
if (milliseconds < MIN_HEARTBEAT_FREQUENCY_MS)
{
throw Poco::InvalidArgumentException(
"heartbeatFrequencyMS must be at least " +
std::to_string(MIN_HEARTBEAT_FREQUENCY_MS) +
" milliseconds per MongoDB SDAM specification");
}
_heartbeatFrequencyMS = milliseconds;
}
unsigned int ReplicaSetURI::reconnectRetries() const
{
return _reconnectRetries;
}
void ReplicaSetURI::setReconnectRetries(unsigned int retries)
{
_reconnectRetries = retries;
}
unsigned int ReplicaSetURI::reconnectDelay() const
{
return _reconnectDelay;
}
void ReplicaSetURI::setReconnectDelay(unsigned int seconds)
{
_reconnectDelay = seconds;
}
std::string ReplicaSetURI::database() const
{
return _database;
}
void ReplicaSetURI::setDatabase(const std::string& database)
{
_database = database;
}
std::string ReplicaSetURI::username() const
{
return _username;
}
void ReplicaSetURI::setUsername(const std::string& username)
{
_username = username;
}
std::string ReplicaSetURI::password() const
{
return _password;
}
void ReplicaSetURI::setPassword(const std::string& password)
{
_password = password;
}
std::string ReplicaSetURI::toString() const
{
if (_servers.empty())
{
throw Poco::InvalidArgumentException("Cannot generate URI: no servers configured");
}
std::ostringstream uri;
// Scheme
uri << "mongodb://";
// User info
if (!_username.empty())
{
uri << _username;
if (!_password.empty())
{
uri << ":" << _password;
}
uri << "@";
}
// Hosts
for (std::size_t i = 0; i < _servers.size(); ++i)
{
if (i > 0)
{
uri << ",";
}
uri << _servers[i];
}
// Database
if (!_database.empty())
{
uri << "/" << _database;
}
// Query parameters
std::string queryString = buildQueryString();
if (!queryString.empty())
{
// Add leading '/' if we don't have a database
if (_database.empty())
{
uri << "/";
}
uri << "?" << queryString;
}
return uri.str();
}
void ReplicaSetURI::parse(const std::string& uri)
{
// MongoDB URIs can contain comma-separated hosts which Poco::URI doesn't handle correctly.
// We need to extract the host list manually first, then create a simplified URI for Poco::URI
// to parse the scheme, path, and query parameters.
// Find the scheme delimiter
auto schemeEnd = uri.find("://");
if (schemeEnd == std::string::npos)
{
throw Poco::SyntaxException("Invalid URI: missing scheme delimiter");
}
std::string scheme = uri.substr(0, schemeEnd);
if (scheme != "mongodb"s)
{
throw Poco::UnknownURISchemeException("Replica set URI must use 'mongodb' scheme");
}
// Find where the authority (hosts) section ends
// It ends at either '/' (path) or '?' (query)
std::string::size_type authorityStart = schemeEnd + 3; // Skip "://"
std::string::size_type authorityEnd = uri.find_first_of("/?", authorityStart);
// Extract authority and the rest of the URI
std::string authority;
std::string pathAndQuery;
if (authorityEnd != std::string::npos)
{
authority = uri.substr(authorityStart, authorityEnd - authorityStart);
pathAndQuery = uri.substr(authorityEnd);
}
else
{
authority = uri.substr(authorityStart);
pathAndQuery = "";
}
// Parse user info if present (username:password@)
const auto atPos = authority.find('@');
std::string hostsStr;
if (atPos != std::string::npos)
{
std::string userInfo = authority.substr(0, atPos);
hostsStr = authority.substr(atPos + 1);
// Parse username and password
auto colonPos = userInfo.find(':');
if (colonPos != std::string::npos)
{
_username = userInfo.substr(0, colonPos);
_password = userInfo.substr(colonPos + 1);
}
else
{
_username = userInfo;
_password = "";
}
}
else
{
hostsStr = authority;
_username = "";
_password = "";
}
// Parse comma-separated hosts
// Store as strings WITHOUT resolving - resolution happens in ReplicaSet
_servers.clear();
std::string::size_type start = 0;
std::string::size_type end;
while ((end = hostsStr.find(',', start)) != std::string::npos)
{
const auto hostPort = hostsStr.substr(start, end - start);
if (!hostPort.empty())
{
_servers.push_back(hostPort);
}
start = end + 1;
}
// Parse last host
const auto lastHost = hostsStr.substr(start);
if (!lastHost.empty())
{
_servers.push_back(lastHost);
}
if (_servers.empty())
{
throw Poco::SyntaxException("No valid hosts found in replica set URI");
}
// Parse path and query using Poco::URI
// Create a simplified URI with just the scheme and path/query for Poco::URI to parse
std::string simplifiedURI = scheme + "://localhost" + pathAndQuery;
Poco::URI theURI(simplifiedURI);
// Extract database from path
std::string path = theURI.getPath();
if (!path.empty() && path[0] == '/')
{
_database = path.substr(1); // Remove leading '/'
}
else
{
_database = path;
}
// Parse query parameters
Poco::URI::QueryParameters params = theURI.getQueryParameters();
parseOptions(params);
}
void ReplicaSetURI::parseOptions(const Poco::URI::QueryParameters& params)
{
for (const auto& param : params)
{
if (param.first == "replicaSet"s)
{
setReplicaSet(param.second);
}
else if (param.first == "readPreference"s)
{
setReadPreference(param.second);
}
else if (param.first == "connectTimeoutMS"s)
{
setConnectTimeoutMS(Poco::NumberParser::parseUnsigned(param.second));
}
else if (param.first == "socketTimeoutMS"s)
{
setSocketTimeoutMS(Poco::NumberParser::parseUnsigned(param.second));
}
else if (param.first == "heartbeatFrequencyMS"s)
{
setHeartbeatFrequencyMS(Poco::NumberParser::parseUnsigned(param.second));
}
else if (param.first == "reconnectRetries"s)
{
setReconnectRetries(Poco::NumberParser::parseUnsigned(param.second));
}
else if (param.first == "reconnectDelay"s)
{
setReconnectDelay(Poco::NumberParser::parseUnsigned(param.second));
}
// Add other options as needed
}
}
std::string ReplicaSetURI::buildQueryString() const
{
std::vector<std::string> params;
if (!_replicaSet.empty())
{
params.push_back("replicaSet=" + _replicaSet);
}
if (_readPreference.mode() != ReadPreference::Primary)
{
params.push_back("readPreference=" + _readPreference.toString());
}
if (_connectTimeoutMS != DEFAULT_CONNECT_TIMEOUT_MS) // Only add if non-default
{
params.push_back("connectTimeoutMS=" + std::to_string(_connectTimeoutMS));
}
if (_socketTimeoutMS != DEFAULT_SOCKET_TIMEOUT_MS) // Only add if non-default
{
params.push_back("socketTimeoutMS=" + std::to_string(_socketTimeoutMS));
}
if (_heartbeatFrequencyMS != DEFAULT_HEARTBEAT_FREQUENCY_MS) // Only add if non-default
{
params.push_back("heartbeatFrequencyMS=" + std::to_string(_heartbeatFrequencyMS));
}
if (_reconnectRetries != DEFAULT_RECONNECT_RETRIES) // Only add if non-default
{
params.push_back("reconnectRetries=" + std::to_string(_reconnectRetries));
}
if (_reconnectDelay != DEFAULT_RECONNECT_DELAY) // Only add if non-default
{
params.push_back("reconnectDelay=" + std::to_string(_reconnectDelay));
}
if (params.empty())
{
return "";
}
std::ostringstream queryString;
for (std::size_t i = 0; i < params.size(); ++i)
{
if (i > 0)
{
queryString << "&";
}
queryString << params[i];
}
return queryString.str();
}
} } // namespace Poco::MongoDB

View File

@@ -0,0 +1,254 @@
//
// ServerDescription.cpp
//
// Library: MongoDB
// Package: MongoDB
// Module: ServerDescription
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/ServerDescription.h"
#include "Poco/MongoDB/Array.h"
using namespace std::string_literals;
namespace Poco {
namespace MongoDB {
ServerDescription::ServerDescription() = default;
ServerDescription::ServerDescription(const Net::SocketAddress& address):
_address(address)
{
}
ServerDescription::ServerDescription(const ServerDescription& other) = default;
ServerDescription::ServerDescription(ServerDescription&& other) noexcept = default;
ServerDescription::~ServerDescription() = default;
ServerDescription& ServerDescription::operator=(const ServerDescription& other) = default;
ServerDescription& ServerDescription::operator=(ServerDescription&& other) noexcept = default;
bool ServerDescription::operator==(const ServerDescription& other) const
{
return _type == other._type &&
_address == other._address &&
_setName == other._setName &&
_hasError == other._hasError;
}
bool ServerDescription::operator!=(const ServerDescription& other) const
{
return !(*this == other);
}
std::vector<Net::SocketAddress> ServerDescription::updateFromHelloResponse(const Document& helloResponse, Poco::Int64 rttMicros)
{
_lastUpdateTime.update();
_roundTripTime = rttMicros;
_hasError = false;
_error.clear();
// Parse server type
parseServerType(helloResponse);
// Get replica set name
if (helloResponse.exists("setName"s))
{
_setName = helloResponse.get<std::string>("setName"s);
}
// Parse and return hosts list
auto hosts = parseHosts(helloResponse);
// Parse tags
parseTags(helloResponse);
return hosts;
}
void ServerDescription::markError(const std::string& errorMessage)
{
_type = Unknown;
_error = errorMessage;
_hasError = true;
_lastUpdateTime.update();
}
void ServerDescription::reset()
{
_type = Unknown;
_lastUpdateTime = 0;
_roundTripTime = 0;
_setName.clear();
_tags.clear();
_error.clear();
_hasError = false;
}
void ServerDescription::parseServerType(const Document& doc)
{
// Check for standalone
if (!doc.exists("setName"s))
{
// Check if it's a mongos
if (doc.get<std::string>("msg"s, ""s) == "isdbgrid")
{
_type = Mongos;
return;
}
_type = Standalone;
return;
}
// It's part of a replica set - determine the role
if (doc.get<bool>("isWritablePrimary"s, false))
{
_type = RsPrimary;
}
else if (doc.get<bool>("secondary"s, false))
{
_type = RsSecondary;
}
else if (doc.get<bool>("arbiterOnly"s, false))
{
_type = RsArbiter;
}
else if (doc.get<bool>("hidden"s, false) || doc.get<bool>("passive"s, false))
{
_type = RsOther;
}
else
{
// Server is in replica set but role unclear (might be initializing)
_type = RsGhost;
}
}
std::vector<Net::SocketAddress> ServerDescription::parseHosts(const Document& doc)
{
std::vector<Net::SocketAddress> hosts;
// Parse hosts array
if (doc.exists("hosts"s))
{
Array::Ptr hostsArray = doc.get<Array::Ptr>("hosts"s);
hosts.reserve(hostsArray->size());
for (std::size_t i = 0; i < hostsArray->size(); ++i)
{
try
{
std::string hostStr = hostsArray->get<std::string>(i);
hosts.emplace_back(hostStr);
}
catch (...)
{
// Skip invalid host addresses
}
}
}
// Parse passives array (hidden/passive members)
if (doc.exists("passives"s))
{
Array::Ptr passivesArray = doc.get<Array::Ptr>("passives"s);
for (std::size_t i = 0; i < passivesArray->size(); ++i)
{
try
{
std::string hostStr = passivesArray->get<std::string>(i);
hosts.emplace_back(hostStr);
}
catch (...)
{
// Skip invalid host addresses
}
}
}
// Parse arbiters array
if (doc.exists("arbiters"s))
{
Array::Ptr arbitersArray = doc.get<Array::Ptr>("arbiters"s);
for (std::size_t i = 0; i < arbitersArray->size(); ++i)
{
try
{
std::string hostStr = arbitersArray->get<std::string>(i);
hosts.emplace_back(hostStr);
}
catch (...)
{
// Skip invalid host addresses
}
}
}
return hosts;
}
void ServerDescription::parseTags(const Document& doc)
{
_tags.clear();
if (doc.exists("tags"s))
{
Document::Ptr tagsDoc = doc.get<Document::Ptr>("tags"s);
if (!tagsDoc.isNull())
{
_tags = *tagsDoc;
}
}
}
std::string ServerDescription::typeToString(ServerType type)
{
switch (type)
{
case RsPrimary:
return "PRIMARY"s;
case RsSecondary:
return "SECONDARY"s;
case RsArbiter:
return "ARBITER"s;
case Standalone:
return "STANDALONE"s;
case Mongos:
return "MONGOS"s;
case RsOther:
return "OTHER"s;
case RsGhost:
return "GHOST"s;
case Unknown:
default:
return "UNKNOWN"s;
}
}
} } // namespace Poco::MongoDB

View File

@@ -0,0 +1,447 @@
//
// TopologyDescription.cpp
//
// Library: MongoDB
// Package: MongoDB
// Module: TopologyDescription
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/MongoDB/TopologyDescription.h"
namespace Poco {
namespace MongoDB {
TopologyDescription::TopologyDescription() = default;
TopologyDescription::TopologyDescription(const std::string& setName):
_setName(setName)
{
}
TopologyDescription::TopologyDescription(const TopologyDescription& other)
{
std::lock_guard<std::mutex> lock(other._mutex);
_type = other._type;
_setName = other._setName;
_servers = other._servers;
}
TopologyDescription::TopologyDescription(TopologyDescription&& other) noexcept
{
std::lock_guard<std::mutex> lock(other._mutex);
_type = other._type;
_setName = std::move(other._setName);
_servers = std::move(other._servers);
}
TopologyDescription::~TopologyDescription() = default;
TopologyDescription& TopologyDescription::operator=(const TopologyDescription& other)
{
if (this != &other)
{
// Lock both mutexes to avoid deadlock (lock in consistent order)
std::scoped_lock lock(_mutex, other._mutex);
_type = other._type;
_setName = other._setName;
_servers = other._servers;
}
return *this;
}
TopologyDescription& TopologyDescription::operator=(TopologyDescription&& other) noexcept
{
if (this != &other)
{
// Lock both mutexes to avoid deadlock (lock in consistent order)
std::scoped_lock lock(_mutex, other._mutex);
_type = other._type;
_setName = std::move(other._setName);
_servers = std::move(other._servers);
}
return *this;
}
bool TopologyDescription::operator==(const TopologyDescription& other) const
{
std::scoped_lock lock(_mutex, other._mutex);
// Compare topology type
if (_type != other._type)
return false;
// Compare set name
if (_setName != other._setName)
return false;
// Compare servers map
if (_servers.size() != other._servers.size())
return false;
// Compare each server
for (const auto& [address, server] : _servers)
{
auto it = other._servers.find(address);
if (it == other._servers.end())
return false;
if (server != it->second)
return false;
}
return true;
}
bool TopologyDescription::operator!=(const TopologyDescription& other) const
{
return !(*this == other);
}
TopologyDescription::TopologyType TopologyDescription::type() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _type;
}
std::string TopologyDescription::setName() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _setName;
}
void TopologyDescription::setName(const std::string& name)
{
std::lock_guard<std::mutex> lock(_mutex);
_setName = name;
}
std::vector<ServerDescription> TopologyDescription::servers() const
{
std::lock_guard<std::mutex> lock(_mutex);
std::vector<ServerDescription> result;
result.reserve(_servers.size());
for (const auto& [address, server] : _servers)
{
result.emplace_back(server);
}
return result;
}
ServerDescription TopologyDescription::findPrimary() const
{
std::lock_guard<std::mutex> lock(_mutex);
for (const auto& [address, server] : _servers)
{
if (server.isPrimary())
{
return server;
}
}
return ServerDescription();
}
std::vector<ServerDescription> TopologyDescription::findSecondaries() const
{
std::lock_guard<std::mutex> lock(_mutex);
std::vector<ServerDescription> result;
result.reserve(_servers.size());
for (const auto& [address, server] : _servers)
{
if (server.isSecondary())
{
result.emplace_back(server);
}
}
return result;
}
bool TopologyDescription::hasPrimary() const
{
std::lock_guard<std::mutex> lock(_mutex);
for (const auto& [address, server] : _servers)
{
if (server.isPrimary())
{
return true;
}
}
return false;
}
bool TopologyDescription::hasServer(const Net::SocketAddress& address) const
{
std::lock_guard<std::mutex> lock(_mutex);
return _servers.find(address) != _servers.end();
}
ServerDescription TopologyDescription::getServer(const Net::SocketAddress& address) const
{
std::lock_guard<std::mutex> lock(_mutex);
auto it = _servers.find(address);
if (it != _servers.end())
{
return it->second;
}
return ServerDescription(address);
}
const ServerDescription& TopologyDescription::updateServer(const Net::SocketAddress& address, const Document& helloResponse, Poco::Int64 rttMicros)
{
std::lock_guard<std::mutex> lock(_mutex);
// Find or create server description
auto it = _servers.find(address);
if (it == _servers.end())
{
it = _servers.try_emplace(address, address).first;
}
// Update from hello response and get discovered hosts
auto hosts = it->second.updateFromHelloResponse(helloResponse, rttMicros);
// Validate replica set name matches
// Per MongoDB SDAM specification: servers with mismatching replica set names
// should be marked as Unknown and their discovered hosts should be ignored
const std::string& serverSetName = it->second.setName();
if (!serverSetName.empty() && !_setName.empty() && serverSetName != _setName)
{
// Replica set name mismatch - mark server as Unknown with error
it->second.markError("Replica set name mismatch: expected '" + _setName +
"', but server reports '" + serverSetName + "'");
// Do NOT add discovered hosts from this server (they belong to a different replica set)
// Do NOT update topology type yet - wait for next refresh
updateTopologyType();
return it->second;
}
// Update replica set name if not set (only if server reports a set name)
if (_setName.empty() && !serverSetName.empty())
{
_setName = serverSetName;
}
// Add newly discovered hosts to the topology (only if set name matches)
// This prevents cross-contamination between different replica sets
// Note: We add ALL discovered hosts even if types might be incompatible
// The updateTopologyType() will detect incompatibility and set topology to Unknown
// This approach preserves all discovered servers for diagnostic purposes
for (const auto& host : hosts)
{
_servers.try_emplace(host, host);
}
// Update topology type
// This will detect any incompatible server type combinations and set topology to Unknown if needed
updateTopologyType();
// Return reference to the updated server
return it->second;
}
void TopologyDescription::markServerUnknown(const Net::SocketAddress& address, const std::string& error)
{
std::lock_guard<std::mutex> lock(_mutex);
auto it = _servers.find(address);
if (it != _servers.end())
{
it->second.markError(error);
updateTopologyType();
}
}
void TopologyDescription::addServer(const Net::SocketAddress& address)
{
std::lock_guard<std::mutex> lock(_mutex);
auto [_, inserted] = _servers.try_emplace(address, address);
if (inserted)
{
updateTopologyType();
}
}
void TopologyDescription::removeServer(const Net::SocketAddress& address)
{
std::lock_guard<std::mutex> lock(_mutex);
_servers.erase(address);
updateTopologyType();
}
void TopologyDescription::clear()
{
std::lock_guard<std::mutex> lock(_mutex);
_servers.clear();
_type = Unknown;
}
std::size_t TopologyDescription::serverCount() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _servers.size();
}
void TopologyDescription::updateTopologyType()
{
// This method must be called while holding the mutex
if (_servers.empty())
{
_type = Unknown;
return;
}
// Count server types for topology classification
// Note: These counters are used to determine the overall topology type
// based on the types of servers that have been discovered and updated
int primaries = 0;
int otherRsMembers = 0; // Non-primary replica set members (secondaries, arbiters, etc.)
int mongosCount = 0;
int standaloneCount = 0;
int unknownCount = 0;
for (const auto& [address, server] : _servers)
{
switch (server.type())
{
case ServerDescription::RsPrimary:
primaries++;
break;
case ServerDescription::RsSecondary:
case ServerDescription::RsArbiter:
case ServerDescription::RsOther:
case ServerDescription::RsGhost:
// Count all non-primary replica set members together
// for topology determination (they all indicate replica set membership)
otherRsMembers++;
break;
case ServerDescription::Mongos:
mongosCount++;
break;
case ServerDescription::Standalone:
standaloneCount++;
break;
case ServerDescription::Unknown:
// Unknown servers don't affect topology classification
// Count them to help with diagnostics
unknownCount++;
break;
}
}
// STEP 1: Validate server type compatibility (per MongoDB SDAM specification)
// Incompatible combinations indicate misconfiguration and must result in Unknown topology
// This prevents incorrect routing and security issues
// Cannot mix mongos with replica set members
if (mongosCount > 0 && (primaries > 0 || otherRsMembers > 0))
{
_type = Unknown;
return;
}
// Cannot mix standalone with any other server type
if (standaloneCount > 0 && (primaries > 0 || otherRsMembers > 0 || mongosCount > 0))
{
_type = Unknown;
return;
}
// Multiple standalone servers is invalid (each standalone is independent)
if (standaloneCount > 1)
{
_type = Unknown;
return;
}
// STEP 2: Classify valid topologies
// At this point, server types are compatible, so we can safely determine topology type
if (mongosCount > 0)
{
// Sharded cluster: one or more mongos routers detected
_type = Sharded;
}
else if (standaloneCount == 1 && _servers.size() == 1)
{
// Single standalone server - treat as Single topology
// Standalone servers behave like a single primary for read preferences
_type = Single;
}
else if (primaries > 0)
{
// Replica set with at least one primary
_type = ReplicaSetWithPrimary;
}
else if (otherRsMembers > 0 || !_setName.empty())
{
// Replica set without primary: either we have non-primary replica set members,
// or we have a configured setName (indicating this is intended to be a replica set)
_type = ReplicaSetNoPrimary;
}
else
{
// Unable to determine topology (all servers are unknown or no clear pattern)
_type = Unknown;
}
}
std::string TopologyDescription::typeToString(TopologyType type)
{
switch (type)
{
case Single:
return "Single Server"s;
case ReplicaSetWithPrimary:
return "Replica Set (with Primary)"s;
case ReplicaSetNoPrimary:
return "Replica Set (no Primary)"s;
case Sharded:
return "Sharded Cluster"s;
case Unknown:
default:
return "Unknown"s;
}
}
} } // namespace Poco::MongoDB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
//
// BSONTest.h
//
// Definition of the BSONTest class.
//
// Copyright (c) 2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef BSONTest_INCLUDED
#define BSONTest_INCLUDED
#include "CppUnit/TestCase.h"
class BSONTest: public CppUnit::TestCase
{
public:
BSONTest(const std::string& name);
virtual ~BSONTest();
void setUp();
void tearDown();
// Document tests
void testDocumentAddGet();
void testDocumentExists();
void testDocumentRemove();
void testDocumentClear();
void testDocumentSize();
void testDocumentElementNames();
void testNestedDocuments();
void testDuplicateDocumentMembers();
// Array tests
void testArray();
void testArrayIndexAccess();
void testArrayNested();
// Type tests
void testDouble();
void testString();
void testBool();
void testInt32();
void testInt64();
void testTimestamp();
void testNull();
void testBSONTimestamp();
// Binary tests
void testBinaryGeneric();
void testBinaryUUID();
void testBinarySubtypes();
// ObjectId tests
void testObjectID();
void testObjectIDTimestamp();
// RegularExpression tests
void testRegularExpression();
// JavaScriptCode tests
void testJavaScriptCode();
// Serialization/Deserialization tests
void testDocumentSerialization();
void testDocumentDeserialization();
void testArraySerialization();
void testComplexDocumentSerialization();
// toString tests
void testSimpleDocumentToString();
void testNestedDocumentToString();
void testDocumentWithArrayToString();
void testComplexDocumentToString();
void testToStringIndentation();
void testArrayToString();
// Failure/Error tests
void testGetNonExistent();
void testBadCast();
void testInvalidObjectID();
void testEmptyDocument();
static CppUnit::Test* suite();
};
#endif // BSONTest_INCLUDED

View File

@@ -27,6 +27,7 @@
using namespace Poco::MongoDB;
using namespace std::string_literals;
Poco::MongoDB::Connection::Ptr MongoDBTest::_mongo;
@@ -54,43 +55,6 @@ void MongoDBTest::tearDown()
}
void MongoDBTest::testArray()
{
Poco::MongoDB::Array::Ptr arr = new Poco::MongoDB::Array();
arr->add(std::string("First"));
Poco::DateTime birthdate;
birthdate.assign(1969, 3, 9);
arr->add(birthdate.timestamp());
arr->add(static_cast<Poco::Int32>(1993));
arr->add(false);
// Document-style interface
arr->add("4", "12.4E");
assertEqual(arr->size(), 5);
assertTrue(arr->exists("0"));
assertTrue(arr->exists("1"));
assertTrue(arr->exists("2"));
assertTrue(arr->exists("3"));
assertTrue(arr->exists("4"));
assertFalse(arr->exists("5"));
assertEqual(arr->get<std::string>(0), "First");
assertEqual(arr->get<Poco::Timestamp>(1).raw(), birthdate.timestamp().raw());
assertEqual(arr->get<Poco::Int32>(2), 1993);
assertEqual(arr->get<bool>(3), false);
assertEqual(arr->get<std::string>(4), "12.4E");
// Document-style interface
assertEqual(arr->get<Poco::Int32>("2"), 1993);
assertEqual(arr->get<std::string>("4"), "12.4E");
}
void MongoDBTest::testBuildInfo()
{
Poco::MongoDB::Database db("config");
@@ -187,8 +151,8 @@ void MongoDBTest::testDBCount()
request->setCommandName(OpMsgMessage::CMD_INSERT);
Document::Ptr player = new Document();
player->add("lastname", std::string("TestPlayer"));
player->add("firstname", std::string("Test"));
player->add("lastname"s, "TestPlayer"s);
player->add("firstname"s, "Test"s);
request->documents().push_back(player);
OpMsgMessage response;
@@ -203,7 +167,7 @@ void MongoDBTest::testDBCount()
request = db.createOpMsgMessage("players");
request->setCommandName(OpMsgMessage::CMD_DELETE);
Document::Ptr del = new Document();
del->add("limit", 0).addNewDocument("q").add("lastname" , std::string("TestPlayer"));
del->add("limit"s, 0).addNewDocument("q").add("lastname"s, "TestPlayer"s);
request->documents().push_back(del);
_mongo->sendRequest(*request, response);
}
@@ -235,7 +199,6 @@ CppUnit::Test* MongoDBTest::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("MongoDBTest");
CppUnit_addTest(pSuite, MongoDBTest, testObjectID);
CppUnit_addTest(pSuite, MongoDBTest, testArray);
CppUnit_addTest(pSuite, MongoDBTest, testConnectURI);
CppUnit_addTest(pSuite, MongoDBTest, testHello);
CppUnit_addTest(pSuite, MongoDBTest, testBuildInfo);
@@ -258,8 +221,6 @@ CppUnit::Test* MongoDBTest::suite()
CppUnit_addTest(pSuite, MongoDBTest, testOpCmdCursorAggregate);
CppUnit_addTest(pSuite, MongoDBTest, testOpCmdKillCursor);
CppUnit_addTest(pSuite, MongoDBTest, testOpCmdCursorEmptyFirstBatch);
CppUnit_addTest(pSuite, MongoDBTest, testOpCmdUUID);
CppUnit_addTest(pSuite, MongoDBTest, testDBCount);

View File

@@ -14,7 +14,6 @@
#define MongoDBTest_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "Poco/MongoDB/Connection.h"
#include "CppUnit/TestCase.h"
@@ -30,13 +29,11 @@ public:
void tearDown();
void testObjectID();
void testArray();
void testBuildInfo();
void testHello();
void testConnectURI();
// OP_MSG wire protocol
void testOpCmdUUID();
void testOpCmdHello();
void testOpCmdWriteRead();
void testOpCmdInsert();

View File

@@ -23,60 +23,7 @@
using namespace Poco::MongoDB;
void MongoDBTest::testOpCmdUUID()
{
Database db("team");
Poco::SharedPtr<OpMsgMessage> request = db.createOpMsgMessage("club");
OpMsgMessage response;
request->setCommandName(OpMsgMessage::CMD_DROP);
_mongo->sendRequest(*request, response);
Document::Ptr club = new Document();
club->add("name", std::string("Barcelona"));
Poco::UUIDGenerator generator;
Poco::UUID uuid = generator.create();
Binary::Ptr uuidBinary = new Binary(uuid);
club->add("uuid", uuidBinary);
request->setCommandName(OpMsgMessage::CMD_INSERT);
request->documents().push_back(club);
_mongo->sendRequest(*request, response);
assertTrue(response.responseOk());
request->setCommandName(OpMsgMessage::CMD_FIND);
request->body().addNewDocument("filter").add("name", std::string("Barcelona"));
_mongo->sendRequest(*request, response);
assertTrue(response.responseOk());
if ( response.documents().size() > 0 )
{
Document::Ptr doc = response.documents()[0];
try
{
const auto& name = doc->get<std::string>("name");
assertEquals ("Barcelona", name );
Binary::Ptr uuidBinary = doc->get<Binary::Ptr>("uuid");
assertTrue (uuid == uuidBinary->uuid());
}
catch(Poco::NotFoundException& nfe)
{
fail(nfe.message() + " not found.");
}
}
else
{
fail("No document returned");
}
}
using namespace std::string_literals;
void MongoDBTest::testOpCmdHello()
@@ -110,11 +57,11 @@ void MongoDBTest::testOpCmdWriteRead()
request->setCommandName(OpMsgMessage::CMD_INSERT);
Document::Ptr doc = new Document();
doc->add("name", "John").add("number", -2);
doc->add("name"s, "John").add("number", -2);
request->documents().push_back(doc);
doc = new Document();
doc->add("name", "Franz").add("number", -2.8);
doc->add("name"s, "Franz").add("number", -2.8);
request->documents().push_back(doc);
try
@@ -142,20 +89,20 @@ void MongoDBTest::testOpCmdWriteRead()
void MongoDBTest::testOpCmdInsert()
{
Document::Ptr player = new Document();
player->add("lastname", std::string("Braem"));
player->add("firstname", std::string("Franky"));
player->add("lastname"s, "Braem"s);
player->add("firstname"s, "Franky"s);
Poco::DateTime birthdate;
birthdate.assign(1969, 3, 9);
player->add("birthdate", birthdate.timestamp());
player->add("birthdate"s, birthdate.timestamp());
player->add("start", 1993);
player->add("active", false);
player->add("start"s, 1993);
player->add("active"s, false);
Poco::DateTime now;
player->add("lastupdated", now.timestamp());
player->add("lastupdated"s, now.timestamp());
player->add("unknown", NullValue());
player->add("unknown"s, NullValue());
Database db("team");
Poco::SharedPtr<OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -181,7 +128,7 @@ void MongoDBTest::testOpCmdFind()
Poco::SharedPtr<OpMsgMessage> request = db.createOpMsgMessage("players");
request->setCommandName(OpMsgMessage::CMD_FIND);
request->body().add("limit", 1).addNewDocument("filter").add("lastname" , std::string("Braem"));
request->body().add("limit"s, 1).addNewDocument("filter").add("lastname"s, "Braem"s);
OpMsgMessage response;
_mongo->sendRequest(*request, response);
@@ -223,20 +170,20 @@ void MongoDBTest::testOpCmdFind()
void MongoDBTest::testOpCmdUnaknowledgedInsert()
{
Document::Ptr player = new Document();
player->add("lastname", std::string("Braem"));
player->add("firstname", std::string("Franky"));
player->add("lastname"s, "Braem"s);
player->add("firstname"s, "Franky"s);
Poco::DateTime birthdate;
birthdate.assign(1969, 3, 9);
player->add("birthdate", birthdate.timestamp());
player->add("birthdate"s, birthdate.timestamp());
player->add("start", 1993);
player->add("active", false);
player->add("start"s, 1993);
player->add("active"s, false);
Poco::DateTime now;
player->add("lastupdated", now.timestamp());
player->add("lastupdated"s, now.timestamp());
player->add("unknown", NullValue());
player->add("unknown"s, NullValue());
Database db("team");
Poco::SharedPtr<OpMsgMessage> request = db.createOpMsgMessage("players");
@@ -269,7 +216,7 @@ void MongoDBTest::testOpCmdCursor()
for(int i = 0; i < 10000; ++i)
{
Document::Ptr doc = new Document();
doc->add("number", i);
doc->add("number"s, i);
request->documents().push_back(doc);
}
_mongo->sendRequest(*request, response);
@@ -309,7 +256,7 @@ void MongoDBTest::testOpCmdCursorAggregate()
for(int i = 0; i < 10000; ++i)
{
Document::Ptr doc = new Document();
doc->add("number", i);
doc->add("number"s, i);
request->documents().push_back(doc);
}
_mongo->sendRequest(*request, response);
@@ -356,7 +303,7 @@ void MongoDBTest::testOpCmdKillCursor()
for(int i = 0; i < 10000; ++i)
{
Document::Ptr doc = new Document();
doc->add("number", i);
doc->add("number"s, i);
request->documents().push_back(doc);
}
_mongo->sendRequest(*request, response);
@@ -413,7 +360,7 @@ void MongoDBTest::testOpCmdCursorEmptyFirstBatch()
for(int i = 0; i < 10000; ++i)
{
Document::Ptr doc = new Document();
doc->add("number", i);
doc->add("number"s, i);
request->documents().push_back(doc);
}
_mongo->sendRequest(*request, response);
@@ -453,7 +400,7 @@ void MongoDBTest::testOpCmdDelete()
request->setCommandName(OpMsgMessage::CMD_DELETE);
Document::Ptr del = new Document();
del->add("limit", 0).addNewDocument("q").add("lastname" , std::string("Braem"));
del->add("limit"s, 0).addNewDocument("q").add("lastname"s, "Braem"s);
request->documents().push_back(del);
OpMsgMessage response;

View File

@@ -10,13 +10,22 @@
#include "MongoDBTestSuite.h"
#include "MongoDBTest.h"
#include "BSONTest.h"
#include "ReplicaSetTest.h"
CppUnit::Test* MongoDBTestSuite::suite()
{
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("MongoDBTestSuite");
pSuite->addTest(MongoDBTest::suite());
pSuite->addTest(BSONTest::suite());
pSuite->addTest(ReplicaSetTest::suite());
CppUnit::Test* mongoTests = MongoDBTest::suite();
if (mongoTests != nullptr)
{
pSuite->addTest(mongoTests);
}
return pSuite;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
//
// ReplicaSetTest.h
//
// Definition of the ReplicaSetTest class.
//
// Copyright (c) 2012-2025, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef ReplicaSetTest_INCLUDED
#define ReplicaSetTest_INCLUDED
#include "Poco/MongoDB/MongoDB.h"
#include "CppUnit/TestCase.h"
class ReplicaSetTest: public CppUnit::TestCase
{
public:
ReplicaSetTest(const std::string& name);
~ReplicaSetTest() override;
void testServerDescriptionPrimary();
void testServerDescriptionSecondary();
void testServerDescriptionArbiter();
void testServerDescriptionStandalone();
void testServerDescriptionMongos();
void testServerDescriptionWithTags();
void testServerDescriptionWithHosts();
void testServerDescriptionErrorHandling();
void testServerDescriptionReset();
void testTopologyEmpty();
void testTopologyAddServers();
void testTopologyUpdateToPrimary();
void testTopologyUpdateToSecondary();
void testTopologyReplicaSetWithPrimary();
void testTopologyReplicaSetNoPrimary();
void testTopologyStandalone();
void testTopologySharded();
void testTopologyFindPrimary();
void testTopologyFindSecondaries();
void testTopologyMarkServerUnknown();
void testTopologyRemoveServer();
void testTopologyDiscoverNewHosts();
void testTopologySetNameMismatch();
void testTopologyMixedUnknownAndKnown();
void testTopologyAllUnknown();
void testTopologyMultipleStandalone();
void testTopologyMixedMongosAndPrimary();
void testTopologyMixedStandaloneAndPrimary();
void testTopologyMultipleStandaloneWithSetName();
void testTopologyMixedMongosAndSecondary();
void testTopologyMixedStandaloneAndSecondary();
void testTopologyTransitions();
void testTopologyReplicaSetNoPrimaryWithSetName();
void testReadPreferencePrimary();
void testReadPreferencePrimaryPreferred();
void testReadPreferenceSecondary();
void testReadPreferenceSecondaryPreferred();
void testReadPreferenceNearest();
void testReadPreferenceWithTags();
void testReplicaSetURIParsing();
void testReplicaSetURIClass();
void testReplicaSetURIToString();
void testReplicaSetURIModification();
void testReplicaSetWithURIObject();
void setUp() override;
void tearDown() override;
static CppUnit::Test* suite();
};
#endif // ReplicaSetTest_INCLUDED

View File

@@ -51,12 +51,13 @@ if(ENABLE_CPPPARSER)
endif()
if(ENABLE_CPPUNIT)
list(APPEND POCO_MODULES
CppUnit.cppm
)
# CppUnit is test-only infrastructure and should not be part of the
# exported Modules interface. Tests can use CppUnit directly without
# it being included in POCO_MODULES or linked to the Modules library.
# Note: CppUnit.cppm exists but is NOT added to POCO_MODULES to avoid
# CMake export errors since Poco::CppUnit is not an exported target.
target_compile_definitions(Modules PUBLIC ENABLE_CPPUNIT)
target_link_libraries(Modules PUBLIC Poco::CppUnit)
# Do NOT link CppUnit to Modules - it's only needed by test executables
endif()
if(ENABLE_DATA)

View File

@@ -17,15 +17,10 @@ module;
#include "Poco/MongoDB/BSONReader.h"
#include "Poco/MongoDB/BSONWriter.h"
#include "Poco/MongoDB/Connection.h"
#include "Poco/MongoDB/Cursor.h"
#include "Poco/MongoDB/Database.h"
#include "Poco/MongoDB/DeleteRequest.h"
#include "Poco/MongoDB/Document.h"
#include "Poco/MongoDB/Element.h"
#include "Poco/MongoDB/GetMoreRequest.h"
#include "Poco/MongoDB/InsertRequest.h"
#include "Poco/MongoDB/JavaScriptCode.h"
#include "Poco/MongoDB/KillCursorsRequest.h"
#include "Poco/MongoDB/Message.h"
#include "Poco/MongoDB/MessageHeader.h"
#include "Poco/MongoDB/MongoDB.h"
@@ -33,50 +28,49 @@ module;
#include "Poco/MongoDB/OpMsgCursor.h"
#include "Poco/MongoDB/OpMsgMessage.h"
#include "Poco/MongoDB/PoolableConnectionFactory.h"
#include "Poco/MongoDB/QueryRequest.h"
#include "Poco/MongoDB/ReadPreference.h"
#include "Poco/MongoDB/RegularExpression.h"
#include "Poco/MongoDB/ReplicaSet.h"
#include "Poco/MongoDB/RequestMessage.h"
#include "Poco/MongoDB/ResponseMessage.h"
#include "Poco/MongoDB/UpdateRequest.h"
#include "Poco/MongoDB/ReplicaSetConnection.h"
#include "Poco/MongoDB/ReplicaSetPoolableConnectionFactory.h"
#include "Poco/MongoDB/ReplicaSetURI.h"
#include "Poco/MongoDB/ServerDescription.h"
#include "Poco/MongoDB/TopologyChangeNotification.h"
#include "Poco/MongoDB/TopologyDescription.h"
#endif
export module Poco.MongoDB;
export namespace Poco::MongoDB {
#ifdef ENABLE_MONGODB
// Main classes
using Poco::MongoDB::Array;
using Poco::MongoDB::BSONReader;
using Poco::MongoDB::BSONTimestamp;
using Poco::MongoDB::BSONWriter;
using Poco::MongoDB::Binary;
using Poco::MongoDB::ConcreteElement;
using Poco::MongoDB::Connection;
using Poco::MongoDB::Cursor;
using Poco::MongoDB::Database;
using Poco::MongoDB::DeleteRequest;
using Poco::MongoDB::Document;
using Poco::MongoDB::Element;
using Poco::MongoDB::ElementFindByName;
using Poco::MongoDB::ElementTraits;
using Poco::MongoDB::GetMoreRequest;
using Poco::MongoDB::InsertRequest;
using Poco::MongoDB::JavaScriptCode;
using Poco::MongoDB::KillCursorsRequest;
using Poco::MongoDB::Message;
using Poco::MongoDB::MessageHeader;
using Poco::MongoDB::ObjectId;
using Poco::MongoDB::OpMsgCursor;
using Poco::MongoDB::OpMsgMessage;
using Poco::MongoDB::PooledConnection;
using Poco::MongoDB::QueryRequest;
using Poco::MongoDB::ReadPreference;
using Poco::MongoDB::RegularExpression;
using Poco::MongoDB::ReplicaSet;
using Poco::MongoDB::RequestMessage;
using Poco::MongoDB::ResponseMessage;
using Poco::MongoDB::UpdateRequest;
using Poco::MongoDB::ReplicaSetConnection;
using Poco::MongoDB::ReplicaSetURI;
using Poco::MongoDB::ServerDescription;
using Poco::MongoDB::TopologyChangeNotification;
using Poco::MongoDB::TopologyDescription;
using Poco::MongoDB::ElementSet;
// Helper classes and structs
using Poco::MongoDB::BSONTimestamp;
using Poco::MongoDB::PooledConnection;
using Poco::MongoDB::PooledReplicaSetConnection;
// Type aliases
using Poco::MongoDB::NullValue;
#endif
}

View File

@@ -8,7 +8,8 @@
// SPDX-License-Identifier: BSL-1.0
//
#include <print>
#include <format>
#include <iostream>
#include <string>
import Poco;
@@ -32,18 +33,18 @@ int main(int argc, char** argv)
LocalDateTime ldt(tzd, dt);
URI uri1("http://www.appinf.com:81/sample?example-query#somewhere");
std::println("Scheme: {}", uri1.getScheme());
std::println("Authority: {}". uri1.getAuthority());
std::println("Path: {}", uri1.getPath());
std::println("Query: {}", uri1.getQuery());
std::println("Fragment: {}", uri1.getFragment());
std::cout << std::format("Scheme: {}\n", uri1.getScheme());
std::cout << std::format("Authority: {}\n", uri1.getAuthority());
std::cout << std::format("Path: {}\n", uri1.getPath());
std::cout << std::format("Query: {}\n", uri1.getQuery());
std::cout << std::format("Fragment: {}\n", uri1.getFragment());
URI uri2;
uri2.setScheme("https");
uri2.setAuthority("www.appinf.com");
uri2.setPath("/another sample");
std::println("{}", uri1.toString());
std::cout << std::format("{}\n", uri1.toString());
return 0;
}