Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package redis++ for openSUSE:Factory checked 
in at 2026-03-30 18:34:04
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/redis++ (Old)
 and      /work/SRC/openSUSE:Factory/.redis++.new.1999 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "redis++"

Mon Mar 30 18:34:04 2026 rev:13 rq:1343672 version:1.3.15

Changes:
--------
--- /work/SRC/openSUSE:Factory/redis++/redis++.changes  2025-05-20 
09:40:15.039139185 +0200
+++ /work/SRC/openSUSE:Factory/.redis++.new.1999/redis++.changes        
2026-03-30 18:38:21.128747114 +0200
@@ -1,0 +2,9 @@
+Sun Mar 29 22:06:09 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.3.15:
+  * Support cmake FetchContent feature
+  * Support more hash commands
+  * Make variant parser more efficient
+  * Fix event loop crash: avoid closing handle opened by hiredis
+
+-------------------------------------------------------------------

Old:
----
  redis++-1.3.14.tar.gz

New:
----
  redis++-1.3.15.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ redis++.spec ++++++
--- /var/tmp/diff_new_pack.fMk2Zh/_old  2026-03-30 18:38:21.624767827 +0200
+++ /var/tmp/diff_new_pack.fMk2Zh/_new  2026-03-30 18:38:21.624767827 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package redis++
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 # Copyright (c) 2025 Andreas Stieger <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -19,7 +19,7 @@
 
 %define sover 1
 Name:           redis++
-Version:        1.3.14
+Version:        1.3.15
 Release:        0
 Summary:        C++ client for Redis
 License:        Apache-2.0

++++++ redis++-1.3.14.tar.gz -> redis++-1.3.15.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/redis-plus-plus-1.3.14/.github/workflows/build-and-test.yml 
new/redis-plus-plus-1.3.15/.github/workflows/build-and-test.yml
--- old/redis-plus-plus-1.3.14/.github/workflows/build-and-test.yml     
2025-03-30 01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/.github/workflows/build-and-test.yml     
2025-07-22 16:58:50.000000000 +0200
@@ -26,6 +26,7 @@
       matrix:
         os: [ubuntu-latest]
         build_type: [Release]
+        redis_version: [7.2.8]
         c_compiler: [gcc, clang]
         include:
           - os: ubuntu-latest
@@ -39,12 +40,21 @@
     - uses: actions/checkout@v4
 
     - name: libuv-dep
-      run: sudo apt-get update && sudo apt-get install -y libuv1-dev wget unzip
+      run: |
+        sudo apt-get update
+        sudo apt-get install -yq libuv1-dev wget unzip
 
     - name: redis-dep
       run: |
-        wget -L https://github.com/redis/redis/archive/refs/tags/7.2.3.zip -O 
redis-7.2.3.zip && unzip redis-7.2.3.zip
-        cd redis-7.2.3 && make -j2 && ./src/redis-server --port 7000 
--cluster-enabled yes --cluster-config-file nodes-7000.conf --daemonize yes && 
./src/redis-server --port 7001 --cluster-enabled yes --cluster-config-file 
nodes-7001.conf --daemonize yes && ./src/redis-server --port 7002 
--cluster-enabled yes --cluster-config-file nodes-7002.conf --daemonize yes && 
./src/redis-cli --cluster-yes --cluster create 127.0.0.1:7000 127.0.0.1:7001 
127.0.0.1:7002 && ./src/redis-server --daemonize yes
+        wget -L 
https://github.com/redis/redis/archive/refs/tags/${{matrix.redis_version}}.zip 
-O redis-${{matrix.redis_version}}.zip
+        unzip redis-${{matrix.redis_version}}.zip
+        cd redis-${{matrix.redis_version}}
+        make -j2
+        ./src/redis-server --port 7000 --cluster-enabled yes 
--cluster-config-file nodes-7000.conf --daemonize yes
+        ./src/redis-server --port 7001 --cluster-enabled yes 
--cluster-config-file nodes-7001.conf --daemonize yes
+        ./src/redis-server --port 7002 --cluster-enabled yes 
--cluster-config-file nodes-7002.conf --daemonize yes
+        sleep 2
+        ./src/redis-cli --cluster-yes --cluster create 127.0.0.1:7000 
127.0.0.1:7001 127.0.0.1:7002 && ./src/redis-server --daemonize yes
 
     - name: hiredis-dep
       run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/CMakeLists.txt 
new/redis-plus-plus-1.3.15/CMakeLists.txt
--- old/redis-plus-plus-1.3.14/CMakeLists.txt   2025-03-30 01:54:24.000000000 
+0100
+++ new/redis-plus-plus-1.3.15/CMakeLists.txt   2025-07-22 16:58:50.000000000 
+0200
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 function(GET_VERSION VERSION_PART VERSION_NUM)
     set(VERSION_REGEX "^const int VERSION_${VERSION_PART} = (.+);$")
@@ -139,52 +139,27 @@
 endif()
 
 # hiredis dependency
-find_package(hiredis QUIET)
-if(hiredis_FOUND)
-    list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS hiredis::hiredis)
-
-    if(REDIS_PLUS_PLUS_USE_TLS)
-        find_package(hiredis_ssl REQUIRED)
-        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS hiredis::hiredis_ssl)
-        find_package(OpenSSL REQUIRED)
-        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${OPENSSL_LIBRARIES})
-    endif()
-else()
-    find_path(HIREDIS_HEADER hiredis)
-    find_library(HIREDIS_LIB hiredis)
-    list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${HIREDIS_LIB})
-
-    if(REDIS_PLUS_PLUS_USE_TLS)
-        find_library(HIREDIS_TLS_LIB hiredis_ssl)
-        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${HIREDIS_TLS_LIB})
-        find_package(OpenSSL REQUIRED)
-        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${OPENSSL_LIBRARIES})
-    endif()
-endif()
+include(${CMAKE_CURRENT_LIST_DIR}/cmake/FindHiredis.cmake)
 
 # Check hiredis features
 message(STATUS "redis-plus-plus check hiredis features")
 if(hiredis_FOUND)
     set(HIREDIS_FEATURE_TEST_INCLUDE ${hiredis_INCLUDE_DIRS})
