mirror of
https://github.com/pocoproject/poco.git
synced 2026-01-12 00:04:54 +08:00
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:
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/codeql-buildscript.sh
vendored
2
.github/workflows/codeql-buildscript.sh
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -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}}"
|
||||
|
||||
13
.github/workflows/packages-qa.yml
vendored
13
.github/workflows/packages-qa.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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
1560
MongoDB/README-ReplicaSet.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
205
MongoDB/include/Poco/MongoDB/ReadPreference.h
Normal file
205
MongoDB/include/Poco/MongoDB/ReadPreference.h
Normal 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
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
133
MongoDB/include/Poco/MongoDB/ReplicaSetConnection.h
Normal file
133
MongoDB/include/Poco/MongoDB/ReplicaSetConnection.h
Normal 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
|
||||
@@ -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
|
||||
208
MongoDB/include/Poco/MongoDB/ReplicaSetURI.h
Normal file
208
MongoDB/include/Poco/MongoDB/ReplicaSetURI.h
Normal 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
|
||||
245
MongoDB/include/Poco/MongoDB/ServerDescription.h
Normal file
245
MongoDB/include/Poco/MongoDB/ServerDescription.h
Normal 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
|
||||
123
MongoDB/include/Poco/MongoDB/TopologyChangeNotification.h
Normal file
123
MongoDB/include/Poco/MongoDB/TopologyChangeNotification.h
Normal 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
|
||||
168
MongoDB/include/Poco/MongoDB/TopologyDescription.h
Normal file
168
MongoDB/include/Poco/MongoDB/TopologyDescription.h
Normal 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
|
||||
@@ -1 +1,2 @@
|
||||
add_subdirectory(SQLToMongo)
|
||||
add_subdirectory(ReplicaSet)
|
||||
|
||||
17
MongoDB/samples/ReplicaSet/CMakeLists.txt
Normal file
17
MongoDB/samples/ReplicaSet/CMakeLists.txt
Normal 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)
|
||||
429
MongoDB/samples/ReplicaSet/README.md
Normal file
429
MongoDB/samples/ReplicaSet/README.md
Normal 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/)
|
||||
462
MongoDB/samples/ReplicaSet/src/ReplicaSet.cpp
Normal file
462
MongoDB/samples/ReplicaSet/src/ReplicaSet.cpp
Normal 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;
|
||||
}
|
||||
547
MongoDB/samples/ReplicaSet/src/ReplicaSetMonitor.cpp
Normal file
547
MongoDB/samples/ReplicaSet/src/ReplicaSetMonitor.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
184
MongoDB/samples/ReplicaSet/src/URIExample.cpp
Normal file
184
MongoDB/samples/ReplicaSet/src/URIExample.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
328
MongoDB/src/ReadPreference.cpp
Normal file
328
MongoDB/src/ReadPreference.cpp
Normal 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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
334
MongoDB/src/ReplicaSetConnection.cpp
Normal file
334
MongoDB/src/ReplicaSetConnection.cpp
Normal 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
|
||||
495
MongoDB/src/ReplicaSetURI.cpp
Normal file
495
MongoDB/src/ReplicaSetURI.cpp
Normal 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
|
||||
254
MongoDB/src/ServerDescription.cpp
Normal file
254
MongoDB/src/ServerDescription.cpp
Normal 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
|
||||
447
MongoDB/src/TopologyDescription.cpp
Normal file
447
MongoDB/src/TopologyDescription.cpp
Normal 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
|
||||
1275
MongoDB/testsuite/src/BSONTest.cpp
Normal file
1275
MongoDB/testsuite/src/BSONTest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
93
MongoDB/testsuite/src/BSONTest.h
Normal file
93
MongoDB/testsuite/src/BSONTest.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
1696
MongoDB/testsuite/src/ReplicaSetTest.cpp
Normal file
1696
MongoDB/testsuite/src/ReplicaSetTest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
82
MongoDB/testsuite/src/ReplicaSetTest.h
Normal file
82
MongoDB/testsuite/src/ReplicaSetTest.h
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user