-    set(HIREDIS_FEATURE_TEST_LIB ${REDIS_PLUS_PLUS_HIREDIS_LIBS})
 else()
     set(HIREDIS_FEATURE_TEST_INCLUDE ${HIREDIS_HEADER})
-    set(HIREDIS_FEATURE_TEST_LIB ${HIREDIS_LIB})
 endif()
 set(HIREDIS_FEATURE_TEST_HEADER 
"${HIREDIS_FEATURE_TEST_INCLUDE}/hiredis/hiredis.h")
 
-include(CheckSymbolExists)
-set(CMAKE_REQUIRED_LIBRARIES_BACK ${CMAKE_REQUIRED_LIBRARIES})
-set(CMAKE_REQUIRED_LIBRARIES ${HIREDIS_FEATURE_TEST_LIB})
+file(READ "${HIREDIS_FEATURE_TEST_HEADER}" HIREDIS_HEADER_CONTENT)
 
-CHECK_SYMBOL_EXISTS(redisEnableKeepAliveWithInterval 
${HIREDIS_FEATURE_TEST_HEADER} 
REDIS_PLUS_PLUS_HAS_redisEnableKeepAliveWithInterval)
+string(FIND "${HIREDIS_HEADER_CONTENT}" redisEnableKeepAliveWithInterval 
redisEnableKeepAliveWithInterval_POS)
+if(${redisEnableKeepAliveWithInterval_POS} GREATER -1)
+    set(REDIS_PLUS_PLUS_HAS_redisEnableKeepAliveWithInterval ON)
+endif()
 
 set(REDIS_PLUS_PLUS_GENERATED_HEADER_DIR 
${CMAKE_CURRENT_BINARY_DIR}/${REDIS_PLUS_PLUS_HEADER_DIR})
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hiredis_features.h.in 
${CMAKE_CURRENT_BINARY_DIR}/${REDIS_PLUS_PLUS_SOURCE_DIR}/hiredis_features.h)
 
-# Restore CMAKE_REQUIRED_LIBRARIES
-set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_BACK})
-
 # Build static library
 option(REDIS_PLUS_PLUS_BUILD_STATIC "Build static library" ON)
 message(STATUS "redis-plus-plus build static library: 
${REDIS_PLUS_PLUS_BUILD_STATIC}")
@@ -273,11 +248,10 @@
 
     if(hiredis_FOUND)
         target_include_directories(${SHARED_LIB} PUBLIC 
$<BUILD_INTERFACE:${hiredis_INCLUDE_DIRS}>)
-        target_link_libraries(${SHARED_LIB} PUBLIC 
${REDIS_PLUS_PLUS_HIREDIS_LIBS})
     else()
         target_include_directories(${SHARED_LIB} PUBLIC 
$<BUILD_INTERFACE:${HIREDIS_HEADER}>)
-        target_link_libraries(${SHARED_LIB} PUBLIC 
${REDIS_PLUS_PLUS_HIREDIS_LIBS})
     endif()
+    target_link_libraries(${SHARED_LIB} PUBLIC ${REDIS_PLUS_PLUS_HIREDIS_LIBS})
 
     if(REDIS_PLUS_PLUS_BUILD_ASYNC)
         target_include_directories(${SHARED_LIB} PUBLIC 
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${REDIS_PLUS_PLUS_ASYNC_FUTURE_HEADER}>)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/README.md 
new/redis-plus-plus-1.3.15/README.md
--- old/redis-plus-plus-1.3.14/README.md        2025-03-30 01:54:24.000000000 
+0100
+++ new/redis-plus-plus-1.3.15/README.md        2025-07-22 16:58:50.000000000 
+0200
@@ -8,7 +8,6 @@
 
 - [Overview](#overview)
     - [Features](#features)
-    - [Branches](#branches)
 - [Installation](#installation)
     - [Install hiredis](#install-hiredis)
     - [Install redis-plus-plus](#install-redis-plus-plus)
@@ -59,17 +58,13 @@
 - Sync and Async interface.
 - Coroutine support.
 
-### Branches
-
-The master branch is the stable branch, which passes all tests. The dev branch 
is unstable. If you want to contribute, please create pull request on dev 
branch.
-
 ## Installation
 
 ### Install hiredis
 
 Since *redis-plus-plus* is based on *hiredis*, you should install *hiredis* 
first. The minimum version requirement for *hiredis* is **v0.12.1**. However, 
[the latest stable release](https://github.com/redis/hiredis/releases) of 
*hiredis* is always recommended.
 
-**NOTE**: You must ensure that there's only 1 version of hiredis is installed. 
Otherwise, you might get some wired problems. Check the following issues for 
example: [issue 135](https://github.com/sewenew/redis-plus-plus/issues/135), 
[issue 140](https://github.com/sewenew/redis-plus-plus/issues/140) and [issue 
158](https://github.com/sewenew/redis-plus-plus/issues/158).
+**NOTE**: You must ensure that there's only 1 version of *hiredis* installed. 
Otherwise, you might get some weird problems. Check the following issues for 
example: [issue 135](https://github.com/sewenew/redis-plus-plus/issues/135), 
[issue 140](https://github.com/sewenew/redis-plus-plus/issues/140) and [issue 
158](https://github.com/sewenew/redis-plus-plus/issues/158).
 
 Normally, you can install *hiredis* with a C++ package manager, and that's the 
easiest way to do it, e.g. `sudo apt-get install libhiredis-dev`. However, if 
you want to install the latest code of hiredis, or a specified version (e.g. 
async support needs hiredis v1.0.0 or later), you can install it from source.
 
@@ -82,7 +77,7 @@
 
 make
 
-make install
+sudo make install
 ```
 
 By default, *hiredis* is installed at */usr/local*. If you want to install 
*hiredis* at non-default location, use the following commands to specify the 
installation path.
@@ -110,7 +105,7 @@
 
 make
 
-make install
+sudo make install
 
 cd ..
 ```
@@ -139,6 +134,10 @@
 
 *redis-plus-plus* builds static library with `-fPIC` option, i.e. Position 
Independent Code, by default. However, you can disable it with 
`-DREDIS_PLUS_PLUS_BUILD_STATIC_WITH_PIC=OFF`.
 
+#### FetchContent
+
+You can also use cmake's 
[FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) 
feature to install redis-plus-plus. Check 
[this](https://github.com/sewenew/redis-plus-plus/pull/639) for an example.
+
 #### Windows Support
 
 Now *hiredis* has Windows support, and since Visual Studio 2017, Visual Studio 
has built-in support for CMake. So *redis-plus-plus* also supports Windows 
platform now. It has been fully tested with Visual Studio 2017 and later on Win 
10. I'm not familiar with Visual Studio environment, and the following doc 
might not be accurate. If you're familiar with the Windows platform, feel free 
to update this doc on how to install *redis-plus-plus* on Windows.
@@ -206,7 +205,7 @@
 
 **NOTE**:
 
-- Since 1.3.0, *redis-puls-plus* is built with C++17 by default, and you 
should also set your application code to be built with C++17. If you still want 
to build the *redis-plus-plus* with C++11, you can set the 
`REDIS_PLUS_PLUS_CXX_STANDARD` cmake option to 11.
+- Since 1.3.0, *redis-plus-plus* is built with C++17 by default, and you 
should also set your application code to be built with C++17. If you still want 
to build the *redis-plus-plus* with C++11, you can set the 
`REDIS_PLUS_PLUS_CXX_STANDARD` cmake option to 11.
 - TLS/SSL support has not been tested on Windows yet.
 
 ##### Build with Visual Studio
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/cmake/FindHiredis.cmake 
new/redis-plus-plus-1.3.15/cmake/FindHiredis.cmake
--- old/redis-plus-plus-1.3.14/cmake/FindHiredis.cmake  1970-01-01 
01:00:00.000000000 +0100
+++ new/redis-plus-plus-1.3.15/cmake/FindHiredis.cmake  2025-07-22 
16:58:50.000000000 +0200
@@ -0,0 +1,38 @@
+find_package(hiredis QUIET)
+if(hiredis_FOUND)
+    list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS hiredis::hiredis)
+
+    if(NOT hiredis_INCLUDE_DIRS)
+        # This can happen when hiredis is included with FetchContent with 
OVERRIDE_FIND_PACKAGE
+        find_path(
+          hiredis_INCLUDE_DIRS
+          hiredis.h
+          PATHS
+            ${CMAKE_BINARY_DIR}/_deps/hiredis
+            ${CMAKE_CURRENT_BINARY_DIR}/_deps/hiredis
+          NO_CACHE
+          REQUIRED
+        )
+
+        # Remove the trailing /hiredis from the include path so that we can 
include hiredis with hiredis/hiredis.h
+        get_filename_component(hiredis_INCLUDE_DIRS "${hiredis_INCLUDE_DIRS}" 
DIRECTORY)
+    endif()
+
+    if(REDIS_PLUS_PLUS_USE_TLS)
+        find_package(hiredis_ssl REQUIRED)
+        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS hiredis::hiredis_ssl)
+        find_package(OpenSSL REQUIRED)
+        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${OPENSSL_LIBRARIES})
+    endif()
+else()
+    find_path(HIREDIS_HEADER hiredis)
+    find_library(HIREDIS_LIB hiredis)
+    list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${HIREDIS_LIB})
+
+    if(REDIS_PLUS_PLUS_USE_TLS)
+        find_library(HIREDIS_TLS_LIB hiredis_ssl)
+        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${HIREDIS_TLS_LIB})
+        find_package(OpenSSL REQUIRED)
+        list(APPEND REDIS_PLUS_PLUS_HIREDIS_LIBS ${OPENSSL_LIBRARIES})
+    endif()
+endif()
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/src/sw/redis++/command.h 
new/redis-plus-plus-1.3.15/src/sw/redis++/command.h
--- old/redis-plus-plus-1.3.14/src/sw/redis++/command.h 2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/command.h 2025-07-22 
16:58:50.000000000 +0200
@@ -792,6 +792,208 @@
     connection.send(args);
 }
 
+template <typename Input>
+void hsetex_keep_ttl_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last,
+        bool keep_ttl,
+        HSetExOption opt) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HSETEX" << key;
+
+    switch (opt) {
+    case HSetExOption::FNX:
+        args << "FNX";
+        break;
+    case HSetExOption::FXX:
+        args << "FXX";
+        break;
+    case HSetExOption::ALWAYS:
+        break;
+    default:
+        throw Error("unknown HSetExOption");
+    }
+
+    if (keep_ttl) {
+        args << "KEEPTTL";
+    }
+
+    auto keys_num = std::distance(first, last);
+    args << "FIELDS" << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void hsetex_ttl_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        HSetExOption opt) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HSETEX" << key;
+
+    switch (opt) {
+    case HSetExOption::FNX:
+        args << "FNX";
+        break;
+    case HSetExOption::FXX:
+        args << "FXX";
+        break;
+    case HSetExOption::ALWAYS:
+        break;
+    default:
+        throw Error("unknown HSetExOption");
+    }
+
+    args << "PX" << ttl.count();
+
+    auto keys_num = std::distance(first, last);
+    args << "FIELDS" << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void hsetex_time_point_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::time_point<std::chrono::system_clock, 
std::chrono::milliseconds> &tp,
+        HSetExOption opt) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HSETEX" << key;
+
+    switch (opt) {
+    case HSetExOption::FNX:
+        args << "FNX";
+        break;
+    case HSetExOption::FXX:
+        args << "FXX";
+        break;
+    case HSetExOption::ALWAYS:
+        break;
+    default:
+        throw Error("unknown HSetExOption");
+    }
+
+    args << "PXAT" << tp.time_since_epoch().count();
+
+    auto keys_num = std::distance(first, last);
+    args << "FIELDS" << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void httl_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HTTL" << key << "FIELDS";
+
+    auto keys_num = std::distance(first, last);
+    args << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void hpttl_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HPTTL" << key << "FIELDS";
+
+    auto keys_num = std::distance(first, last);
+    args << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void hexpiretime_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HEXPIRETIME" << key << "FIELDS";
+
+    auto keys_num = std::distance(first, last);
+    args << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void hpexpiretime_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HPEXPIRETIME" << key << "FIELDS";
+
+    auto keys_num = std::distance(first, last);
+    args << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void hpexpire_range(Connection &connection,
+        const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        HPExpireOption opt) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HPEXPIRE" << key << ttl.count();
+
+    switch (opt) {
+    case HPExpireOption::NX:
+        args << "NX";
+        break;
+    case HPExpireOption::XX:
+        args << "XX";
+        break;
+    case HPExpireOption::GT:
+        args << "GT";
+        break;
+    case HPExpireOption::LT:
+        args << "LT";
+        break;
+    case HPExpireOption::ALWAYS:
+        break;
+    default:
+        throw Error("unknown hpexpire option");
+    }
+
+    auto keys_num = std::distance(first, last);
+    args << "FIELDS" << keys_num << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
 inline void hsetnx(Connection &connection,
                     const StringView &key,
                     const StringView &field,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/redis-plus-plus-1.3.14/src/sw/redis++/command_options.h 
new/redis-plus-plus-1.3.15/src/sw/redis++/command_options.h
--- old/redis-plus-plus-1.3.14/src/sw/redis++/command_options.h 2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/command_options.h 2025-07-22 
16:58:50.000000000 +0200
@@ -216,6 +216,20 @@
 
 std::string to_string(ListWhence whence);
 
+enum class HSetExOption {
+    FNX = 0,
+    FXX,
+    ALWAYS
+};
+
+enum class HPExpireOption {
+    NX = 0,
+    XX,
+    GT,
+    LT,
+    ALWAYS
+};
+
 }
 
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/src/sw/redis++/event_loop.cpp 
new/redis-plus-plus-1.3.15/src/sw/redis++/event_loop.cpp
--- old/redis-plus-plus-1.3.14/src/sw/redis++/event_loop.cpp    2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/event_loop.cpp    2025-07-22 
16:58:50.000000000 +0200
@@ -16,6 +16,8 @@
 
 #include "sw/redis++/event_loop.h"
 #include <cassert>
+#include <chrono>
+#include <thread>
 #include <hiredis/adapters/libuv.h>
 #include "sw/redis++/async_connection.h"
 
@@ -208,26 +210,42 @@
     // How to correctly close an event loop:
     // 
https://stackoverflow.com/questions/25615340/closing-libuv-handles-correctly
     // TODO: do we need to call this? Since we always has 2 async_t handles.
-    if (uv_loop_close(loop) == 0) {
-        delete loop;
+    //if (uv_loop_close(loop) == 0) {
+    //    delete loop;
+    //
+    //    return;
+    //}
 
-        return;
-    }
+    assert(loop->data != nullptr);
 
     uv_walk(loop,
-            [](uv_handle_t *handle, void *) {
+            [](uv_handle_t *handle, void *arg) {
                 if (handle != nullptr) {
-                    // We don't need to release handle's memory in close 
callback,
-                    // since we'll release the memory in EventLoop's 
destructor.
-                    uv_close(handle, nullptr);
+                    auto *event_loop = static_cast<EventLoop *>(arg);
+
+                    assert(event_loop != nullptr);
+
+                    if (handle == reinterpret_cast<uv_handle_t 
*>(event_loop->_event_async.get()) ||
+                            handle == reinterpret_cast<uv_handle_t 
*>(event_loop->_stop_async.get())) {
+                        // We don't need to release handle's memory in close 
callback,
+                        // since we'll release the memory in EventLoop's 
destructor.
+                        uv_close(handle, nullptr);
+                    }
                 }
             },
-            nullptr);
+            loop->data);
 
     // Ensure uv_walk's callback to be called.
     uv_run(loop, UV_RUN_DEFAULT);
 
-    uv_loop_close(loop);
+    for (auto idx = 0; idx < 10; ++idx) {
+        if (uv_loop_close(loop) == 0) {
+            break;
+        }
+
+        // maybe hiredis does not close the handle yet? wait a while.
+        std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    }
 
     delete loop;
 }
@@ -271,7 +289,7 @@
     return uv_async;
 }
 
-EventLoop::LoopUPtr EventLoop::_create_event_loop() const {
+EventLoop::LoopUPtr EventLoop::_create_event_loop() {
     auto *loop = new uv_loop_t;
     auto err = uv_loop_init(loop);
     if (err != 0) {
@@ -279,6 +297,8 @@
         throw Error("failed to initialize event loop: " + _err_msg(err));
     }
 
+    loop->data = this;
+
     return LoopUPtr(loop);
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/src/sw/redis++/event_loop.h 
new/redis-plus-plus-1.3.15/src/sw/redis++/event_loop.h
--- old/redis-plus-plus-1.3.14/src/sw/redis++/event_loop.h      2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/event_loop.h      2025-07-22 
16:58:50.000000000 +0200
@@ -73,7 +73,7 @@
         return uv_strerror(err);
     }
 
-    LoopUPtr _create_event_loop() const;
+    LoopUPtr _create_event_loop();
 
     using UvAsyncUPtr = std::unique_ptr<uv_async_t>;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/src/sw/redis++/redis.h 
new/redis-plus-plus-1.3.15/src/sw/redis++/redis.h
--- old/redis-plus-plus-1.3.14/src/sw/redis++/redis.h   2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/redis.h   2025-07-22 
16:58:50.000000000 +0200
@@ -1675,6 +1675,57 @@
         return hset(key, il.begin(), il.end());
     }
 
+    template <typename Input>
+    auto hsetex(const StringView &key,
+            Input first,
+            Input last,
+            bool keep_ttl = false,
+            HSetExOption opt = HSetExOption::ALWAYS)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value, long long>::type;
+
+    template <typename Input>
+    auto hsetex(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::milliseconds &ttl,
+            HSetExOption opt = HSetExOption::ALWAYS)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value, long long>::type;
+
+    template <typename Input>
+    auto hsetex(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::time_point<std::chrono::system_clock, 
std::chrono::milliseconds> &tp,
+            HSetExOption opt = HSetExOption::ALWAYS)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value, long long>::type;
+
+    template <typename Input, typename Output>
+    void httl(const StringView &key, Input first, Input last, Output output);
+
+    template <typename Input, typename Output>
+    void hpttl(const StringView &key, Input first, Input last, Output output);
+
+    template <typename Input, typename Output>
+    void hexpiretime(const StringView &key, Input first, Input last, Output 
output);
+
+    template <typename Input, typename Output>
+    void hpexpiretime(const StringView &key, Input first, Input last, Output 
output);
+
+    template <typename Input, typename Output>
+    void hpexpire(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::milliseconds &ttl,
+            Output output);
+
+    template <typename Input, typename Output>
+    void hpexpire(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::milliseconds &ttl,
+            HPExpireOption opt,
+            Output output);
+
     /// @brief Set hash field to value, only if the given field does not exist.
     /// @param key Key where the hash is stored.
     /// @param field Field.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/src/sw/redis++/redis.hpp 
new/redis-plus-plus-1.3.15/src/sw/redis++/redis.hpp
--- old/redis-plus-plus-1.3.14/src/sw/redis++/redis.hpp 2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/redis.hpp 2025-07-22 
16:58:50.000000000 +0200
@@ -437,6 +437,114 @@
     return reply::parse<long long>(*reply);
 }
 
+template <typename Input>
+auto Redis::hsetex(const StringView &key,
+        Input first,
+        Input last,
+        bool keep_ttl,
+        HSetExOption opt)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value,
+                                    long long>::type {
+    range_check("HSETEX", first, last);
+
+    auto reply = command(cmd::hsetex_keep_ttl_range<Input>, key, first, last, 
keep_ttl, opt);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+auto Redis::hsetex(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        HSetExOption opt)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value,
+                                    long long>::type {
+    range_check("HSETEX", first, last);
+
+    auto reply = command(cmd::hsetex_ttl_range<Input>, key, first, last, ttl, 
opt);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+auto Redis::hsetex(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::time_point<std::chrono::system_clock, 
std::chrono::milliseconds> &tp,
+        HSetExOption opt)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value,
+                                    long long>::type {
+    range_check("HSETEX", first, last);
+
+    auto reply = command(cmd::hsetex_time_point_range<Input>, key, first, 
last, tp, opt);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void Redis::httl(const StringView &key, Input first, Input last, Output 
output) {
+    range_check("HTTL", first, last);
+
+    auto reply = command(cmd::httl_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::hpttl(const StringView &key, Input first, Input last, Output 
output) {
+    range_check("HPTTL", first, last);
+
+    auto reply = command(cmd::hpttl_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::hexpiretime(const StringView &key, Input first, Input last, Output 
output) {
+    range_check("HEXPIRETIME", first, last);
+
+    auto reply = command(cmd::hexpiretime_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::hpexpiretime(const StringView &key, Input first, Input last, 
Output output) {
+    range_check("HPEXPIRETIME", first, last);
+
+    auto reply = command(cmd::hpexpiretime_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::hpexpire(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        Output output) {
+    range_check("HPEXPIRE", first, last);
+
+    auto reply = command(cmd::hpexpire_range<Input>, key, first, last, ttl, 
HPExpireOption::ALWAYS);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::hpexpire(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        HPExpireOption opt,
+        Output output) {
+    range_check("HPEXPIRE", first, last);
+
+    auto reply = command(cmd::hpexpire_range<Input>, key, first, last, ttl, 
opt);
+
+    reply::to_array(*reply, output);
+}
+
 template <typename Output>
 inline void Redis::hvals(const StringView &key, Output output) {
     auto reply = command(cmd::hvals, key);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/redis-plus-plus-1.3.14/src/sw/redis++/redis_cluster.h 
new/redis-plus-plus-1.3.15/src/sw/redis++/redis_cluster.h
--- old/redis-plus-plus-1.3.14/src/sw/redis++/redis_cluster.h   2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/redis_cluster.h   2025-07-22 
16:58:50.000000000 +0200
@@ -499,6 +499,57 @@
         return hset(key, il.begin(), il.end());
     }
 
+    template <typename Input>
+    auto hsetex(const StringView &key,
+            Input first,
+            Input last,
+            bool keep_ttl = false,
+            HSetExOption opt = HSetExOption::ALWAYS)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value, long long>::type;
+
+    template <typename Input>
+    auto hsetex(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::milliseconds &ttl,
+            HSetExOption opt = HSetExOption::ALWAYS)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value, long long>::type;
+
+    template <typename Input>
+    auto hsetex(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::time_point<std::chrono::system_clock, 
std::chrono::milliseconds> &tp,
+            HSetExOption opt = HSetExOption::ALWAYS)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value, long long>::type;
+
+    template <typename Input, typename Output>
+    void httl(const StringView &key, Input first, Input last, Output output);
+
+    template <typename Input, typename Output>
+    void hpttl(const StringView &key, Input first, Input last, Output output);
+
+    template <typename Input, typename Output>
+    void hexpiretime(const StringView &key, Input first, Input last, Output 
output);
+
+    template <typename Input, typename Output>
+    void hpexpiretime(const StringView &key, Input first, Input last, Output 
output);
+
+    template <typename Input, typename Output>
+    void hpexpire(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::milliseconds &ttl,
+            Output output);
+
+    template <typename Input, typename Output>
+    void hpexpire(const StringView &key,
+            Input first,
+            Input last,
+            const std::chrono::milliseconds &ttl,
+            HPExpireOption opt,
+            Output output);
+
     bool hsetnx(const StringView &key, const StringView &field, const 
StringView &val);
 
     bool hsetnx(const StringView &key, const std::pair<StringView, StringView> 
&item);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/redis-plus-plus-1.3.14/src/sw/redis++/redis_cluster.hpp 
new/redis-plus-plus-1.3.15/src/sw/redis++/redis_cluster.hpp
--- old/redis-plus-plus-1.3.14/src/sw/redis++/redis_cluster.hpp 2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/redis_cluster.hpp 2025-07-22 
16:58:50.000000000 +0200
@@ -407,6 +407,114 @@
     return reply::parse<long long>(*reply);
 }
 
+template <typename Input>
+auto RedisCluster::hsetex(const StringView &key,
+        Input first,
+        Input last,
+        bool keep_ttl,
+        HSetExOption opt)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value,
+                                    long long>::type {
+    range_check("HSETEX", first, last);
+
+    auto reply = command(cmd::hsetex_keep_ttl_range<Input>, key, first, last, 
keep_ttl, opt);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+auto RedisCluster::hsetex(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        HSetExOption opt)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value,
+                                    long long>::type {
+    range_check("HSETEX", first, last);
+
+    auto reply = command(cmd::hsetex_ttl_range<Input>, key, first, last, ttl, 
opt);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+auto RedisCluster::hsetex(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::time_point<std::chrono::system_clock, 
std::chrono::milliseconds> &tp,
+        HSetExOption opt)
+        -> typename std::enable_if<!std::is_convertible<Input, 
StringView>::value,
+                                    long long>::type {
+    range_check("HSETEX", first, last);
+
+    auto reply = command(cmd::hsetex_time_point_range<Input>, key, first, 
last, tp, opt);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::httl(const StringView &key, Input first, Input last, Output 
output) {
+    range_check("HTTL", first, last);
+
+    auto reply = command(cmd::httl_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::hpttl(const StringView &key, Input first, Input last, 
Output output) {
+    range_check("HPTTL", first, last);
+
+    auto reply = command(cmd::hpttl_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::hexpiretime(const StringView &key, Input first, Input last, 
Output output) {
+    range_check("HEXPIRETIME", first, last);
+
+    auto reply = command(cmd::hexpiretime_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::hpexpiretime(const StringView &key, Input first, Input 
last, Output output) {
+    range_check("HPEXPIRETIME", first, last);
+
+    auto reply = command(cmd::hpexpiretime_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::hpexpire(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        Output output) {
+    range_check("HPEXPIRE", first, last);
+
+    auto reply = command(cmd::hpexpire_range<Input>, key, first, last, ttl, 
HPExpireOption::ALWAYS);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::hpexpire(const StringView &key,
+        Input first,
+        Input last,
+        const std::chrono::milliseconds &ttl,
+        HPExpireOption opt,
+        Output output) {
+    range_check("HPEXPIRE", first, last);
+
+    auto reply = command(cmd::hpexpire_range<Input>, key, first, last, ttl, 
opt);
+
+    reply::to_array(*reply, output);
+}
+
 template <typename Output>
 inline void RedisCluster::hvals(const StringView &key, Output output) {
     auto reply = command(cmd::hvals, key);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/src/sw/redis++/reply.h 
new/redis-plus-plus-1.3.15/src/sw/redis++/reply.h
--- old/redis-plus-plus-1.3.14/src/sw/redis++/reply.h   2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/reply.h   2025-07-22 
16:58:50.000000000 +0200
@@ -18,6 +18,7 @@
 #define SEWENEW_REDISPLUSPLUS_REPLY_H
 
 #include <cassert>
+#include <cstdlib>
 #include <string>
 #include <iterator>
 #include <memory>
@@ -301,25 +302,305 @@
                             parse_tuple<Args...>(reply, idx + 1));
 }
 
+template <typename T>
+bool is_parsable(redisReply &reply);
+
+bool is_parsable(ParseTag<std::string>, redisReply &reply);
+
+bool is_parsable(ParseTag<long long>, redisReply &reply);
+
+bool is_parsable(ParseTag<double>, redisReply &reply);
+
+bool is_parsable(ParseTag<bool>, redisReply &reply);
+
+bool is_parsable(ParseTag<void>, redisReply &reply);
+
+template <typename T>
+bool is_parsable(ParseTag<Optional<T>>, redisReply &reply);
+
+template <typename T, typename U>
+bool is_parsable(ParseTag<std::pair<T, U>>, redisReply &reply);
+
+template <typename ...Args>
+bool is_parsable(ParseTag<std::tuple<Args...>>, redisReply &reply);
+
+template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, 
int>::type = 0>
+bool is_parsable(ParseTag<T>, redisReply &reply);
+
+template <typename T, typename 
std::enable_if<IsAssociativeContainer<T>::value, int>::type = 0>
+bool is_parsable(ParseTag<T>, redisReply &reply);
+
 #ifdef REDIS_PLUS_PLUS_HAS_VARIANT
 
+bool is_parsable(ParseTag<Monostate>, redisReply &reply);
+
 template <typename T>
-Variant<T> parse_variant(redisReply &reply) {
-    return parse<T>(reply);
+bool is_parsable(ParseTag<Variant<T>>, redisReply &reply);
+
+template <typename T, typename ...Args>
+auto is_parsable(ParseTag<Variant<T, Args...>>, redisReply &reply)
+    -> typename std::enable_if<sizeof...(Args) != 0, bool>::type;
+
+#endif
+
+template <typename T>
+inline bool is_parsable(redisReply &reply) {
+    return is_parsable(ParseTag<T>{}, reply);
+}
+
+inline bool is_parsable(ParseTag<std::string>, redisReply &reply) {
+#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
+    return is_string(reply) || is_status(reply)
+            || is_verb(reply) || is_bignum(reply);
+#else
+    return is_string(reply) || is_status(reply);
+#endif
+}
+
+inline bool is_parsable(ParseTag<long long>, redisReply &reply) {
+    return is_integer(reply);
+}
+
+inline bool is_parsable(ParseTag<double>, redisReply &reply) {
+#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
+    if (is_double(reply)) {
+        return true;
+    }
+#endif
+
+    if (!is_string(reply) || reply.str == nullptr) {
+        return false;
+    }
+
+    char *end = nullptr;
+    std::strtod(reply.str, &end);
+    return end != reply.str;
+}
+
+inline bool is_parsable(ParseTag<bool>, redisReply &reply) {
+#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
+    if (!is_bool(reply) && !is_integer(reply)) {
+        return false;
+    }
+#else
+    if (!is_integer(reply)) {
+        return false;
+    }
+#endif
+
+    return reply.integer == 0 || reply.integer == 1;
+}
+
+inline bool is_parsable(ParseTag<void>, redisReply &reply) {
+    return is_status(reply);
+}
+
+template <typename T>
+inline bool is_parsable(ParseTag<Optional<T>>, redisReply &reply) {
+    if (is_nil(reply)) {
+        return true;
+    }
+
+    return is_parsable<T>(reply);
+}
+
+template <typename T, typename U>
+inline bool is_parsable(ParseTag<std::pair<T, U>>, redisReply &reply) {
+    if (!is_array(reply)) {
+        return false;
+    }
+
+    if (reply.element == nullptr) {
+        return false;
+    }
+
+    if (reply.elements == 1) {
+        // Nested array reply. Check the first element of the nested array.
+        auto *nested_element = reply.element[0];
+        if (nested_element == nullptr) {
+            return false;
+        }
+
+        return is_parsable(ParseTag<std::pair<T, U>>{}, *nested_element);
+    }
+
+    if (reply.elements != 2) {
+        return false;
+    }
+
+    auto *first = reply.element[0];
+    auto *second = reply.element[1];
+    if (first == nullptr || second == nullptr) {
+        return false;
+    }
+
+    return is_parsable(ParseTag<typename std::decay<T>::type>{}, *first) &&
+        is_parsable(ParseTag<typename std::decay<U>::type>{}, *second);
+}
+
+template <typename T>
+bool is_tuple_parsable(redisReply **reply, std::size_t idx) {
+    assert(reply != nullptr);
+
+    auto *sub_reply = reply[idx];
+    if (sub_reply == nullptr) {
+        return false;
+    }
+
+    return is_parsable<T>(*sub_reply);
 }
 
 template <typename T, typename ...Args>
-auto parse_variant(redisReply &reply) ->
-    typename std::enable_if<sizeof...(Args) != 0, Variant<T, Args...>>::type {
+auto is_tuple_parsable(redisReply **reply, std::size_t idx) ->
+    typename std::enable_if<sizeof...(Args) != 0, bool>::type {
+    assert(reply != nullptr);
+
+    return is_tuple_parsable<T>(reply, idx) &&
+        is_tuple_parsable<Args...>(reply, idx + 1);
+}
+
+template <typename ...Args>
+bool is_parsable(ParseTag<std::tuple<Args...>>, redisReply &reply) {
+    constexpr auto size = sizeof...(Args);
+
+    static_assert(size > 0, "DO NOT support parsing tuple with 0 element");
+
+    if (!is_array(reply)) {
+        return false;
+    }
+
+    if (reply.elements != size) {
+        return false;
+    }
+
+    if (reply.element == nullptr) {
+        return false;
+    }
+
+    return is_tuple_parsable<Args...>(reply.element, 0);
+}
+
+template <typename T>
+bool is_flat_kv_parsable(redisReply &reply) {
+    if (reply.element == nullptr || reply.elements == 0) {
+        // Empty array.
+        return true;
+    }
+
+    if (reply.elements % 2 != 0) {
+        return false;
+    }
+
+    auto *key_reply = reply.element[0];
+    auto *val_reply = reply.element[1];
+    if (key_reply == nullptr || val_reply == nullptr) {
+        return false;
+    }
+
+    using Pair = typename T::value_type;
+    using FirstType = typename std::decay<typename Pair::first_type>::type;
+    using SecondType = typename std::decay<typename Pair::second_type>::type;
+    return is_parsable<FirstType>(*key_reply) &&
+        is_parsable<SecondType>(*val_reply);
+}
+
+template <typename T>
+bool is_container_parsable(std::false_type, redisReply &reply) {
+    if (reply.element == nullptr || reply.elements == 0) {
+        // Empty array.
+        return true;
+    }
+
+    auto *sub_reply = reply.element[0];
+    if (sub_reply == nullptr) {
+        return false;
+    }
+
+    return is_parsable<typename T::value_type>(*sub_reply);
+}
+
+template <typename T>
+bool is_container_parsable(std::true_type, redisReply &reply) {
+    if (is_flat_array(reply)) {
+        return is_flat_kv_parsable<T>(reply);
+    } else {
+        return is_container_parsable<T>(std::false_type{}, reply);
+    }
+}
+
+template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, 
int>::type>
+bool is_parsable(ParseTag<T>, redisReply &reply) {
+#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
+    if (!is_array(reply) && !is_set(reply)) {
+        return false;
+#else
+    if (!is_array(reply)) {
+        return false;
+#endif
+    }
+
+    return is_container_parsable<T>(typename IsKvPair<typename 
T::value_type>::type(), reply);
+}
+
+template <typename T, typename 
std::enable_if<IsAssociativeContainer<T>::value, int>::type>
+bool is_parsable(ParseTag<T>, redisReply &reply) {
+#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
+    if (!is_array(reply) && !is_map(reply) && !is_set(reply)) {
+#else
+    if (!is_array(reply)) {
+#endif
+        return false;
+    }
+
+    return is_container_parsable<T>(std::true_type{}, reply);
+}
+
+#ifdef REDIS_PLUS_PLUS_HAS_VARIANT
+
+template <typename Result, typename T>
+Result parse_variant(redisReply &reply) {
+    if (!is_parsable<T>(reply)) {
+        throw Error("no variant type matches");
+    }
+
     auto return_var = [](auto &&arg) {
-        return Variant<T, Args...>(std::forward<decltype(arg)>(arg));
+        return Result(std::forward<decltype(arg)>(arg));
     };
 
-    try {
-        return std::visit(return_var, parse_variant<T>(reply));
-    } catch (const ProtoError &) {
-        return std::visit(return_var, parse_variant<Args...>(reply));
+    return std::visit(return_var, Variant<T>(parse<T>(reply)));
+}
+
+template <typename Result, typename T, typename ...Args>
+auto parse_variant(redisReply &reply) ->
+    typename std::enable_if<sizeof...(Args) != 0, Result>::type {
+    if (is_parsable<T>(reply)) {
+        auto return_var = [](auto &&arg) {
+            return Result(std::forward<decltype(arg)>(arg));
+        };
+
+        return std::visit(return_var, Variant<T>(parse<T>(reply)));
     }
+
+    return parse_variant<Result, Args...>(reply);
+}
+
+inline bool is_parsable(ParseTag<Monostate>, redisReply &) {
+    return true;
+}
+
+template <typename T>
+bool is_parsable(ParseTag<Variant<T>>, redisReply &reply) {
+    return is_parsable(ParseTag<T>{}, reply);
+}
+
+template <typename T, typename ...Args>
+auto is_parsable(ParseTag<Variant<T, Args...>>, redisReply &reply) ->
+    typename std::enable_if<sizeof...(Args) != 0, bool>::type {
+    if (is_parsable(ParseTag<T>{}, reply)) {
+        return true;
+    }
+
+    return is_parsable(ParseTag<Variant<Args...>>{}, reply);
 }
 
 #endif
@@ -417,7 +698,7 @@
 
 template <typename ...Args>
 Variant<Args...> parse(ParseTag<Variant<Args...>>, redisReply &reply) {
-    return detail::parse_variant<Args...>(reply);
+    return detail::parse_variant<Variant<Args...>, Args...>(reply);
 }
 
 #endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/src/sw/redis++/version.h 
new/redis-plus-plus-1.3.15/src/sw/redis++/version.h
--- old/redis-plus-plus-1.3.14/src/sw/redis++/version.h 2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/src/sw/redis++/version.h 2025-07-22 
16:58:50.000000000 +0200
@@ -23,7 +23,7 @@
 
 const int VERSION_MAJOR = 1;
 const int VERSION_MINOR = 3;
-const int VERSION_PATCH = 14;
+const int VERSION_PATCH = 15;
 
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/redis-plus-plus-1.3.14/test/CMakeLists.txt 
new/redis-plus-plus-1.3.15/test/CMakeLists.txt
--- old/redis-plus-plus-1.3.14/test/CMakeLists.txt      2025-03-30 
01:54:24.000000000 +0100
+++ new/redis-plus-plus-1.3.15/test/CMakeLists.txt      2025-07-22 
16:58:50.000000000 +0200
@@ -1,23 +1,15 @@
 project(test_redis++)
 
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(REDIS_PLUS_PLUS_TEST_SOURCES src/sw/redis++/test_main.cpp)
 
 add_executable(${PROJECT_NAME} ${REDIS_PLUS_PLUS_TEST_SOURCES})
 
 # hiredis dependency
-find_path(HIREDIS_HEADER hiredis REQUIRED)
-target_include_directories(${PROJECT_NAME} PRIVATE 
$<BUILD_INTERFACE:${HIREDIS_HEADER}>)
+include(${CMAKE_CURRENT_LIST_DIR}/../cmake/FindHiredis.cmake)
 
-find_library(TEST_HIREDIS_LIB NAMES libhiredis.a libhiredisd.a)
-if(NOT TEST_HIREDIS_LIB)
-    find_library(TEST_HIREDIS_LIB NAMES libhiredis_static.a 
libhiredis_staticd.a)
-    if(NOT TEST_HIREDIS_LIB)
-        find_library(TEST_HIREDIS_LIB NAMES hiredis hiredisd)
-    endif()
-endif()
-target_link_libraries(${PROJECT_NAME} ${TEST_HIREDIS_LIB})
+target_link_libraries(${PROJECT_NAME} ${REDIS_PLUS_PLUS_HIREDIS_LIBS})
 
 if(REDIS_PLUS_PLUS_USE_TLS)
     find_package(OpenSSL REQUIRED)

Reply via email to