First Commit
This commit is contained in:
commit
dbdc5bcc4a
1791 files changed
+489451
No files matched your search
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Minimal code to reproduce the bug.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Environment:**
|
||||
- OS: [e.g. ubuntu]
|
||||
- Compiler: [e.g. gcc 7.3.1, clang 3.9.1]
|
||||
- hiredis version: [e.g. v1.0.0, master]
|
||||
- redis-plus-plus version: [e.g. 1.3.2, master, commit b0a42e]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question
|
||||
title: "[QUESTION]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before Asking A Question**
|
||||
The [README.md file](https://github.com/sewenew/redis-plus-plus/blob/master/README.md) has a detailed introduction with examples on how to use redis-plus-plus, and [redis.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/redis.h) has doxgen style comments for most commands. Before asking a question, please try to find answers with these two files.
|
||||
|
||||
For general questions on redis-plus-plus, you can ask questions at StackOverflow with [redis tag](https://stackoverflow.com/questions/tagged/redis), and normally, you'll get faster response on StackOverflow.
|
||||
|
||||
However, if you still don't get answers, feel free to ask a question here.
|
||||
|
||||
**Describe the problem**
|
||||
A clear and concise description of the problem, with minimal code to reproduce the problem.
|
||||
|
||||
**Environment:**
|
||||
- OS: [e.g. ubuntu]
|
||||
- Compiler: [e.g. gcc 7.3.1, clang 3.9.1]
|
||||
- hiredis version: [e.g. v1.0.0, master]
|
||||
- redis-plus-plus version: [e.g. 1.3.2, master, commit b0a42e]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
@@ -0,0 +1,32 @@
|
||||
language: cpp
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
dist: bionic
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
before_install:
|
||||
- wget -L https://github.com/redis/redis/archive/6.0.8.tar.gz -O redis-6.0.8.tar.gz
|
||||
- tar xfz redis-6.0.8.tar.gz
|
||||
- wget -L https://github.com/redis/hiredis/archive/master.zip -O hiredis-master.zip
|
||||
- unzip hiredis-master.zip
|
||||
- mkdir -p $TRAVIS_BUILD_DIR/install
|
||||
|
||||
install:
|
||||
- cd redis-6.0.8 && make -j2 && cd ..
|
||||
- ./redis-6.0.8/src/redis-server &
|
||||
- cd hiredis-master && make PREFIX=$TRAVIS_BUILD_DIR/install -j2 install && cd ..
|
||||
|
||||
script:
|
||||
- mkdir compile && cd compile && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$TRAVIS_BUILD_DIR/install .. && make -j2 && cd ..
|
||||
- ./compile/test/test_redis++ -h 127.0.0.1 -p 6379
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev
|
||||
@@ -0,0 +1,307 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
set(REDIS_PLUS_PLUS_VERSION "1.3.3")
|
||||
message(STATUS "redis-plus-plus version: ${REDIS_PLUS_PLUS_VERSION}")
|
||||
|
||||
project(redis++ LANGUAGES CXX VERSION ${REDIS_PLUS_PLUS_VERSION})
|
||||
|
||||
set(REDIS_PLUS_PLUS_DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE ${REDIS_PLUS_PLUS_DEFAULT_BUILD_TYPE} CACHE STRING "Set build type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel")
|
||||
endif()
|
||||
message(STATUS "redis-plus-plus build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
set(REDIS_PLUS_PLUS_DEFAULT_CXX_STANDARD 17)
|
||||
if(NOT REDIS_PLUS_PLUS_CXX_STANDARD)
|
||||
set(REDIS_PLUS_PLUS_CXX_STANDARD ${REDIS_PLUS_PLUS_DEFAULT_CXX_STANDARD} CACHE STRING "Set CXX standard" FORCE)
|
||||
set_property(CACHE REDIS_PLUS_PLUS_CXX_STANDARD PROPERTY STRINGS "11" "14" "17" "20")
|
||||
endif()
|
||||
message(STATUS "redis-plus-plus build with CXX standard: c++${REDIS_PLUS_PLUS_CXX_STANDARD}")
|
||||
|
||||
if(NOT WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++${REDIS_PLUS_PLUS_CXX_STANDARD}")
|
||||
else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++${REDIS_PLUS_PLUS_CXX_STANDARD}")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++${REDIS_PLUS_PLUS_CXX_STANDARD}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_ASYNC)
|
||||
if(REDIS_PLUS_PLUS_BUILD_ASYNC STREQUAL "libuv")
|
||||
message(STATUS "redis-plus-plus build async interface with libuv")
|
||||
|
||||
# libuv dependency
|
||||
find_path(REDIS_PLUS_PLUS_ASYNC_LIB_HEADER NAMES uv.h)
|
||||
find_library(REDIS_PLUS_PLUS_ASYNC_LIB uv)
|
||||
else()
|
||||
message(FATAL_ERROR "invalid REDIS_PLUS_PLUS_BUILD_ASYNC")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(REDIS_PLUS_PLUS_SOURCE_DIR src/sw/redis++)
|
||||
|
||||
set(REDIS_PLUS_PLUS_SOURCES
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/command.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/command_options.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/connection.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/connection_pool.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/crc16.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/errors.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/pipeline.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/redis.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/redis_cluster.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/reply.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/sentinel.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/shards.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/shards_pool.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/subscriber.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/transaction.cpp"
|
||||
)
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_ASYNC)
|
||||
list(APPEND REDIS_PLUS_PLUS_SOURCES
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/async_connection.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/async_connection_pool.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/async_redis.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/event_loop.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/async_sentinel.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/async_redis_cluster.cpp"
|
||||
"${REDIS_PLUS_PLUS_SOURCE_DIR}/async_shards_pool.cpp"
|
||||
)
|
||||
|
||||
if(NOT REDIS_PLUS_PLUS_ASYNC_FUTURE)
|
||||
set(REDIS_PLUS_PLUS_ASYNC_FUTURE "std")
|
||||
endif()
|
||||
|
||||
if(REDIS_PLUS_PLUS_ASYNC_FUTURE STREQUAL "std")
|
||||
set(REDIS_PLUS_PLUS_ASYNC_FUTURE_HEADER "${REDIS_PLUS_PLUS_SOURCE_DIR}/future/std")
|
||||
elseif(REDIS_PLUS_PLUS_ASYNC_FUTURE STREQUAL "boost")
|
||||
set(REDIS_PLUS_PLUS_ASYNC_FUTURE_HEADER "${REDIS_PLUS_PLUS_SOURCE_DIR}/future/boost")
|
||||
find_package(Boost REQUIRED COMPONENTS system thread)
|
||||
else()
|
||||
message(FATAL_ERROR "invalid REDIS_PLUS_PLUS_ASYNC_FUTURE")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# cxx utils
|
||||
if(REDIS_PLUS_PLUS_CXX_STANDARD LESS 17)
|
||||
set(CXX_UTILS_DIR "${REDIS_PLUS_PLUS_SOURCE_DIR}/cxx11")
|
||||
else()
|
||||
set(CXX_UTILS_DIR "${REDIS_PLUS_PLUS_SOURCE_DIR}/cxx17")
|
||||
endif()
|
||||
|
||||
# TLS support
|
||||
option(REDIS_PLUS_PLUS_USE_TLS "Build with TLS support" OFF)
|
||||
message(STATUS "redis-plus-plus TLS support: ${REDIS_PLUS_PLUS_USE_TLS}")
|
||||
|
||||
if(REDIS_PLUS_PLUS_USE_TLS)
|
||||
set(TLS_SUB_DIR "${REDIS_PLUS_PLUS_SOURCE_DIR}/tls")
|
||||
|
||||
list(APPEND REDIS_PLUS_PLUS_SOURCES "${TLS_SUB_DIR}/tls.cpp")
|
||||
|
||||
set(REDIS_PLUS_PLUS_DEPENDS "hiredis,hiredis_ssl")
|
||||
else()
|
||||
set(TLS_SUB_DIR "${REDIS_PLUS_PLUS_SOURCE_DIR}/no_tls")
|
||||
|
||||
set(REDIS_PLUS_PLUS_DEPENDS "hiredis")
|
||||
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)
|
||||
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})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 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}")
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_STATIC)
|
||||
set(STATIC_LIB redis++_static)
|
||||
|
||||
add_library(${STATIC_LIB} STATIC ${REDIS_PLUS_PLUS_SOURCES})
|
||||
add_library(redis++::${STATIC_LIB} ALIAS ${STATIC_LIB})
|
||||
|
||||
list(APPEND REDIS_PLUS_PLUS_TARGETS ${STATIC_LIB})
|
||||
|
||||
target_include_directories(${STATIC_LIB} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${REDIS_PLUS_PLUS_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${TLS_SUB_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${CXX_UTILS_DIR}>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
if(hiredis_FOUND)
|
||||
target_link_libraries(${STATIC_LIB} PUBLIC ${REDIS_PLUS_PLUS_HIREDIS_LIBS})
|
||||
else()
|
||||
target_include_directories(${STATIC_LIB} PUBLIC $<BUILD_INTERFACE:${HIREDIS_HEADER}>)
|
||||
endif()
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_ASYNC)
|
||||
target_include_directories(${STATIC_LIB} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${REDIS_PLUS_PLUS_ASYNC_FUTURE_HEADER}>)
|
||||
target_include_directories(${STATIC_LIB} PUBLIC $<BUILD_INTERFACE:${REDIS_PLUS_PLUS_ASYNC_LIB_HEADER}>)
|
||||
if(REDIS_PLUS_PLUS_ASYNC_FUTURE STREQUAL "boost")
|
||||
target_include_directories(${STATIC_LIB} SYSTEM PUBLIC $<BUILD_INTERFACE:${Boost_INCLUDE_DIR}>)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_compile_definitions(${STATIC_LIB} PRIVATE NOMINMAX)
|
||||
set_target_properties(${STATIC_LIB} PROPERTIES CXX_STANDARD ${REDIS_PLUS_PLUS_CXX_STANDARD})
|
||||
set_target_properties(${STATIC_LIB} PROPERTIES OUTPUT_NAME redis++_static)
|
||||
else()
|
||||
target_compile_options(${STATIC_LIB} PRIVATE "-Wall" "-W" "-Werror")
|
||||
set_target_properties(${STATIC_LIB} PROPERTIES OUTPUT_NAME redis++)
|
||||
endif()
|
||||
|
||||
set_target_properties(${STATIC_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1)
|
||||
set_target_properties(${STATIC_LIB} PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
||||
option(REDIS_PLUS_PLUS_BUILD_STATIC_WITH_PIC "Build static library with position independent code" ON)
|
||||
message(STATUS "redis-plus-plus build static library with position independent code: ${REDIS_PLUS_PLUS_BUILD_STATIC_WITH_PIC}")
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_STATIC_WITH_PIC)
|
||||
set_target_properties(${STATIC_LIB} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Build shared library
|
||||
option(REDIS_PLUS_PLUS_BUILD_SHARED "Build shared library" ON)
|
||||
message(STATUS "redis-plus-plus build shared library: ${REDIS_PLUS_PLUS_BUILD_SHARED}")
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_SHARED)
|
||||
set(SHARED_LIB redis++)
|
||||
|
||||
add_library(${SHARED_LIB} SHARED ${REDIS_PLUS_PLUS_SOURCES})
|
||||
add_library(redis++::${SHARED_LIB} ALIAS ${SHARED_LIB})
|
||||
|
||||
list(APPEND REDIS_PLUS_PLUS_TARGETS ${SHARED_LIB})
|
||||
|
||||
target_include_directories(${SHARED_LIB} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${REDIS_PLUS_PLUS_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${TLS_SUB_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${CXX_UTILS_DIR}>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
if(hiredis_FOUND)
|
||||
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()
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_ASYNC)
|
||||
target_include_directories(${SHARED_LIB} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${REDIS_PLUS_PLUS_ASYNC_FUTURE_HEADER}>)
|
||||
target_include_directories(${SHARED_LIB} PUBLIC $<BUILD_INTERFACE:${REDIS_PLUS_PLUS_ASYNC_LIB_HEADER}>)
|
||||
target_link_libraries(${SHARED_LIB} PUBLIC ${REDIS_PLUS_PLUS_ASYNC_LIB})
|
||||
if(REDIS_PLUS_PLUS_ASYNC_FUTURE STREQUAL "boost")
|
||||
target_include_directories(${SHARED_LIB} SYSTEM PUBLIC $<BUILD_INTERFACE:${Boost_INCLUDE_DIR}>)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(${SHARED_LIB} PRIVATE NOMINMAX)
|
||||
set_target_properties(${SHARED_LIB} PROPERTIES CXX_STANDARD ${REDIS_PLUS_PLUS_CXX_STANDARD})
|
||||
set_target_properties(${SHARED_LIB} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
|
||||
else()
|
||||
target_compile_options(${SHARED_LIB} PRIVATE "-Wall" "-W" "-Werror")
|
||||
endif()
|
||||
|
||||
set_target_properties(${SHARED_LIB} PROPERTIES OUTPUT_NAME redis++)
|
||||
set_target_properties(${SHARED_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1)
|
||||
set_target_properties(${SHARED_LIB} PROPERTIES CXX_EXTENSIONS OFF)
|
||||
set_target_properties(${SHARED_LIB} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
set_target_properties(${SHARED_LIB} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
endif()
|
||||
|
||||
option(REDIS_PLUS_PLUS_BUILD_TEST "Build tests for redis++" ON)
|
||||
message(STATUS "redis-plus-plus build test: ${REDIS_PLUS_PLUS_BUILD_TEST}")
|
||||
|
||||
if(REDIS_PLUS_PLUS_BUILD_TEST)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${REDIS_PLUS_PLUS_TARGETS}
|
||||
EXPORT redis++-targets
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
INCLUDES DESTINATION include)
|
||||
|
||||
set(REDIS_PLUS_PLUS_CMAKE_DESTINATION share/cmake/redis++)
|
||||
|
||||
install(EXPORT redis++-targets
|
||||
FILE redis++-targets.cmake
|
||||
NAMESPACE redis++::
|
||||
DESTINATION ${REDIS_PLUS_PLUS_CMAKE_DESTINATION})
|
||||
|
||||
# Install headers.
|
||||
set(HEADER_PATH "sw/redis++")
|
||||
file(GLOB HEADERS "${REDIS_PLUS_PLUS_SOURCE_DIR}/*.h*" "${TLS_SUB_DIR}/*.h" "${CXX_UTILS_DIR}/*.h" "${REDIS_PLUS_PLUS_ASYNC_FUTURE_HEADER}/*.h")
|
||||
if(NOT REDIS_PLUS_PLUS_BUILD_ASYNC)
|
||||
file(GLOB ASYNC_HEADERS "${REDIS_PLUS_PLUS_SOURCE_DIR}/async_*.h" "${REDIS_PLUS_PLUS_SOURCE_DIR}/event_*.h")
|
||||
list(REMOVE_ITEM HEADERS ${ASYNC_HEADERS})
|
||||
endif()
|
||||
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${HEADER_PATH})
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/cmake/redis++-config-version.cmake"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
|
||||
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/redis++-config.cmake.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/cmake/redis++-config.cmake"
|
||||
INSTALL_DESTINATION ${REDIS_PLUS_PLUS_CMAKE_DESTINATION})
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/redis++-config.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/cmake/redis++-config-version.cmake"
|
||||
DESTINATION ${REDIS_PLUS_PLUS_CMAKE_DESTINATION})
|
||||
|
||||
export(EXPORT redis++-targets
|
||||
FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/redis++-targets.cmake"
|
||||
NAMESPACE redis++::)
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/redis++.pc.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/cmake/redis++.pc" @ONLY)
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/redis++.pc"
|
||||
DESTINATION "lib/pkgconfig")
|
||||
|
||||
# All the Debian-specific cpack defines.
|
||||
if(${CMAKE_VERSION} VERSION_GREATER 3.6)
|
||||
SET(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS "ON")
|
||||
endif()
|
||||
if(NOT DEFINED CPACK_DEBIAN_PACKAGE_DEPENDS)
|
||||
SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libstdc++6, libhiredis-dev")
|
||||
endif()
|
||||
SET(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
SET(CPACK_DEBIAN_PACKAGE_VERSION "${REDIS_PLUS_PLUS_VERSION}")
|
||||
SET(CPACK_DEBIAN_PACKAGE_SOURCE "https://github.com/sewenew/redis-plus-plus")
|
||||
message(STATUS "Debian package name: ${CPACK_PACKAGE_FILE_NAME}.deb")
|
||||
|
||||
# All the common cpack defines.
|
||||
if(NOT DEFINED CPACK_PACKAGE_NAME)
|
||||
SET(CPACK_PACKAGE_NAME "libredis++-dev")
|
||||
endif()
|
||||
SET(CPACK_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
|
||||
SET(CPACK_PACKAGE_DESCRIPTION "A pure C++ client for Redis, based on hiredis.")
|
||||
SET(CPACK_PACKAGE_CONTACT "anonymous")
|
||||
SET(CPACK_GENERATOR "DEB")
|
||||
INCLUDE(CPack)
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,12 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
string(REPLACE "," ";" REDIS_PLUS_PLUS_DEPENDS_LIST @REDIS_PLUS_PLUS_DEPENDS@)
|
||||
foreach(REDIS_PLUS_PLUS_DEP ${REDIS_PLUS_PLUS_DEPENDS_LIST})
|
||||
find_dependency(${REDIS_PLUS_PLUS_DEP} REQUIRED)
|
||||
endforeach()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/redis++-targets.cmake")
|
||||
|
||||
check_required_components(redis++)
|
||||
@@ -0,0 +1,12 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: redis++
|
||||
Description: This is a Redis client, based on hiredis and written in C++11. It supports scritpting, pub/sub, pipeline, transaction, Redis Cluster, Redis Sentinel, connection pool, ACL, SSL and thread safety.
|
||||
Version: @PROJECT_VERSION@
|
||||
URL: https://github.com/sewenew/redis-plus-plus
|
||||
Requires: @REDIS_PLUS_PLUS_DEPENDS@
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lredis++
|
||||
+775
@@ -0,0 +1,775 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "command_options.h"
|
||||
#include "command_args.h"
|
||||
#include "command.h"
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class FormattedCommand {
|
||||
public:
|
||||
FormattedCommand(char *data, int len) : _data(data), _size(len) {
|
||||
if (data == nullptr || len < 0) {
|
||||
throw Error("failed to format command");
|
||||
}
|
||||
}
|
||||
|
||||
FormattedCommand(const FormattedCommand &) = delete;
|
||||
FormattedCommand& operator=(const FormattedCommand &) = delete;
|
||||
|
||||
FormattedCommand(FormattedCommand &&that) noexcept {
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
FormattedCommand& operator=(FormattedCommand &&that) noexcept {
|
||||
if (this != &that) {
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~FormattedCommand() noexcept {
|
||||
if (_data != nullptr) {
|
||||
redisFreeCommand(_data);
|
||||
}
|
||||
}
|
||||
|
||||
const char* data() const noexcept {
|
||||
return _data;
|
||||
}
|
||||
|
||||
int size() const noexcept {
|
||||
return _size;
|
||||
}
|
||||
|
||||
private:
|
||||
void _move(FormattedCommand &&that) noexcept {
|
||||
_data = that._data;
|
||||
_size = that._size;
|
||||
that._data = nullptr;
|
||||
that._size = 0;
|
||||
}
|
||||
|
||||
char *_data = nullptr;
|
||||
int _size = 0;
|
||||
};
|
||||
|
||||
namespace fmt {
|
||||
|
||||
template <typename ...Args>
|
||||
FormattedCommand format_cmd(const char *format, Args &&...args) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommand(&data, format, std::forward<Args>(args)...);
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
inline FormattedCommand format_cmd(int argc, const char **argv, const std::size_t *argv_len) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommandArgv(&data, argc, argv, argv_len);
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
inline FormattedCommand format_cmd(CmdArgs &args) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommandArgv(&data, args.size(), args.argv(), args.argv_len());
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
struct SetResultParser {
|
||||
bool operator()(redisReply &reply) const {
|
||||
sw::redis::reply::rewrite_set_reply(reply);
|
||||
return sw::redis::reply::parse<bool>(reply);
|
||||
}
|
||||
};
|
||||
|
||||
// CONNECTION commands.
|
||||
|
||||
inline FormattedCommand echo(const StringView &msg) {
|
||||
return format_cmd("ECHO %b", msg.data(), msg.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ping() {
|
||||
return format_cmd("PING");
|
||||
}
|
||||
|
||||
inline FormattedCommand ping(const StringView &msg) {
|
||||
return format_cmd("PING %b", msg.data(), msg.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand del(const StringView &key) {
|
||||
return format_cmd("DEL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand del_range(Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "DEL" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand exists(const StringView &key) {
|
||||
return format_cmd("EXISTS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand exists_range(Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "EXISTS" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand expire(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("EXPIRE %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand expireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::seconds> &tp) {
|
||||
return format_cmd("EXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pexpire(const StringView &key,
|
||||
const std::chrono::milliseconds &timeout) {
|
||||
return format_cmd("PEXPIRE %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pexpireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds> &tp) {
|
||||
return format_cmd("PEXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pttl(const StringView &key) {
|
||||
return format_cmd("PTTL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rename(const StringView &key, const StringView &newkey) {
|
||||
return format_cmd("RENAME %b %b", key.data(), key.size(), newkey.data(), newkey.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand renamenx(const StringView &key, const StringView &newkey) {
|
||||
return format_cmd("RENAMENX %b %b", key.data(), key.size(), newkey.data(), newkey.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ttl(const StringView &key) {
|
||||
return format_cmd("TTL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand unlink(const StringView &key) {
|
||||
return format_cmd("UNLINK %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand unlink_range(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "UNLINK" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// STRING commands.
|
||||
|
||||
inline FormattedCommand get(const StringView &key) {
|
||||
return format_cmd("GET %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand incr(const StringView &key) {
|
||||
return format_cmd("INCR %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand incrby(const StringView &key, long long increment) {
|
||||
return format_cmd("INCRBY %b %lld", key.data(), key.size(), increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand incrbyfloat(const StringView &key, double increment) {
|
||||
return format_cmd("INCRBYFLOAT %b %f", key.data(), key.size(), increment);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand mget(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MGET" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand mset(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MSET" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand msetnx(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MSETNX" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand set(const StringView &key,
|
||||
const StringView &val,
|
||||
const std::chrono::milliseconds &ttl,
|
||||
UpdateType type) {
|
||||
CmdArgs args;
|
||||
args << "SET" << key << val;
|
||||
|
||||
if (ttl > std::chrono::milliseconds(0)) {
|
||||
args << "PX" << ttl.count();
|
||||
}
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand strlen(const StringView &key) {
|
||||
return format_cmd("STRLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand blpop(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BLPOP %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand blpop_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BLPOP" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand brpop(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BRPOP %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand brpop_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BRPOP" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand brpoplpush(const StringView &source,
|
||||
const StringView &destination,
|
||||
const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BRPOPLPUSH %b %b %lld",
|
||||
source.data(), source.size(),
|
||||
destination.data(), destination.size(),
|
||||
timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand llen(const StringView &key) {
|
||||
return format_cmd("LLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand lpop(const StringView &key) {
|
||||
return format_cmd("LPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand lpush(const StringView &key, const StringView &val) {
|
||||
return format_cmd("LPUSH %b %b", key.data(), key.size(), val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand lpush_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "LPUSH" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand lrange(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("LRANGE %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
inline FormattedCommand lrem(const StringView &key, long long count, const StringView &val) {
|
||||
return format_cmd("LREM %b %lld %b", key.data(), key.size(), count, val.data(), val.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ltrim(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("LTRIM %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
inline FormattedCommand rpop(const StringView &key) {
|
||||
return format_cmd("RPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rpoplpush(const StringView &source, const StringView &destination) {
|
||||
return format_cmd("RPOPLPUSH %b %b",
|
||||
source.data(), source.size(),
|
||||
destination.data(), destination.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rpush(const StringView &key, const StringView &val) {
|
||||
return format_cmd("RPUSH %b %b", key.data(), key.size(), val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand rpush_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "RPUSH" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// HASH commands.
|
||||
|
||||
inline FormattedCommand hdel(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HDEL %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hdel_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HDEL" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hexists(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HEXISTS %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hget(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HGET %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hgetall(const StringView &key) {
|
||||
return format_cmd("HGETALL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hincrby(const StringView &key,
|
||||
const StringView &field,
|
||||
long long increment) {
|
||||
return format_cmd("HINCRBY %b %b %lld",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand hincrbyfloat(const StringView &key,
|
||||
const StringView &field,
|
||||
double increment) {
|
||||
return format_cmd("HINCRBYFLOAT %b %b %f",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand hkeys(const StringView &key) {
|
||||
return format_cmd("HKEYS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hlen(const StringView &key) {
|
||||
return format_cmd("HLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hmget(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HMGET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hmset(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HMSET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hset(const StringView &key,
|
||||
const StringView &field,
|
||||
const StringView &val) {
|
||||
return format_cmd("HSET %b %b %b",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto hset_range(const StringView &key,
|
||||
Input first,
|
||||
Input last)
|
||||
-> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
|
||||
FormattedCommand>::type {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HSET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hvals(const StringView &key) {
|
||||
return format_cmd("HVALS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
// SET commands.
|
||||
|
||||
inline FormattedCommand sadd(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SADD %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand sadd_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "SADD" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand scard(const StringView &key) {
|
||||
return format_cmd("SCARD %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand sismember(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SISMEMBER %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand smembers(const StringView &key) {
|
||||
return format_cmd("SMEMBERS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand spop(const StringView &key) {
|
||||
return format_cmd("SPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand spop(const StringView &key, long long count) {
|
||||
return format_cmd("SPOP %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand srem(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SREM %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand srem_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "SREM" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// SORTED SET commands.
|
||||
|
||||
inline FormattedCommand bzpopmax(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BZPOPMAX %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand bzpopmax_range(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BZPOPMAX" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand bzpopmin(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BZPOPMIN %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand bzpopmin_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BZPOPMIN" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand zadd(const StringView &key,
|
||||
const StringView &member,
|
||||
double score,
|
||||
UpdateType type,
|
||||
bool changed) {
|
||||
CmdArgs args;
|
||||
args << "ZADD" << key;
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
if (changed) {
|
||||
args << "CH";
|
||||
}
|
||||
|
||||
args << score << member;
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand zadd_range(const StringView &key,
|
||||
Input first,
|
||||
Input last,
|
||||
UpdateType type,
|
||||
bool changed) {
|
||||
CmdArgs args;
|
||||
args << "ZADD" << key;
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
if (changed) {
|
||||
args << "CH";
|
||||
}
|
||||
|
||||
while (first != last) {
|
||||
// Swap the <member, score> pair to <score, member> pair.
|
||||
args << first->second << first->first;
|
||||
++first;
|
||||
}
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand zcard(const StringView &key) {
|
||||
return format_cmd("ZCARD %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zcount(const StringView &key, const Interval &interval) {
|
||||
return format_cmd("ZCOUNT %b %s %s",
|
||||
key.data(), key.size(),
|
||||
interval.min().c_str(),
|
||||
interval.max().c_str());
|
||||
}
|
||||
|
||||
inline FormattedCommand zincrby(const StringView &key,
|
||||
double increment,
|
||||
const StringView &member) {
|
||||
return format_cmd("ZINCRBY %b %f %b",
|
||||
key.data(), key.size(),
|
||||
increment,
|
||||
member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zlexcount(const StringView &key,
|
||||
const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZLEXCOUNT %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmax(const StringView &key) {
|
||||
return format_cmd("ZPOPMAX %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmax(const StringView &key, long long count) {
|
||||
return format_cmd("ZPOPMAX %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmin(const StringView &key) {
|
||||
return format_cmd("ZPOPMIN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmin_count(const StringView &key, long long count) {
|
||||
return format_cmd("ZPOPMIN %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrange(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("ZRANGE %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZRANGEBYLEX %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrangebyscore(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrank(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZRANK %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zrem(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZREM %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand zrem_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "ZREM" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zremrangebylex(const StringView &key, const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREMRANGEBYLEX %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zremrangebyrank(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("ZREMRANGEBYRANK %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zremrangebyscore(const StringView &key,
|
||||
const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREMRANGEBYSCORE %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrevrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
max.data(), max.size(),
|
||||
min.data(), min.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrevrank(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZREVRANK %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zscore(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZSCORE %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
// SCRIPTING commands.
|
||||
template <typename Keys, typename Args>
|
||||
FormattedCommand eval(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
CmdArgs args;
|
||||
auto keys_num = std::distance(keys_first, keys_last);
|
||||
|
||||
args << "EVAL" << script << keys_num
|
||||
<< std::make_pair(keys_first, keys_last)
|
||||
<< std::make_pair(args_first, args_last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Keys, typename Args>
|
||||
FormattedCommand evalsha(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
CmdArgs args;
|
||||
auto keys_num = std::distance(keys_first, keys_last);
|
||||
|
||||
args << "EVALSHA" << script << keys_num
|
||||
<< std::make_pair(keys_first, keys_last)
|
||||
<< std::make_pair(args_first, args_last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
File diff suppressed because it is too large.
Load diff
+180
@@ -0,0 +1,180 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class CmdArgs {
|
||||
public:
|
||||
template <typename Arg>
|
||||
CmdArgs& append(Arg &&arg);
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
CmdArgs& append(Arg &&arg, Args &&...args);
|
||||
|
||||
// All overloads of operator<< are for internal use only.
|
||||
CmdArgs& operator<<(const StringView &arg);
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type = 0>
|
||||
CmdArgs& operator<<(T &&arg);
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& operator<<(const std::pair<Iter, Iter> &range);
|
||||
|
||||
template <std::size_t N, typename ...Args>
|
||||
auto operator<<(const std::tuple<Args...> &) ->
|
||||
typename std::enable_if<N == sizeof...(Args), CmdArgs&>::type {
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <std::size_t N = 0, typename ...Args>
|
||||
auto operator<<(const std::tuple<Args...> &arg) ->
|
||||
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type;
|
||||
|
||||
const char** argv() {
|
||||
return _argv.data();
|
||||
}
|
||||
|
||||
const std::size_t* argv_len() {
|
||||
return _argv_len.data();
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return _argv.size();
|
||||
}
|
||||
|
||||
private:
|
||||
// Deep copy.
|
||||
CmdArgs& _append(std::string arg);
|
||||
|
||||
// Shallow copy.
|
||||
CmdArgs& _append(const StringView &arg);
|
||||
|
||||
// Shallow copy.
|
||||
CmdArgs& _append(const char *arg);
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type = 0>
|
||||
CmdArgs& _append(T &&arg) {
|
||||
return operator<<(std::forward<T>(arg));
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& _append(std::true_type, const std::pair<Iter, Iter> &range);
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& _append(std::false_type, const std::pair<Iter, Iter> &range);
|
||||
|
||||
std::vector<const char *> _argv;
|
||||
std::vector<std::size_t> _argv_len;
|
||||
|
||||
std::list<std::string> _args;
|
||||
};
|
||||
|
||||
template <typename Arg>
|
||||
inline CmdArgs& CmdArgs::append(Arg &&arg) {
|
||||
return _append(std::forward<Arg>(arg));
|
||||
}
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) {
|
||||
_append(std::forward<Arg>(arg));
|
||||
|
||||
return append(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::operator<<(const StringView &arg) {
|
||||
_argv.push_back(arg.data());
|
||||
_argv_len.push_back(arg.size());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
inline CmdArgs& CmdArgs::operator<<(const std::pair<Iter, Iter> &range) {
|
||||
return _append(IsKvPair<typename std::decay<decltype(*std::declval<Iter>())>::type>(), range);
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type>
|
||||
inline CmdArgs& CmdArgs::operator<<(T &&arg) {
|
||||
return _append(std::to_string(std::forward<T>(arg)));
|
||||
}
|
||||
|
||||
template <std::size_t N, typename ...Args>
|
||||
auto CmdArgs::operator<<(const std::tuple<Args...> &arg) ->
|
||||
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type {
|
||||
operator<<(std::get<N>(arg));
|
||||
|
||||
return operator<<<N + 1, Args...>(arg);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(std::string arg) {
|
||||
_args.push_back(std::move(arg));
|
||||
return operator<<(_args.back());
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(const StringView &arg) {
|
||||
return operator<<(arg);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(const char *arg) {
|
||||
return operator<<(arg);
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& CmdArgs::_append(std::false_type, const std::pair<Iter, Iter> &range) {
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
while (first != last) {
|
||||
*this << *first;
|
||||
++first;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& CmdArgs::_append(std::true_type, const std::pair<Iter, Iter> &range) {
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
while (first != last) {
|
||||
*this << first->first << first->second;
|
||||
++first;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
|
||||
#include <string>
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum class UpdateType {
|
||||
EXIST,
|
||||
NOT_EXIST,
|
||||
ALWAYS
|
||||
};
|
||||
|
||||
enum class InsertPosition {
|
||||
BEFORE,
|
||||
AFTER
|
||||
};
|
||||
|
||||
enum class BoundType {
|
||||
CLOSED,
|
||||
OPEN,
|
||||
LEFT_OPEN,
|
||||
RIGHT_OPEN
|
||||
};
|
||||
|
||||
// (-inf, +inf)
|
||||
template <typename T>
|
||||
class UnboundedInterval;
|
||||
|
||||
// [min, max], (min, max), (min, max], [min, max)
|
||||
template <typename T>
|
||||
class BoundedInterval;
|
||||
|
||||
// [min, +inf), (min, +inf)
|
||||
template <typename T>
|
||||
class LeftBoundedInterval;
|
||||
|
||||
// (-inf, max], (-inf, max)
|
||||
template <typename T>
|
||||
class RightBoundedInterval;
|
||||
|
||||
template <>
|
||||
class UnboundedInterval<double> {
|
||||
public:
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
class BoundedInterval<double> {
|
||||
public:
|
||||
BoundedInterval(double min, double max, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class LeftBoundedInterval<double> {
|
||||
public:
|
||||
LeftBoundedInterval(double min, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const;
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
};
|
||||
|
||||
template <>
|
||||
class RightBoundedInterval<double> {
|
||||
public:
|
||||
RightBoundedInterval(double max, BoundType type);
|
||||
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class UnboundedInterval<std::string> {
|
||||
public:
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
class BoundedInterval<std::string> {
|
||||
public:
|
||||
BoundedInterval(const std::string &min, const std::string &max, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class LeftBoundedInterval<std::string> {
|
||||
public:
|
||||
LeftBoundedInterval(const std::string &min, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const;
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
};
|
||||
|
||||
template <>
|
||||
class RightBoundedInterval<std::string> {
|
||||
public:
|
||||
RightBoundedInterval(const std::string &max, BoundType type);
|
||||
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
struct LimitOptions {
|
||||
long long offset = 0;
|
||||
long long count = -1;
|
||||
};
|
||||
|
||||
enum class Aggregation {
|
||||
SUM,
|
||||
MIN,
|
||||
MAX
|
||||
};
|
||||
|
||||
enum class BitOp {
|
||||
AND,
|
||||
OR,
|
||||
XOR,
|
||||
NOT
|
||||
};
|
||||
|
||||
enum class GeoUnit {
|
||||
M,
|
||||
KM,
|
||||
MI,
|
||||
FT
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct WithCoord : TupleWithType<std::pair<double, double>, T> {};
|
||||
|
||||
template <typename T>
|
||||
struct WithDist : TupleWithType<double, T> {};
|
||||
|
||||
template <typename T>
|
||||
struct WithHash : TupleWithType<long long, T> {};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
@@ -0,0 +1,237 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "errors.h"
|
||||
#include "reply.h"
|
||||
#include "utils.h"
|
||||
#include "tls.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum class ConnectionType {
|
||||
TCP = 0,
|
||||
UNIX
|
||||
};
|
||||
|
||||
struct ConnectionOptions {
|
||||
public:
|
||||
ConnectionOptions() = default;
|
||||
|
||||
explicit ConnectionOptions(const std::string &uri);
|
||||
|
||||
ConnectionOptions(const ConnectionOptions &) = default;
|
||||
ConnectionOptions& operator=(const ConnectionOptions &) = default;
|
||||
|
||||
ConnectionOptions(ConnectionOptions &&) = default;
|
||||
ConnectionOptions& operator=(ConnectionOptions &&) = default;
|
||||
|
||||
~ConnectionOptions() = default;
|
||||
|
||||
ConnectionType type = ConnectionType::TCP;
|
||||
|
||||
std::string host;
|
||||
|
||||
int port = 6379;
|
||||
|
||||
std::string path;
|
||||
|
||||
std::string user = "default";
|
||||
|
||||
std::string password;
|
||||
|
||||
int db = 0;
|
||||
|
||||
bool keep_alive = false;
|
||||
|
||||
std::chrono::milliseconds connect_timeout{0};
|
||||
|
||||
std::chrono::milliseconds socket_timeout{0};
|
||||
|
||||
tls::TlsOptions tls;
|
||||
|
||||
// `readonly` is only used for reading from a slave node in Redis Cluster mode.
|
||||
// Client code should never manually set/get it. This member might be removed in the future.
|
||||
bool readonly = false;
|
||||
|
||||
private:
|
||||
ConnectionOptions _parse_uri(const std::string &uri) const;
|
||||
|
||||
auto _split_uri(const std::string &uri) const
|
||||
-> std::tuple<std::string, std::string, std::string>;
|
||||
|
||||
auto _split_path(const std::string &path) const
|
||||
-> std::tuple<std::string, int, std::string>;
|
||||
|
||||
void _parse_parameters(const std::string ¶meter_string,
|
||||
ConnectionOptions &opts) const;
|
||||
|
||||
void _set_option(const std::string &key, const std::string &val, ConnectionOptions &opts) const;
|
||||
|
||||
bool _parse_bool_option(const std::string &str) const;
|
||||
|
||||
std::chrono::milliseconds _parse_timeout_option(const std::string &str) const;
|
||||
|
||||
std::vector<std::string> _split(const std::string &str, const std::string &delimiter) const;
|
||||
|
||||
void _set_auth_opts(const std::string &auth, ConnectionOptions &opts) const;
|
||||
|
||||
void _set_tcp_opts(const std::string &path, ConnectionOptions &opts) const;
|
||||
|
||||
void _set_unix_opts(const std::string &path, ConnectionOptions &opts) const;
|
||||
};
|
||||
|
||||
class CmdArgs;
|
||||
|
||||
class Connection {
|
||||
public:
|
||||
explicit Connection(const ConnectionOptions &opts);
|
||||
|
||||
Connection(const Connection &) = delete;
|
||||
Connection& operator=(const Connection &) = delete;
|
||||
|
||||
Connection(Connection &&) = default;
|
||||
Connection& operator=(Connection &&) = default;
|
||||
|
||||
~Connection() = default;
|
||||
|
||||
// Check if the connection is broken. Client needs to do this check
|
||||
// before sending some command to the connection. If it's broken,
|
||||
// client needs to reconnect it.
|
||||
bool broken() const noexcept {
|
||||
return _ctx->err != REDIS_OK;
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
_ctx->err = 0;
|
||||
}
|
||||
|
||||
void invalidate() noexcept {
|
||||
_ctx->err = REDIS_ERR;
|
||||
}
|
||||
|
||||
void reconnect();
|
||||
|
||||
auto create_time() const
|
||||
-> std::chrono::time_point<std::chrono::steady_clock> {
|
||||
return _create_time;
|
||||
}
|
||||
|
||||
auto last_active() const
|
||||
-> std::chrono::time_point<std::chrono::steady_clock> {
|
||||
return _last_active;
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
void send(const char *format, Args &&...args);
|
||||
|
||||
void send(int argc, const char **argv, const std::size_t *argv_len);
|
||||
|
||||
void send(CmdArgs &args);
|
||||
|
||||
ReplyUPtr recv(bool handle_error_reply = true);
|
||||
|
||||
const ConnectionOptions& options() const {
|
||||
return _opts;
|
||||
}
|
||||
|
||||
friend void swap(Connection &lhs, Connection &rhs) noexcept;
|
||||
|
||||
private:
|
||||
class Connector;
|
||||
|
||||
struct ContextDeleter {
|
||||
void operator()(redisContext *context) const {
|
||||
if (context != nullptr) {
|
||||
redisFree(context);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
using ContextUPtr = std::unique_ptr<redisContext, ContextDeleter>;
|
||||
|
||||
void _set_options();
|
||||
|
||||
void _auth();
|
||||
|
||||
void _select_db();
|
||||
|
||||
void _enable_readonly();
|
||||
|
||||
redisContext* _context();
|
||||
|
||||
ContextUPtr _ctx;
|
||||
|
||||
// The time that the connection is created.
|
||||
std::chrono::time_point<std::chrono::steady_clock> _create_time{};
|
||||
|
||||
// The time that the connection is created or the time that
|
||||
// the connection is recently used, i.e. `_context()` is called.
|
||||
std::chrono::time_point<std::chrono::steady_clock> _last_active{};
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
// TODO: define _tls_ctx before _ctx
|
||||
tls::TlsContextUPtr _tls_ctx;
|
||||
};
|
||||
|
||||
using ConnectionSPtr = std::shared_ptr<Connection>;
|
||||
|
||||
enum class Role {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// Inline implementaions.
|
||||
|
||||
template <typename ...Args>
|
||||
inline void Connection::send(const char *format, Args &&...args) {
|
||||
auto ctx = _context();
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
if (redisAppendCommand(ctx,
|
||||
format,
|
||||
std::forward<Args>(args)...) != REDIS_OK) {
|
||||
throw_error(*ctx, "Failed to send command");
|
||||
}
|
||||
|
||||
assert(!broken());
|
||||
}
|
||||
|
||||
inline redisContext* Connection::_context() {
|
||||
_last_active = std::chrono::steady_clock::now();
|
||||
|
||||
return _ctx.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include "connection.h"
|
||||
#include "sentinel.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
struct ConnectionPoolOptions {
|
||||
// Max number of connections, including both in-use and idle ones.
|
||||
std::size_t size = 1;
|
||||
|
||||
// Max time to wait for a connection. 0ms means client waits forever.
|
||||
std::chrono::milliseconds wait_timeout{0};
|
||||
|
||||
// Max lifetime of a connection. 0ms means we never expire the connection.
|
||||
std::chrono::milliseconds connection_lifetime{0};
|
||||
|
||||
// Max idle time of a connection. 0ms means we never expire the connection.
|
||||
std::chrono::milliseconds connection_idle_time{0};
|
||||
};
|
||||
|
||||
class ConnectionPool {
|
||||
public:
|
||||
ConnectionPool(const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
ConnectionPool(SimpleSentinel sentinel,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
ConnectionPool() = default;
|
||||
|
||||
ConnectionPool(ConnectionPool &&that);
|
||||
ConnectionPool& operator=(ConnectionPool &&that);
|
||||
|
||||
ConnectionPool(const ConnectionPool &) = delete;
|
||||
ConnectionPool& operator=(const ConnectionPool &) = delete;
|
||||
|
||||
~ConnectionPool() = default;
|
||||
|
||||
// Fetch a connection from pool.
|
||||
Connection fetch();
|
||||
|
||||
ConnectionOptions connection_options();
|
||||
|
||||
void release(Connection connection);
|
||||
|
||||
// Create a new connection.
|
||||
Connection create();
|
||||
|
||||
ConnectionPool clone();
|
||||
|
||||
private:
|
||||
void _move(ConnectionPool &&that);
|
||||
|
||||
// NOT thread-safe
|
||||
Connection _create();
|
||||
|
||||
Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked);
|
||||
|
||||
Connection _fetch();
|
||||
|
||||
void _wait_for_connection(std::unique_lock<std::mutex> &lock);
|
||||
|
||||
bool _need_reconnect(const Connection &connection,
|
||||
const std::chrono::milliseconds &connection_lifetime,
|
||||
const std::chrono::milliseconds &connection_idle_time) const;
|
||||
|
||||
void _update_connection_opts(const std::string &host, int port) {
|
||||
_opts.host = host;
|
||||
_opts.port = port;
|
||||
}
|
||||
|
||||
bool _role_changed(const ConnectionOptions &opts) const {
|
||||
return opts.port != _opts.port || opts.host != _opts.host;
|
||||
}
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
ConnectionPoolOptions _pool_opts;
|
||||
|
||||
std::deque<Connection> _pool;
|
||||
|
||||
std::size_t _used_connections = 0;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
std::condition_variable _cv;
|
||||
|
||||
SimpleSentinel _sentinel;
|
||||
};
|
||||
|
||||
using ConnectionPoolSPtr = std::shared_ptr<ConnectionPool>;
|
||||
|
||||
class SafeConnection {
|
||||
public:
|
||||
explicit SafeConnection(ConnectionPool &pool) : _pool(pool), _connection(_pool.fetch()) {
|
||||
assert(!_connection.broken());
|
||||
}
|
||||
|
||||
SafeConnection(const SafeConnection &) = delete;
|
||||
SafeConnection& operator=(const SafeConnection &) = delete;
|
||||
|
||||
SafeConnection(SafeConnection &&) = delete;
|
||||
SafeConnection& operator=(SafeConnection &&) = delete;
|
||||
|
||||
~SafeConnection() {
|
||||
_pool.release(std::move(_connection));
|
||||
}
|
||||
|
||||
Connection& connection() {
|
||||
return _connection;
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionPool &_pool;
|
||||
Connection _connection;
|
||||
};
|
||||
|
||||
// NOTE: This class is similar to `SafeConnection`.
|
||||
// The difference is that `SafeConnection` tries to avoid copying a std::shared_ptr.
|
||||
class GuardedConnection {
|
||||
public:
|
||||
explicit GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool),
|
||||
_connection(_pool->fetch()) {
|
||||
assert(!_connection.broken());
|
||||
}
|
||||
|
||||
GuardedConnection(const GuardedConnection &) = delete;
|
||||
GuardedConnection& operator=(const GuardedConnection &) = delete;
|
||||
|
||||
GuardedConnection(GuardedConnection &&) = default;
|
||||
GuardedConnection& operator=(GuardedConnection &&) = default;
|
||||
|
||||
~GuardedConnection() {
|
||||
// If `GuardedConnection` has been moved, `_pool` will be nullptr.
|
||||
if (_pool) {
|
||||
_pool->release(std::move(_connection));
|
||||
}
|
||||
}
|
||||
|
||||
Connection& connection() {
|
||||
return _connection;
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionPoolSPtr _pool;
|
||||
Connection _connection;
|
||||
};
|
||||
|
||||
using GuardedConnectionSPtr = std::shared_ptr<GuardedConnection>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
@@ -0,0 +1,46 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#define REDIS_PLUS_PLUS_HAS_OPTIONAL
|
||||
|
||||
#define REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
using StringView = std::string_view;
|
||||
|
||||
template <typename T>
|
||||
using Optional = std::optional<T>;
|
||||
|
||||
template <typename ...Args>
|
||||
using Variant = std::variant<Args...>;
|
||||
|
||||
using Monostate = std::monostate;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
@@ -0,0 +1,166 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <hiredis/hiredis.h>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum ReplyErrorType {
|
||||
ERR,
|
||||
MOVED,
|
||||
ASK
|
||||
};
|
||||
|
||||
class Error : public std::exception {
|
||||
public:
|
||||
explicit Error(const std::string &msg) : _msg(msg) {}
|
||||
|
||||
Error(const Error &) = default;
|
||||
Error& operator=(const Error &) = default;
|
||||
|
||||
Error(Error &&) = default;
|
||||
Error& operator=(Error &&) = default;
|
||||
|
||||
virtual ~Error() override = default;
|
||||
|
||||
virtual const char* what() const noexcept override {
|
||||
return _msg.data();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _msg;
|
||||
};
|
||||
|
||||
class IoError : public Error {
|
||||
public:
|
||||
explicit IoError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
IoError(const IoError &) = default;
|
||||
IoError& operator=(const IoError &) = default;
|
||||
|
||||
IoError(IoError &&) = default;
|
||||
IoError& operator=(IoError &&) = default;
|
||||
|
||||
virtual ~IoError() override = default;
|
||||
};
|
||||
|
||||
class TimeoutError : public IoError {
|
||||
public:
|
||||
explicit TimeoutError(const std::string &msg) : IoError(msg) {}
|
||||
|
||||
TimeoutError(const TimeoutError &) = default;
|
||||
TimeoutError& operator=(const TimeoutError &) = default;
|
||||
|
||||
TimeoutError(TimeoutError &&) = default;
|
||||
TimeoutError& operator=(TimeoutError &&) = default;
|
||||
|
||||
virtual ~TimeoutError() override = default;
|
||||
};
|
||||
|
||||
class ClosedError : public Error {
|
||||
public:
|
||||
explicit ClosedError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ClosedError(const ClosedError &) = default;
|
||||
ClosedError& operator=(const ClosedError &) = default;
|
||||
|
||||
ClosedError(ClosedError &&) = default;
|
||||
ClosedError& operator=(ClosedError &&) = default;
|
||||
|
||||
virtual ~ClosedError() override = default;
|
||||
};
|
||||
|
||||
class ProtoError : public Error {
|
||||
public:
|
||||
explicit ProtoError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ProtoError(const ProtoError &) = default;
|
||||
ProtoError& operator=(const ProtoError &) = default;
|
||||
|
||||
ProtoError(ProtoError &&) = default;
|
||||
ProtoError& operator=(ProtoError &&) = default;
|
||||
|
||||
virtual ~ProtoError() override = default;
|
||||
};
|
||||
|
||||
class OomError : public Error {
|
||||
public:
|
||||
explicit OomError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
OomError(const OomError &) = default;
|
||||
OomError& operator=(const OomError &) = default;
|
||||
|
||||
OomError(OomError &&) = default;
|
||||
OomError& operator=(OomError &&) = default;
|
||||
|
||||
virtual ~OomError() override = default;
|
||||
};
|
||||
|
||||
class ReplyError : public Error {
|
||||
public:
|
||||
explicit ReplyError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ReplyError(const ReplyError &) = default;
|
||||
ReplyError& operator=(const ReplyError &) = default;
|
||||
|
||||
ReplyError(ReplyError &&) = default;
|
||||
ReplyError& operator=(ReplyError &&) = default;
|
||||
|
||||
virtual ~ReplyError() override = default;
|
||||
};
|
||||
|
||||
class WatchError : public Error {
|
||||
public:
|
||||
explicit WatchError() : Error("Watched key has been modified") {}
|
||||
|
||||
WatchError(const WatchError &) = default;
|
||||
WatchError& operator=(const WatchError &) = default;
|
||||
|
||||
WatchError(WatchError &&) = default;
|
||||
WatchError& operator=(WatchError &&) = default;
|
||||
|
||||
virtual ~WatchError() override = default;
|
||||
};
|
||||
|
||||
|
||||
// MovedError and AskError are defined in shards.h
|
||||
class MovedError;
|
||||
|
||||
class AskError;
|
||||
|
||||
void throw_error(const redisContext &context, const std::string &err_info);
|
||||
|
||||
void throw_error(const redisReply &reply);
|
||||
|
||||
template <typename Input>
|
||||
inline void range_check(const char *cmd, Input first, Input last) {
|
||||
if (first == last) {
|
||||
throw Error(std::string(cmd) + ": no key specified");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
@@ -0,0 +1,49 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H
|
||||
#define SEWENEW_REDISPLUSPLUS_PIPELINE_H
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "connection.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class PipelineImpl {
|
||||
public:
|
||||
template <typename Cmd, typename ...Args>
|
||||
void command(Connection &connection, Cmd cmd, Args &&...args) {
|
||||
assert(!connection.broken());
|
||||
|
||||
cmd(connection, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
void discard(Connection &connection, std::size_t /*cmd_num*/) {
|
||||
// Reconnect to Redis to discard all commands.
|
||||
connection.reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H
|
||||
+2013
File diff suppressed because it is too large.
Load diff
+275
@@ -0,0 +1,275 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
|
||||
#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
template <typename Impl>
|
||||
template <typename ...Args>
|
||||
QueuedRedis<Impl>::QueuedRedis(const ConnectionPoolSPtr &pool,
|
||||
bool new_connection,
|
||||
Args &&...args) :
|
||||
_new_connection(new_connection),
|
||||
_impl(std::forward<Args>(args)...) {
|
||||
assert(pool);
|
||||
|
||||
if (_new_connection) {
|
||||
_connection_pool = std::make_shared<ConnectionPool>(pool->clone());
|
||||
} else {
|
||||
// Create a connection from the origin pool.
|
||||
_connection_pool = pool;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
QueuedRedis<Impl>::~QueuedRedis() {
|
||||
try {
|
||||
_clean_up();
|
||||
} catch (const Error &e) {
|
||||
// Ensure the destructor does not throw
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
Redis QueuedRedis<Impl>::redis() {
|
||||
_sanity_check();
|
||||
|
||||
assert(_guarded_connection);
|
||||
|
||||
return Redis(_guarded_connection);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename Cmd, typename ...Args>
|
||||
auto QueuedRedis<Impl>::command(Cmd cmd, Args &&...args)
|
||||
-> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value,
|
||||
QueuedRedis<Impl>&>::type {
|
||||
try {
|
||||
_sanity_check();
|
||||
|
||||
_impl.command(_connection(), cmd, std::forward<Args>(args)...);
|
||||
|
||||
++_cmd_num;
|
||||
} catch (const Error &e) {
|
||||
_invalidate();
|
||||
throw;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename ...Args>
|
||||
QueuedRedis<Impl>& QueuedRedis<Impl>::command(const StringView &cmd_name, Args &&...args) {
|
||||
auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) {
|
||||
CmdArgs cmd_args;
|
||||
cmd_args.append(cmd_name, std::forward<Args>(args)...);
|
||||
connection.send(cmd_args);
|
||||
};
|
||||
|
||||
return command(cmd, cmd_name, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename Input>
|
||||
auto QueuedRedis<Impl>::command(Input first, Input last)
|
||||
-> typename std::enable_if<IsIter<Input>::value, QueuedRedis<Impl>&>::type {
|
||||
if (first == last) {
|
||||
throw Error("command: empty range");
|
||||
}
|
||||
|
||||
auto cmd = [](Connection &connection, Input first, Input last) {
|
||||
CmdArgs cmd_args;
|
||||
while (first != last) {
|
||||
cmd_args.append(*first);
|
||||
++first;
|
||||
}
|
||||
connection.send(cmd_args);
|
||||
};
|
||||
|
||||
return command(cmd, first, last);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
QueuedReplies QueuedRedis<Impl>::exec() {
|
||||
try {
|
||||
_sanity_check();
|
||||
|
||||
auto replies = _impl.exec(_connection(), _cmd_num);
|
||||
|
||||
_rewrite_replies(replies);
|
||||
|
||||
_reset();
|
||||
|
||||
return QueuedReplies(std::move(replies));
|
||||
} catch (const WatchError &e) {
|
||||
// In this case, we only clear some states and keep the connection,
|
||||
// so that user can retry the transaction.
|
||||
_reset(false);
|
||||
throw;
|
||||
} catch (const Error &e) {
|
||||
_invalidate();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::discard() {
|
||||
try {
|
||||
_sanity_check();
|
||||
|
||||
_impl.discard(_connection(), _cmd_num);
|
||||
|
||||
_reset();
|
||||
} catch (const Error &e) {
|
||||
_invalidate();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
Connection& QueuedRedis<Impl>::_connection() {
|
||||
assert(_valid);
|
||||
|
||||
if (!_guarded_connection) {
|
||||
_guarded_connection = std::make_shared<GuardedConnection>(_connection_pool);
|
||||
}
|
||||
|
||||
return _guarded_connection->connection();
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_sanity_check() {
|
||||
if (!_valid) {
|
||||
throw Error("Not in valid state");
|
||||
}
|
||||
|
||||
if (_connection().broken()) {
|
||||
throw Error("Connection is broken");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
inline void QueuedRedis<Impl>::_reset(bool reset_connection) {
|
||||
if (reset_connection && !_new_connection) {
|
||||
_return_connection();
|
||||
}
|
||||
|
||||
_cmd_num = 0;
|
||||
|
||||
_set_cmd_indexes.clear();
|
||||
|
||||
_empty_array_cmd_indexes.clear();
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
inline void QueuedRedis<Impl>::_return_connection() {
|
||||
if (_guarded_connection.use_count() == 1) {
|
||||
// If no one else holding the connection, return it back to pool.
|
||||
// Instead, if some other `Redis` object holds the connection,
|
||||
// e.g. `auto redis = transaction.redis();`, we cannot return the connection.
|
||||
_guarded_connection.reset();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_invalidate() {
|
||||
_valid = false;
|
||||
|
||||
_clean_up();
|
||||
|
||||
_reset();
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_clean_up() {
|
||||
if (_guarded_connection && !_new_connection) {
|
||||
// Something bad happened, we need to close the current connection
|
||||
// before returning it back to pool.
|
||||
_guarded_connection->connection().invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_rewrite_replies(std::vector<ReplyUPtr> &replies) const {
|
||||
_rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies);
|
||||
|
||||
_rewrite_replies(_empty_array_cmd_indexes, reply::rewrite_empty_array_reply, replies);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename Func>
|
||||
void QueuedRedis<Impl>::_rewrite_replies(const std::vector<std::size_t> &indexes,
|
||||
Func rewriter,
|
||||
std::vector<ReplyUPtr> &replies) const {
|
||||
for (auto idx : indexes) {
|
||||
assert(idx < replies.size());
|
||||
|
||||
auto &reply = replies[idx];
|
||||
|
||||
assert(reply);
|
||||
|
||||
rewriter(*reply);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::size_t QueuedReplies::size() const {
|
||||
return _replies.size();
|
||||
}
|
||||
|
||||
inline redisReply& QueuedReplies::get(std::size_t idx) {
|
||||
_index_check(idx);
|
||||
|
||||
auto &reply = _replies[idx];
|
||||
|
||||
assert(reply);
|
||||
|
||||
if (reply::is_error(*reply)) {
|
||||
throw_error(*reply);
|
||||
}
|
||||
|
||||
return *reply;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
inline Result QueuedReplies::get(std::size_t idx) {
|
||||
auto &reply = get(idx);
|
||||
|
||||
return reply::parse<Result>(reply);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
inline void QueuedReplies::get(std::size_t idx, Output output) {
|
||||
auto &reply = get(idx);
|
||||
|
||||
reply::to_array(reply, output);
|
||||
}
|
||||
|
||||
inline void QueuedReplies::_index_check(std::size_t idx) const {
|
||||
if (idx >= size()) {
|
||||
throw Error("Out of range");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
|
||||
@@ -0,0 +1,25 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
|
||||
|
||||
#include "redis.h"
|
||||
#include "redis_cluster.h"
|
||||
#include "queued_redis.h"
|
||||
#include "sentinel.h"
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1439
File diff suppressed because it is too large.
Load diff
+1403
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,435 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H
|
||||
#define SEWENEW_REDISPLUSPLUS_REPLY_H
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "errors.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
struct ReplyDeleter {
|
||||
void operator()(redisReply *reply) const {
|
||||
if (reply != nullptr) {
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ReplyUPtr = std::unique_ptr<redisReply, ReplyDeleter>;
|
||||
|
||||
namespace reply {
|
||||
|
||||
template <typename T>
|
||||
struct ParseTag {};
|
||||
|
||||
template <typename T>
|
||||
inline T parse(redisReply &reply) {
|
||||
return parse(ParseTag<T>(), reply);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T parse_leniently(redisReply &reply);
|
||||
|
||||
void parse(ParseTag<void>, redisReply &reply);
|
||||
|
||||
std::string parse(ParseTag<std::string>, redisReply &reply);
|
||||
|
||||
long long parse(ParseTag<long long>, redisReply &reply);
|
||||
|
||||
double parse(ParseTag<double>, redisReply &reply);
|
||||
|
||||
bool parse(ParseTag<bool>, redisReply &reply);
|
||||
|
||||
template <typename T>
|
||||
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply);
|
||||
|
||||
template <typename T, typename U>
|
||||
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply);
|
||||
|
||||
template <typename ...Args>
|
||||
std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply);
|
||||
|
||||
#ifdef REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
inline Monostate parse(ParseTag<Monostate>, redisReply &) {
|
||||
// Just ignore the reply
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
Variant<Args...> parse(ParseTag<Variant<Args...>>, redisReply &reply);
|
||||
|
||||
#endif
|
||||
|
||||
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type = 0>
|
||||
T parse(ParseTag<T>, redisReply &reply);
|
||||
|
||||
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type = 0>
|
||||
T parse(ParseTag<T>, redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
long long parse_scan_reply(redisReply &reply, Output output);
|
||||
|
||||
inline bool is_error(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_ERROR;
|
||||
}
|
||||
|
||||
inline bool is_nil(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_NIL;
|
||||
}
|
||||
|
||||
inline bool is_string(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_STRING;
|
||||
}
|
||||
|
||||
inline bool is_status(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_STATUS;
|
||||
}
|
||||
|
||||
inline bool is_integer(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_INTEGER;
|
||||
}
|
||||
|
||||
inline bool is_array(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_ARRAY;
|
||||
}
|
||||
|
||||
std::string to_status(redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
void to_array(redisReply &reply, Output output);
|
||||
|
||||
// Rewrite set reply to bool type
|
||||
void rewrite_set_reply(redisReply &reply);
|
||||
|
||||
// Some command might return an empty array reply as a nil reply,
|
||||
// e.g. georadius, zpopmin, zpopmax. In this case, we rewrite the
|
||||
// reply to a nil reply.
|
||||
void rewrite_empty_array_reply(redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
auto parse_xpending_reply(redisReply &reply, Output output)
|
||||
-> std::tuple<long long, OptionalString, OptionalString>;
|
||||
|
||||
}
|
||||
|
||||
// Inline implementations.
|
||||
|
||||
namespace reply {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Output>
|
||||
void to_array(redisReply &reply, Output output) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
if (reply.element == nullptr) {
|
||||
// Empty array.
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
|
||||
auto *sub_reply = reply.element[idx];
|
||||
if (sub_reply == nullptr) {
|
||||
throw ProtoError("Null array element reply");
|
||||
}
|
||||
|
||||
*output = parse<typename IterType<Output>::type>(*sub_reply);
|
||||
|
||||
++output;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_flat_array(redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
void to_flat_array(redisReply &reply, Output output) {
|
||||
if (reply.element == nullptr) {
|
||||
// Empty array.
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply.elements % 2 != 0) {
|
||||
throw ProtoError("Not string pair array reply");
|
||||
}
|
||||
|
||||
for (std::size_t idx = 0; idx != reply.elements; idx += 2) {
|
||||
auto *key_reply = reply.element[idx];
|
||||
auto *val_reply = reply.element[idx + 1];
|
||||
if (key_reply == nullptr || val_reply == nullptr) {
|
||||
throw ProtoError("Null string array reply");
|
||||
}
|
||||
|
||||
using Pair = typename IterType<Output>::type;
|
||||
using FirstType = typename std::decay<typename Pair::first_type>::type;
|
||||
using SecondType = typename std::decay<typename Pair::second_type>::type;
|
||||
*output = std::make_pair(parse<FirstType>(*key_reply),
|
||||
parse<SecondType>(*val_reply));
|
||||
|
||||
++output;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
void to_array(std::true_type, redisReply &reply, Output output) {
|
||||
if (is_flat_array(reply)) {
|
||||
to_flat_array(reply, output);
|
||||
} else {
|
||||
to_array(reply, output);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
void to_array(std::false_type, redisReply &reply, Output output) {
|
||||
to_array(reply, output);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::tuple<T> parse_tuple(redisReply **reply, std::size_t idx) {
|
||||
assert(reply != nullptr);
|
||||
|
||||
auto *sub_reply = reply[idx];
|
||||
if (sub_reply == nullptr) {
|
||||
throw ProtoError("Null reply");
|
||||
}
|
||||
|
||||
return std::make_tuple(parse<T>(*sub_reply));
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
auto parse_tuple(redisReply **reply, std::size_t idx) ->
|
||||
typename std::enable_if<sizeof...(Args) != 0, std::tuple<T, Args...>>::type {
|
||||
assert(reply != nullptr);
|
||||
|
||||
return std::tuple_cat(parse_tuple<T>(reply, idx),
|
||||
parse_tuple<Args...>(reply, idx + 1));
|
||||
}
|
||||
|
||||
#ifdef REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
template <typename T>
|
||||
Variant<T> parse_variant(redisReply &reply) {
|
||||
return parse<T>(reply);
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
auto parse_variant(redisReply &reply) ->
|
||||
typename std::enable_if<sizeof...(Args) != 0, Variant<T, Args...>>::type {
|
||||
auto return_var = [](auto &&arg) {
|
||||
return Variant<T, Args...>(std::move(arg));
|
||||
};
|
||||
|
||||
try {
|
||||
return std::visit(return_var, parse_variant<T>(reply));
|
||||
} catch (const ProtoError &) {
|
||||
return std::visit(return_var, parse_variant<Args...>(reply));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T parse_leniently(redisReply &reply) {
|
||||
if (is_array(reply) && reply.elements == 1) {
|
||||
if (reply.element == nullptr) {
|
||||
throw ProtoError("null array reply");
|
||||
}
|
||||
|
||||
auto *ele = reply.element[0];
|
||||
if (ele != nullptr) {
|
||||
return parse<T>(*ele);
|
||||
} // else fall through
|
||||
}
|
||||
|
||||
return parse<T>(reply);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
|
||||
if (reply::is_nil(reply)) {
|
||||
// Because of a GCC bug, we cannot return {} for -std=c++17
|
||||
// Refer to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86465
|
||||
#if defined REDIS_PLUS_PLUS_HAS_OPTIONAL
|
||||
return std::nullopt;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
return Optional<T>(parse<T>(reply));
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
if (reply.elements != 2) {
|
||||
throw ProtoError("NOT key-value PAIR reply");
|
||||
}
|
||||
|
||||
if (reply.element == nullptr) {
|
||||
throw ProtoError("Null PAIR reply");
|
||||
}
|
||||
|
||||
auto *first = reply.element[0];
|
||||
auto *second = reply.element[1];
|
||||
if (first == nullptr || second == nullptr) {
|
||||
throw ProtoError("Null pair reply");
|
||||
}
|
||||
|
||||
return std::make_pair(parse<typename std::decay<T>::type>(*first),
|
||||
parse<typename std::decay<U>::type>(*second));
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
std::tuple<Args...> parse(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)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
if (reply.elements != size) {
|
||||
throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements");
|
||||
}
|
||||
|
||||
if (reply.element == nullptr) {
|
||||
throw ProtoError("Null TUPLE reply");
|
||||
}
|
||||
|
||||
return detail::parse_tuple<Args...>(reply.element, 0);
|
||||
}
|
||||
|
||||
#ifdef REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
template <typename ...Args>
|
||||
Variant<Args...> parse(ParseTag<Variant<Args...>>, redisReply &reply) {
|
||||
return detail::parse_variant<Args...>(reply);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type>
|
||||
T parse(ParseTag<T>, redisReply &reply) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
T container;
|
||||
|
||||
to_array(reply, std::back_inserter(container));
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type>
|
||||
T parse(ParseTag<T>, redisReply &reply) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
T container;
|
||||
|
||||
to_array(reply, std::inserter(container, container.end()));
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
long long parse_scan_reply(redisReply &reply, Output output) {
|
||||
if (reply.elements != 2 || reply.element == nullptr) {
|
||||
throw ProtoError("Invalid scan reply");
|
||||
}
|
||||
|
||||
auto *cursor_reply = reply.element[0];
|
||||
auto *data_reply = reply.element[1];
|
||||
if (cursor_reply == nullptr || data_reply == nullptr) {
|
||||
throw ProtoError("Invalid cursor reply or data reply");
|
||||
}
|
||||
|
||||
auto cursor_str = reply::parse<std::string>(*cursor_reply);
|
||||
long long new_cursor = 0;
|
||||
try {
|
||||
new_cursor = std::stoll(cursor_str);
|
||||
} catch (const std::exception &e) {
|
||||
throw ProtoError("Invalid cursor reply: " + cursor_str);
|
||||
}
|
||||
|
||||
reply::to_array(*data_reply, output);
|
||||
|
||||
return new_cursor;
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
void to_array(redisReply &reply, Output output) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
detail::to_array(typename IsKvPairIter<Output>::type(), reply, output);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
auto parse_xpending_reply(redisReply &reply, Output output)
|
||||
-> std::tuple<long long, OptionalString, OptionalString> {
|
||||
if (!is_array(reply) || reply.elements != 4) {
|
||||
throw ProtoError("expect array reply with 4 elements");
|
||||
}
|
||||
|
||||
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
|
||||
if (reply.element[idx] == nullptr) {
|
||||
throw ProtoError("null array reply");
|
||||
}
|
||||
}
|
||||
|
||||
auto num = parse<long long>(*(reply.element[0]));
|
||||
auto start = parse<OptionalString>(*(reply.element[1]));
|
||||
auto end = parse<OptionalString>(*(reply.element[2]));
|
||||
|
||||
auto &entry_reply = *(reply.element[3]);
|
||||
if (!is_nil(entry_reply)) {
|
||||
to_array(entry_reply, output);
|
||||
}
|
||||
|
||||
return std::make_tuple(num, std::move(start), std::move(end));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H
|
||||
@@ -0,0 +1,141 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SENTINEL_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "connection.h"
|
||||
#include "shards.h"
|
||||
#include "reply.h"
|
||||
#include "tls.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
struct SentinelOptions {
|
||||
std::vector<std::pair<std::string, int>> nodes;
|
||||
|
||||
std::string password;
|
||||
|
||||
bool keep_alive = true;
|
||||
|
||||
std::chrono::milliseconds connect_timeout{100};
|
||||
|
||||
std::chrono::milliseconds socket_timeout{100};
|
||||
|
||||
std::chrono::milliseconds retry_interval{100};
|
||||
|
||||
std::size_t max_retry = 2;
|
||||
|
||||
tls::TlsOptions tls;
|
||||
};
|
||||
|
||||
class Sentinel {
|
||||
public:
|
||||
explicit Sentinel(const SentinelOptions &sentinel_opts);
|
||||
|
||||
Sentinel(const Sentinel &) = delete;
|
||||
Sentinel& operator=(const Sentinel &) = delete;
|
||||
|
||||
Sentinel(Sentinel &&) = delete;
|
||||
Sentinel& operator=(Sentinel &&) = delete;
|
||||
|
||||
~Sentinel() = default;
|
||||
|
||||
private:
|
||||
Connection master(const std::string &master_name, const ConnectionOptions &opts);
|
||||
|
||||
Connection slave(const std::string &master_name, const ConnectionOptions &opts);
|
||||
|
||||
class Iterator;
|
||||
|
||||
friend class SimpleSentinel;
|
||||
|
||||
std::list<ConnectionOptions> _parse_options(const SentinelOptions &opts) const;
|
||||
|
||||
Optional<Node> _get_master_addr_by_name(Connection &connection, const StringView &name);
|
||||
|
||||
std::vector<Node> _get_slave_addr_by_name(Connection &connection, const StringView &name);
|
||||
|
||||
Connection _connect_redis(const Node &node, ConnectionOptions opts);
|
||||
|
||||
Role _get_role(Connection &connection);
|
||||
|
||||
std::vector<Node> _parse_slave_info(redisReply &reply) const;
|
||||
|
||||
std::list<Connection> _healthy_sentinels;
|
||||
|
||||
std::list<ConnectionOptions> _broken_sentinels;
|
||||
|
||||
SentinelOptions _sentinel_opts;
|
||||
|
||||
std::mutex _mutex;
|
||||
};
|
||||
|
||||
class SimpleSentinel {
|
||||
public:
|
||||
SimpleSentinel(const std::shared_ptr<Sentinel> &sentinel,
|
||||
const std::string &master_name,
|
||||
Role role);
|
||||
|
||||
SimpleSentinel() = default;
|
||||
|
||||
SimpleSentinel(const SimpleSentinel &) = default;
|
||||
SimpleSentinel& operator=(const SimpleSentinel &) = default;
|
||||
|
||||
SimpleSentinel(SimpleSentinel &&) = default;
|
||||
SimpleSentinel& operator=(SimpleSentinel &&) = default;
|
||||
|
||||
~SimpleSentinel() = default;
|
||||
|
||||
explicit operator bool() const {
|
||||
return bool(_sentinel);
|
||||
}
|
||||
|
||||
Connection create(const ConnectionOptions &opts);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Sentinel> _sentinel;
|
||||
|
||||
std::string _master_name;
|
||||
|
||||
Role _role = Role::MASTER;
|
||||
};
|
||||
|
||||
class StopIterError : public Error {
|
||||
public:
|
||||
StopIterError() : Error("StopIterError") {}
|
||||
|
||||
StopIterError(const StopIterError &) = default;
|
||||
StopIterError& operator=(const StopIterError &) = default;
|
||||
|
||||
StopIterError(StopIterError &&) = default;
|
||||
StopIterError& operator=(StopIterError &&) = default;
|
||||
|
||||
virtual ~StopIterError() override = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H
|
||||
@@ -0,0 +1,115 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SHARDS_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
using Slot = std::size_t;
|
||||
|
||||
struct SlotRange {
|
||||
Slot min;
|
||||
Slot max;
|
||||
};
|
||||
|
||||
inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) {
|
||||
return lhs.max < rhs.max;
|
||||
}
|
||||
|
||||
struct Node {
|
||||
std::string host;
|
||||
int port;
|
||||
};
|
||||
|
||||
inline bool operator==(const Node &lhs, const Node &rhs) {
|
||||
return lhs.host == rhs.host && lhs.port == rhs.port;
|
||||
}
|
||||
|
||||
struct NodeHash {
|
||||
std::size_t operator()(const Node &node) const noexcept {
|
||||
auto host_hash = std::hash<std::string>{}(node.host);
|
||||
auto port_hash = std::hash<int>{}(node.port);
|
||||
return host_hash ^ (port_hash << 1);
|
||||
}
|
||||
};
|
||||
|
||||
using Shards = std::map<SlotRange, Node>;
|
||||
|
||||
class RedirectionError : public ReplyError {
|
||||
public:
|
||||
RedirectionError(const std::string &msg);
|
||||
|
||||
RedirectionError(const RedirectionError &) = default;
|
||||
RedirectionError& operator=(const RedirectionError &) = default;
|
||||
|
||||
RedirectionError(RedirectionError &&) = default;
|
||||
RedirectionError& operator=(RedirectionError &&) = default;
|
||||
|
||||
virtual ~RedirectionError() override = default;
|
||||
|
||||
Slot slot() const {
|
||||
return _slot;
|
||||
}
|
||||
|
||||
const Node& node() const {
|
||||
return _node;
|
||||
}
|
||||
|
||||
private:
|
||||
std::pair<Slot, Node> _parse_error(const std::string &msg) const;
|
||||
|
||||
Slot _slot = 0;
|
||||
Node _node;
|
||||
};
|
||||
|
||||
class MovedError : public RedirectionError {
|
||||
public:
|
||||
explicit MovedError(const std::string &msg) : RedirectionError(msg) {}
|
||||
|
||||
MovedError(const MovedError &) = default;
|
||||
MovedError& operator=(const MovedError &) = default;
|
||||
|
||||
MovedError(MovedError &&) = default;
|
||||
MovedError& operator=(MovedError &&) = default;
|
||||
|
||||
virtual ~MovedError() override = default;
|
||||
};
|
||||
|
||||
class AskError : public RedirectionError {
|
||||
public:
|
||||
explicit AskError(const std::string &msg) : RedirectionError(msg) {}
|
||||
|
||||
AskError(const AskError &) = default;
|
||||
AskError& operator=(const AskError &) = default;
|
||||
|
||||
AskError(AskError &&) = default;
|
||||
AskError& operator=(AskError &&) = default;
|
||||
|
||||
virtual ~AskError() override = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H
|
||||
@@ -0,0 +1,121 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <random>
|
||||
#include <memory>
|
||||
#include "reply.h"
|
||||
#include "connection_pool.h"
|
||||
#include "shards.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class ShardsPool {
|
||||
public:
|
||||
ShardsPool() = default;
|
||||
|
||||
ShardsPool(const ShardsPool &that) = delete;
|
||||
ShardsPool& operator=(const ShardsPool &that) = delete;
|
||||
|
||||
ShardsPool(ShardsPool &&that);
|
||||
ShardsPool& operator=(ShardsPool &&that);
|
||||
|
||||
~ShardsPool() = default;
|
||||
|
||||
ShardsPool(const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts,
|
||||
Role role);
|
||||
|
||||
// Fetch a connection by key.
|
||||
ConnectionPoolSPtr fetch(const StringView &key);
|
||||
|
||||
// Randomly pick a connection.
|
||||
ConnectionPoolSPtr fetch();
|
||||
|
||||
// Fetch a connection by node.
|
||||
ConnectionPoolSPtr fetch(const Node &node);
|
||||
|
||||
void update();
|
||||
|
||||
ConnectionOptions connection_options(const StringView &key);
|
||||
|
||||
ConnectionOptions connection_options();
|
||||
|
||||
Shards shards();
|
||||
|
||||
private:
|
||||
void _move(ShardsPool &&that);
|
||||
|
||||
void _init_pool(const Shards &shards);
|
||||
|
||||
Shards _cluster_slots(Connection &connection) const;
|
||||
|
||||
ReplyUPtr _cluster_slots_command(Connection &connection) const;
|
||||
|
||||
Shards _parse_reply(redisReply &reply) const;
|
||||
|
||||
Slot _parse_slot(redisReply *reply) const;
|
||||
|
||||
Node _parse_node(redisReply *reply) const;
|
||||
|
||||
std::pair<SlotRange, Node> _parse_slot_info(redisReply &reply) const;
|
||||
|
||||
// Get slot by key.
|
||||
std::size_t _slot(const StringView &key) const;
|
||||
|
||||
// Randomly pick a slot.
|
||||
std::size_t _slot() const;
|
||||
|
||||
// Get a random number between [min, max]
|
||||
std::size_t _random(std::size_t min, std::size_t max) const;
|
||||
|
||||
ConnectionPoolSPtr& _get_pool(Slot slot);
|
||||
|
||||
ConnectionPoolSPtr _fetch(Slot slot);
|
||||
|
||||
ConnectionOptions _connection_options(Slot slot);
|
||||
|
||||
using NodeMap = std::unordered_map<Node, ConnectionPoolSPtr, NodeHash>;
|
||||
|
||||
NodeMap::iterator _add_node(const Node &node);
|
||||
|
||||
ConnectionPoolOptions _pool_opts;
|
||||
|
||||
ConnectionOptions _connection_opts;
|
||||
|
||||
Shards _shards;
|
||||
|
||||
NodeMap _pools;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
Role _role = Role::MASTER;
|
||||
|
||||
static const std::size_t SHARDS = 16383;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
|
||||
@@ -0,0 +1,231 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "connection.h"
|
||||
#include "reply.h"
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
// @NOTE: Subscriber is NOT thread-safe.
|
||||
// Subscriber uses callbacks to handle messages. There are 6 kinds of messages:
|
||||
// 1) MESSAGE: message sent to a channel.
|
||||
// 2) PMESSAGE: message sent to channels of a given pattern.
|
||||
// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel.
|
||||
// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel.
|
||||
// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern.
|
||||
// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern.
|
||||
//
|
||||
// Use Subscriber::on_message(MsgCallback) to set the callback function for message of
|
||||
// *MESSAGE* type, and the callback interface is:
|
||||
// void (std::string channel, std::string msg)
|
||||
//
|
||||
// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of
|
||||
// *PMESSAGE* type, and the callback interface is:
|
||||
// void (std::string pattern, std::string channel, std::string msg)
|
||||
//
|
||||
// Messages of other types are called *META MESSAGE*, they have the same callback interface.
|
||||
// Use Subscriber::on_meta(MetaCallback) to set the callback function:
|
||||
// void (Subscriber::MsgType type, OptionalString channel, long long num)
|
||||
//
|
||||
// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to
|
||||
// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all
|
||||
// channels/patterns, *channel* will be null. So the second parameter of meta callback
|
||||
// is of type *OptionalString*.
|
||||
//
|
||||
// All these callback interfaces pass std::string by value, and you can take their ownership
|
||||
// (i.e. std::move) safely.
|
||||
//
|
||||
// If you don't set callback for a specific kind of message, Subscriber::consume() will
|
||||
// receive the message, and ignore it, i.e. no callback will be called.
|
||||
class Subscriber {
|
||||
public:
|
||||
Subscriber(const Subscriber &) = delete;
|
||||
Subscriber& operator=(const Subscriber &) = delete;
|
||||
|
||||
Subscriber(Subscriber &&) = default;
|
||||
Subscriber& operator=(Subscriber &&) = default;
|
||||
|
||||
~Subscriber() = default;
|
||||
|
||||
enum class MsgType {
|
||||
SUBSCRIBE,
|
||||
UNSUBSCRIBE,
|
||||
PSUBSCRIBE,
|
||||
PUNSUBSCRIBE,
|
||||
MESSAGE,
|
||||
PMESSAGE
|
||||
};
|
||||
|
||||
template <typename MsgCb>
|
||||
void on_message(MsgCb msg_callback);
|
||||
|
||||
template <typename PMsgCb>
|
||||
void on_pmessage(PMsgCb pmsg_callback);
|
||||
|
||||
template <typename MetaCb>
|
||||
void on_meta(MetaCb meta_callback);
|
||||
|
||||
void subscribe(const StringView &channel);
|
||||
|
||||
template <typename Input>
|
||||
void subscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void subscribe(std::initializer_list<T> channels) {
|
||||
subscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void unsubscribe();
|
||||
|
||||
void unsubscribe(const StringView &channel);
|
||||
|
||||
template <typename Input>
|
||||
void unsubscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void unsubscribe(std::initializer_list<T> channels) {
|
||||
unsubscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void psubscribe(const StringView &pattern);
|
||||
|
||||
template <typename Input>
|
||||
void psubscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void psubscribe(std::initializer_list<T> channels) {
|
||||
psubscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void punsubscribe();
|
||||
|
||||
void punsubscribe(const StringView &channel);
|
||||
|
||||
template <typename Input>
|
||||
void punsubscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void punsubscribe(std::initializer_list<T> channels) {
|
||||
punsubscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void consume();
|
||||
|
||||
private:
|
||||
friend class Redis;
|
||||
|
||||
friend class RedisCluster;
|
||||
|
||||
explicit Subscriber(Connection connection);
|
||||
|
||||
MsgType _msg_type(redisReply *reply) const;
|
||||
|
||||
void _check_connection();
|
||||
|
||||
void _handle_message(redisReply &reply);
|
||||
|
||||
void _handle_pmessage(redisReply &reply);
|
||||
|
||||
void _handle_meta(MsgType type, redisReply &reply);
|
||||
|
||||
using MsgCallback = std::function<void (std::string channel, std::string msg)>;
|
||||
|
||||
using PatternMsgCallback = std::function<void (std::string pattern,
|
||||
std::string channel,
|
||||
std::string msg)>;
|
||||
|
||||
using MetaCallback = std::function<void (MsgType type,
|
||||
OptionalString channel,
|
||||
long long num)>;
|
||||
|
||||
using TypeIndex = std::unordered_map<std::string, MsgType>;
|
||||
static const TypeIndex _msg_type_index;
|
||||
|
||||
Connection _connection;
|
||||
|
||||
MsgCallback _msg_callback = nullptr;
|
||||
|
||||
PatternMsgCallback _pmsg_callback = nullptr;
|
||||
|
||||
MetaCallback _meta_callback = nullptr;
|
||||
};
|
||||
|
||||
template <typename MsgCb>
|
||||
void Subscriber::on_message(MsgCb msg_callback) {
|
||||
_msg_callback = msg_callback;
|
||||
}
|
||||
|
||||
template <typename PMsgCb>
|
||||
void Subscriber::on_pmessage(PMsgCb pmsg_callback) {
|
||||
_pmsg_callback = pmsg_callback;
|
||||
}
|
||||
|
||||
template <typename MetaCb>
|
||||
void Subscriber::on_meta(MetaCb meta_callback) {
|
||||
_meta_callback = meta_callback;
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::subscribe(Input first, Input last) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
_check_connection();
|
||||
|
||||
cmd::subscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::unsubscribe(Input first, Input last) {
|
||||
_check_connection();
|
||||
|
||||
cmd::unsubscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::psubscribe(Input first, Input last) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
_check_connection();
|
||||
|
||||
cmd::psubscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::punsubscribe(Input first, Input last) {
|
||||
_check_connection();
|
||||
|
||||
cmd::punsubscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
|
||||
@@ -0,0 +1,47 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2020 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_NO_TLS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_NO_TLS_H
|
||||
|
||||
#include <hiredis/hiredis.h>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
namespace tls {
|
||||
|
||||
struct TlsOptions {};
|
||||
|
||||
struct TlsContextUPtr {};
|
||||
|
||||
inline TlsContextUPtr secure_connection(redisContext &/*ctx*/, const TlsOptions &/*opts*/) {
|
||||
// Do nothing
|
||||
return {};
|
||||
}
|
||||
|
||||
inline bool enabled(const TlsOptions &/*opts*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_NO_TLS_H
|
||||
@@ -0,0 +1,77 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H
|
||||
#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "connection.h"
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class TransactionImpl {
|
||||
public:
|
||||
explicit TransactionImpl(bool piped) : _piped(piped) {}
|
||||
|
||||
template <typename Cmd, typename ...Args>
|
||||
void command(Connection &connection, Cmd cmd, Args &&...args);
|
||||
|
||||
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
void discard(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
private:
|
||||
void _open_transaction(Connection &connection);
|
||||
|
||||
void _close_transaction();
|
||||
|
||||
void _get_queued_reply(Connection &connection);
|
||||
|
||||
void _get_queued_replies(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
std::vector<ReplyUPtr> _exec(Connection &connection);
|
||||
|
||||
void _discard(Connection &connection);
|
||||
|
||||
bool _in_transaction = false;
|
||||
|
||||
bool _piped;
|
||||
};
|
||||
|
||||
template <typename Cmd, typename ...Args>
|
||||
void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) {
|
||||
assert(!connection.broken());
|
||||
|
||||
if (!_in_transaction) {
|
||||
_open_transaction(connection);
|
||||
}
|
||||
|
||||
cmd(connection, std::forward<Args>(args)...);
|
||||
|
||||
if (!_piped) {
|
||||
_get_queued_reply(connection);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H
|
||||
@@ -0,0 +1,193 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_UTILS_H
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include "cxx_utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
using OptionalString = Optional<std::string>;
|
||||
|
||||
using OptionalLongLong = Optional<long long>;
|
||||
|
||||
using OptionalDouble = Optional<double>;
|
||||
|
||||
using OptionalStringPair = Optional<std::pair<std::string, std::string>>;
|
||||
|
||||
template <typename ...>
|
||||
struct IsKvPair : std::false_type {};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct IsKvPair<std::pair<T, U>> : std::true_type {};
|
||||
|
||||
template <typename ...>
|
||||
using Void = void;
|
||||
|
||||
template <typename T, typename U = Void<>>
|
||||
struct IsInserter : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
//struct IsInserter<T, Void<typename T::container_type>> : std::true_type {};
|
||||
struct IsInserter<T,
|
||||
typename std::enable_if<!std::is_void<typename T::container_type>::value>::type>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename Iter, typename T = Void<>>
|
||||
struct IterType {
|
||||
using type = typename std::iterator_traits<Iter>::value_type;
|
||||
};
|
||||
|
||||
template <typename Iter>
|
||||
//struct IterType<Iter, Void<typename Iter::container_type>> {
|
||||
struct IterType<Iter,
|
||||
//typename std::enable_if<std::is_void<typename Iter::value_type>::value>::type> {
|
||||
typename std::enable_if<IsInserter<Iter>::value>::type> {
|
||||
using type = typename std::decay<typename Iter::container_type::value_type>::type;
|
||||
};
|
||||
|
||||
template <typename Iter, typename T = Void<>>
|
||||
struct IsIter : std::false_type {};
|
||||
|
||||
template <typename Iter>
|
||||
struct IsIter<Iter, typename std::enable_if<IsInserter<Iter>::value>::type> : std::true_type {};
|
||||
|
||||
template <typename Iter>
|
||||
//struct IsIter<Iter, Void<typename std::iterator_traits<Iter>::iterator_category>>
|
||||
struct IsIter<Iter,
|
||||
typename std::enable_if<!std::is_void<
|
||||
typename std::iterator_traits<Iter>::value_type>::value>::type>
|
||||
: std::integral_constant<bool, !std::is_convertible<Iter, StringView>::value> {};
|
||||
|
||||
template <typename T>
|
||||
struct IsKvPairIter : IsKvPair<typename IterType<T>::type> {};
|
||||
|
||||
template <typename T, typename Tuple>
|
||||
struct TupleWithType : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct TupleWithType<T, std::tuple<>> : std::false_type {};
|
||||
|
||||
template <typename T, typename U, typename ...Args>
|
||||
struct TupleWithType<T, std::tuple<U, Args...>> : TupleWithType<T, std::tuple<Args...>> {};
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
struct TupleWithType<T, std::tuple<T, Args...>> : std::true_type {};
|
||||
|
||||
template <std::size_t ...Is>
|
||||
struct IndexSequence {};
|
||||
|
||||
template <std::size_t I, std::size_t ...Is>
|
||||
struct MakeIndexSequence : MakeIndexSequence<I - 1, I - 1, Is...> {};
|
||||
|
||||
template <std::size_t ...Is>
|
||||
struct MakeIndexSequence<0, Is...> : IndexSequence<Is...> {};
|
||||
|
||||
// NthType and NthValue are taken from
|
||||
// https://stackoverflow.com/questions/14261183
|
||||
template <std::size_t I, typename ...Args>
|
||||
struct NthType {};
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
struct NthType<0, Arg, Args...> {
|
||||
using type = Arg;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename Arg, typename ...Args>
|
||||
struct NthType<I, Arg, Args...> {
|
||||
using type = typename NthType<I - 1, Args...>::type;
|
||||
};
|
||||
|
||||
template <typename ...Args>
|
||||
struct LastType {
|
||||
using type = typename NthType<sizeof...(Args) - 1, Args...>::type;
|
||||
};
|
||||
|
||||
struct InvalidLastType {};
|
||||
|
||||
template <>
|
||||
struct LastType<> {
|
||||
using type = InvalidLastType;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename Arg, typename ...Args>
|
||||
auto NthValue(Arg &&arg, Args &&...)
|
||||
-> typename std::enable_if<(I == 0), decltype(std::forward<Arg>(arg))>::type {
|
||||
return std::forward<Arg>(arg);
|
||||
}
|
||||
|
||||
template <std::size_t I, typename Arg, typename ...Args>
|
||||
auto NthValue(Arg &&, Args &&...args)
|
||||
-> typename std::enable_if<(I > 0),
|
||||
decltype(std::forward<typename NthType<I, Arg, Args...>::type>(
|
||||
std::declval<typename NthType<I, Arg, Args...>::type>()))>::type {
|
||||
return std::forward<typename NthType<I, Arg, Args...>::type>(
|
||||
NthValue<I - 1>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
auto LastValue(Args &&...args)
|
||||
-> decltype(std::forward<typename LastType<Args...>::type>(
|
||||
std::declval<typename LastType<Args...>::type>())) {
|
||||
return std::forward<typename LastType<Args...>::type>(
|
||||
NthValue<sizeof...(Args) - 1>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T, typename = Void<>>
|
||||
struct HasPushBack : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct HasPushBack<T,
|
||||
typename std::enable_if<
|
||||
std::is_void<decltype(
|
||||
std::declval<T>().push_back(std::declval<typename T::value_type>())
|
||||
)>::value>::type> : std::true_type {};
|
||||
|
||||
template <typename T, typename = Void<>>
|
||||
struct HasInsert : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct HasInsert<T,
|
||||
typename std::enable_if<
|
||||
std::is_same<
|
||||
decltype(std::declval<T>().insert(std::declval<typename T::const_iterator>(),
|
||||
std::declval<typename T::value_type>())),
|
||||
typename T::iterator>::value>::type> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct IsSequenceContainer
|
||||
: std::integral_constant<bool,
|
||||
HasPushBack<T>::value
|
||||
&& !std::is_same<typename std::decay<T>::type, std::string>::value> {};
|
||||
|
||||
template <typename T>
|
||||
struct IsAssociativeContainer
|
||||
: std::integral_constant<bool,
|
||||
HasInsert<T>::value && !HasPushBack<T>::value> {};
|
||||
|
||||
uint16_t crc16(const char *buf, int len);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H
|
||||
@@ -0,0 +1,12 @@
|
||||
prefix=/home/grant/dev/ZeroTierOne/ext/redis-plus-plus-1.3.3/install/ubuntu22.04
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: redis++
|
||||
Description: This is a Redis client, based on hiredis and written in C++11. It supports scritpting, pub/sub, pipeline, transaction, Redis Cluster, Redis Sentinel, connection pool, ACL, SSL and thread safety.
|
||||
Version: 1.3.3
|
||||
URL: https://github.com/sewenew/redis-plus-plus
|
||||
Requires: hiredis
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lredis++
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# This is a basic version file for the Config-mode of find_package().
|
||||
# It is used by write_basic_package_version_file() as input file for configure_file()
|
||||
# to create a version-file which can be installed along a config.cmake file.
|
||||
#
|
||||
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
|
||||
# the requested version string are exactly the same and it sets
|
||||
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version.
|
||||
# The variable CVF_VERSION must be set before calling configure_file().
|
||||
|
||||
set(PACKAGE_VERSION "1.3.3")
|
||||
|
||||
if (PACKAGE_FIND_VERSION_RANGE)
|
||||
# Package version must be in the requested version range
|
||||
if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN)
|
||||
OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX)
|
||||
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX)))
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
else()
|
||||
if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
|
||||
set(PACKAGE_VERSION_EXACT TRUE)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# if the installed project requested no architecture check, don't perform the check
|
||||
if("FALSE")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
|
||||
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8")
|
||||
math(EXPR installedBits "8 * 8")
|
||||
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
|
||||
set(PACKAGE_VERSION_UNSUITABLE TRUE)
|
||||
endif()
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
|
||||
####### Any changes to this file will be overwritten by the next CMake run ####
|
||||
####### The input file was redis++-config.cmake.in ########
|
||||
|
||||
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
|
||||
|
||||
macro(set_and_check _var _file)
|
||||
set(${_var} "${_file}")
|
||||
if(NOT EXISTS "${_file}")
|
||||
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(check_required_components _NAME)
|
||||
foreach(comp ${${_NAME}_FIND_COMPONENTS})
|
||||
if(NOT ${_NAME}_${comp}_FOUND)
|
||||
if(${_NAME}_FIND_REQUIRED_${comp})
|
||||
set(${_NAME}_FOUND FALSE)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
####################################################################################
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
string(REPLACE "," ";" REDIS_PLUS_PLUS_DEPENDS_LIST hiredis)
|
||||
foreach(REDIS_PLUS_PLUS_DEP ${REDIS_PLUS_PLUS_DEPENDS_LIST})
|
||||
find_dependency(${REDIS_PLUS_PLUS_DEP} REQUIRED)
|
||||
endforeach()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/redis++-targets.cmake")
|
||||
|
||||
check_required_components(redis++)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file for configuration "Release".
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Import target "redis++::redis++_static" for configuration "Release"
|
||||
set_property(TARGET redis++::redis++_static APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(redis++::redis++_static PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
|
||||
IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libredis++.a"
|
||||
)
|
||||
|
||||
list(APPEND _IMPORT_CHECK_TARGETS redis++::redis++_static )
|
||||
list(APPEND _IMPORT_CHECK_FILES_FOR_redis++::redis++_static "${_IMPORT_PREFIX}/lib/libredis++.a" )
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# Generated by CMake
|
||||
|
||||
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.6)
|
||||
message(FATAL_ERROR "CMake >= 2.6.0 required")
|
||||
endif()
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(VERSION 2.6...3.20)
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file.
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
|
||||
set(_targetsDefined)
|
||||
set(_targetsNotDefined)
|
||||
set(_expectedTargets)
|
||||
foreach(_expectedTarget redis++::redis++_static)
|
||||
list(APPEND _expectedTargets ${_expectedTarget})
|
||||
if(NOT TARGET ${_expectedTarget})
|
||||
list(APPEND _targetsNotDefined ${_expectedTarget})
|
||||
endif()
|
||||
if(TARGET ${_expectedTarget})
|
||||
list(APPEND _targetsDefined ${_expectedTarget})
|
||||
endif()
|
||||
endforeach()
|
||||
if("${_targetsDefined}" STREQUAL "${_expectedTargets}")
|
||||
unset(_targetsDefined)
|
||||
unset(_targetsNotDefined)
|
||||
unset(_expectedTargets)
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
return()
|
||||
endif()
|
||||
if(NOT "${_targetsDefined}" STREQUAL "")
|
||||
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n")
|
||||
endif()
|
||||
unset(_targetsDefined)
|
||||
unset(_targetsNotDefined)
|
||||
unset(_expectedTargets)
|
||||
|
||||
|
||||
# Compute the installation prefix relative to this file.
|
||||
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
if(_IMPORT_PREFIX STREQUAL "/")
|
||||
set(_IMPORT_PREFIX "")
|
||||
endif()
|
||||
|
||||
# Create imported target redis++::redis++_static
|
||||
add_library(redis++::redis++_static STATIC IMPORTED)
|
||||
|
||||
set_target_properties(redis++::redis++_static PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include"
|
||||
)
|
||||
|
||||
# Load information for each installed configuration.
|
||||
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
file(GLOB CONFIG_FILES "${_DIR}/redis++-targets-*.cmake")
|
||||
foreach(f ${CONFIG_FILES})
|
||||
include(${f})
|
||||
endforeach()
|
||||
|
||||
# Cleanup temporary variables.
|
||||
set(_IMPORT_PREFIX)
|
||||
|
||||
# Loop over all imported files and verify that they actually exist
|
||||
foreach(target ${_IMPORT_CHECK_TARGETS} )
|
||||
foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} )
|
||||
if(NOT EXISTS "${file}" )
|
||||
message(FATAL_ERROR "The imported target \"${target}\" references the file
|
||||
\"${file}\"
|
||||
but this file does not exist. Possible reasons include:
|
||||
* The file was deleted, renamed, or moved to another location.
|
||||
* An install or uninstall procedure did not complete successfully.
|
||||
* The installation package was faulty and contained
|
||||
\"${CMAKE_CURRENT_LIST_FILE}\"
|
||||
but not all the files it references.
|
||||
")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_IMPORT_CHECK_FILES_FOR_${target})
|
||||
endforeach()
|
||||
unset(_IMPORT_CHECK_TARGETS)
|
||||
|
||||
# This file does not depend on other imported targets which have
|
||||
# been exported from the same project but in a separate export set.
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
+775
@@ -0,0 +1,775 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "command_options.h"
|
||||
#include "command_args.h"
|
||||
#include "command.h"
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class FormattedCommand {
|
||||
public:
|
||||
FormattedCommand(char *data, int len) : _data(data), _size(len) {
|
||||
if (data == nullptr || len < 0) {
|
||||
throw Error("failed to format command");
|
||||
}
|
||||
}
|
||||
|
||||
FormattedCommand(const FormattedCommand &) = delete;
|
||||
FormattedCommand& operator=(const FormattedCommand &) = delete;
|
||||
|
||||
FormattedCommand(FormattedCommand &&that) noexcept {
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
FormattedCommand& operator=(FormattedCommand &&that) noexcept {
|
||||
if (this != &that) {
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~FormattedCommand() noexcept {
|
||||
if (_data != nullptr) {
|
||||
redisFreeCommand(_data);
|
||||
}
|
||||
}
|
||||
|
||||
const char* data() const noexcept {
|
||||
return _data;
|
||||
}
|
||||
|
||||
int size() const noexcept {
|
||||
return _size;
|
||||
}
|
||||
|
||||
private:
|
||||
void _move(FormattedCommand &&that) noexcept {
|
||||
_data = that._data;
|
||||
_size = that._size;
|
||||
that._data = nullptr;
|
||||
that._size = 0;
|
||||
}
|
||||
|
||||
char *_data = nullptr;
|
||||
int _size = 0;
|
||||
};
|
||||
|
||||
namespace fmt {
|
||||
|
||||
template <typename ...Args>
|
||||
FormattedCommand format_cmd(const char *format, Args &&...args) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommand(&data, format, std::forward<Args>(args)...);
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
inline FormattedCommand format_cmd(int argc, const char **argv, const std::size_t *argv_len) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommandArgv(&data, argc, argv, argv_len);
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
inline FormattedCommand format_cmd(CmdArgs &args) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommandArgv(&data, args.size(), args.argv(), args.argv_len());
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
struct SetResultParser {
|
||||
bool operator()(redisReply &reply) const {
|
||||
sw::redis::reply::rewrite_set_reply(reply);
|
||||
return sw::redis::reply::parse<bool>(reply);
|
||||
}
|
||||
};
|
||||
|
||||
// CONNECTION commands.
|
||||
|
||||
inline FormattedCommand echo(const StringView &msg) {
|
||||
return format_cmd("ECHO %b", msg.data(), msg.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ping() {
|
||||
return format_cmd("PING");
|
||||
}
|
||||
|
||||
inline FormattedCommand ping(const StringView &msg) {
|
||||
return format_cmd("PING %b", msg.data(), msg.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand del(const StringView &key) {
|
||||
return format_cmd("DEL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand del_range(Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "DEL" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand exists(const StringView &key) {
|
||||
return format_cmd("EXISTS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand exists_range(Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "EXISTS" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand expire(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("EXPIRE %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand expireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::seconds> &tp) {
|
||||
return format_cmd("EXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pexpire(const StringView &key,
|
||||
const std::chrono::milliseconds &timeout) {
|
||||
return format_cmd("PEXPIRE %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pexpireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds> &tp) {
|
||||
return format_cmd("PEXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pttl(const StringView &key) {
|
||||
return format_cmd("PTTL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rename(const StringView &key, const StringView &newkey) {
|
||||
return format_cmd("RENAME %b %b", key.data(), key.size(), newkey.data(), newkey.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand renamenx(const StringView &key, const StringView &newkey) {
|
||||
return format_cmd("RENAMENX %b %b", key.data(), key.size(), newkey.data(), newkey.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ttl(const StringView &key) {
|
||||
return format_cmd("TTL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand unlink(const StringView &key) {
|
||||
return format_cmd("UNLINK %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand unlink_range(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "UNLINK" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// STRING commands.
|
||||
|
||||
inline FormattedCommand get(const StringView &key) {
|
||||
return format_cmd("GET %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand incr(const StringView &key) {
|
||||
return format_cmd("INCR %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand incrby(const StringView &key, long long increment) {
|
||||
return format_cmd("INCRBY %b %lld", key.data(), key.size(), increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand incrbyfloat(const StringView &key, double increment) {
|
||||
return format_cmd("INCRBYFLOAT %b %f", key.data(), key.size(), increment);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand mget(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MGET" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand mset(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MSET" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand msetnx(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MSETNX" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand set(const StringView &key,
|
||||
const StringView &val,
|
||||
const std::chrono::milliseconds &ttl,
|
||||
UpdateType type) {
|
||||
CmdArgs args;
|
||||
args << "SET" << key << val;
|
||||
|
||||
if (ttl > std::chrono::milliseconds(0)) {
|
||||
args << "PX" << ttl.count();
|
||||
}
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand strlen(const StringView &key) {
|
||||
return format_cmd("STRLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand blpop(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BLPOP %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand blpop_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BLPOP" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand brpop(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BRPOP %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand brpop_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BRPOP" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand brpoplpush(const StringView &source,
|
||||
const StringView &destination,
|
||||
const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BRPOPLPUSH %b %b %lld",
|
||||
source.data(), source.size(),
|
||||
destination.data(), destination.size(),
|
||||
timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand llen(const StringView &key) {
|
||||
return format_cmd("LLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand lpop(const StringView &key) {
|
||||
return format_cmd("LPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand lpush(const StringView &key, const StringView &val) {
|
||||
return format_cmd("LPUSH %b %b", key.data(), key.size(), val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand lpush_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "LPUSH" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand lrange(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("LRANGE %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
inline FormattedCommand lrem(const StringView &key, long long count, const StringView &val) {
|
||||
return format_cmd("LREM %b %lld %b", key.data(), key.size(), count, val.data(), val.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ltrim(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("LTRIM %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
inline FormattedCommand rpop(const StringView &key) {
|
||||
return format_cmd("RPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rpoplpush(const StringView &source, const StringView &destination) {
|
||||
return format_cmd("RPOPLPUSH %b %b",
|
||||
source.data(), source.size(),
|
||||
destination.data(), destination.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rpush(const StringView &key, const StringView &val) {
|
||||
return format_cmd("RPUSH %b %b", key.data(), key.size(), val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand rpush_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "RPUSH" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// HASH commands.
|
||||
|
||||
inline FormattedCommand hdel(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HDEL %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hdel_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HDEL" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hexists(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HEXISTS %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hget(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HGET %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hgetall(const StringView &key) {
|
||||
return format_cmd("HGETALL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hincrby(const StringView &key,
|
||||
const StringView &field,
|
||||
long long increment) {
|
||||
return format_cmd("HINCRBY %b %b %lld",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand hincrbyfloat(const StringView &key,
|
||||
const StringView &field,
|
||||
double increment) {
|
||||
return format_cmd("HINCRBYFLOAT %b %b %f",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand hkeys(const StringView &key) {
|
||||
return format_cmd("HKEYS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hlen(const StringView &key) {
|
||||
return format_cmd("HLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hmget(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HMGET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hmset(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HMSET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hset(const StringView &key,
|
||||
const StringView &field,
|
||||
const StringView &val) {
|
||||
return format_cmd("HSET %b %b %b",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto hset_range(const StringView &key,
|
||||
Input first,
|
||||
Input last)
|
||||
-> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
|
||||
FormattedCommand>::type {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HSET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hvals(const StringView &key) {
|
||||
return format_cmd("HVALS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
// SET commands.
|
||||
|
||||
inline FormattedCommand sadd(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SADD %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand sadd_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "SADD" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand scard(const StringView &key) {
|
||||
return format_cmd("SCARD %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand sismember(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SISMEMBER %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand smembers(const StringView &key) {
|
||||
return format_cmd("SMEMBERS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand spop(const StringView &key) {
|
||||
return format_cmd("SPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand spop(const StringView &key, long long count) {
|
||||
return format_cmd("SPOP %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand srem(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SREM %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand srem_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "SREM" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// SORTED SET commands.
|
||||
|
||||
inline FormattedCommand bzpopmax(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BZPOPMAX %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand bzpopmax_range(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BZPOPMAX" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand bzpopmin(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BZPOPMIN %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand bzpopmin_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BZPOPMIN" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand zadd(const StringView &key,
|
||||
const StringView &member,
|
||||
double score,
|
||||
UpdateType type,
|
||||
bool changed) {
|
||||
CmdArgs args;
|
||||
args << "ZADD" << key;
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
if (changed) {
|
||||
args << "CH";
|
||||
}
|
||||
|
||||
args << score << member;
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand zadd_range(const StringView &key,
|
||||
Input first,
|
||||
Input last,
|
||||
UpdateType type,
|
||||
bool changed) {
|
||||
CmdArgs args;
|
||||
args << "ZADD" << key;
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
if (changed) {
|
||||
args << "CH";
|
||||
}
|
||||
|
||||
while (first != last) {
|
||||
// Swap the <member, score> pair to <score, member> pair.
|
||||
args << first->second << first->first;
|
||||
++first;
|
||||
}
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand zcard(const StringView &key) {
|
||||
return format_cmd("ZCARD %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zcount(const StringView &key, const Interval &interval) {
|
||||
return format_cmd("ZCOUNT %b %s %s",
|
||||
key.data(), key.size(),
|
||||
interval.min().c_str(),
|
||||
interval.max().c_str());
|
||||
}
|
||||
|
||||
inline FormattedCommand zincrby(const StringView &key,
|
||||
double increment,
|
||||
const StringView &member) {
|
||||
return format_cmd("ZINCRBY %b %f %b",
|
||||
key.data(), key.size(),
|
||||
increment,
|
||||
member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zlexcount(const StringView &key,
|
||||
const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZLEXCOUNT %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmax(const StringView &key) {
|
||||
return format_cmd("ZPOPMAX %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmax(const StringView &key, long long count) {
|
||||
return format_cmd("ZPOPMAX %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmin(const StringView &key) {
|
||||
return format_cmd("ZPOPMIN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmin_count(const StringView &key, long long count) {
|
||||
return format_cmd("ZPOPMIN %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrange(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("ZRANGE %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZRANGEBYLEX %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrangebyscore(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrank(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZRANK %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zrem(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZREM %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand zrem_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "ZREM" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zremrangebylex(const StringView &key, const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREMRANGEBYLEX %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zremrangebyrank(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("ZREMRANGEBYRANK %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zremrangebyscore(const StringView &key,
|
||||
const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREMRANGEBYSCORE %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrevrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
max.data(), max.size(),
|
||||
min.data(), min.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrevrank(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZREVRANK %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zscore(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZSCORE %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
// SCRIPTING commands.
|
||||
template <typename Keys, typename Args>
|
||||
FormattedCommand eval(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
CmdArgs args;
|
||||
auto keys_num = std::distance(keys_first, keys_last);
|
||||
|
||||
args << "EVAL" << script << keys_num
|
||||
<< std::make_pair(keys_first, keys_last)
|
||||
<< std::make_pair(args_first, args_last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Keys, typename Args>
|
||||
FormattedCommand evalsha(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
CmdArgs args;
|
||||
auto keys_num = std::distance(keys_first, keys_last);
|
||||
|
||||
args << "EVALSHA" << script << keys_num
|
||||
<< std::make_pair(keys_first, keys_last)
|
||||
<< std::make_pair(args_first, args_last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
File diff suppressed because it is too large.
Load diff
+180
@@ -0,0 +1,180 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class CmdArgs {
|
||||
public:
|
||||
template <typename Arg>
|
||||
CmdArgs& append(Arg &&arg);
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
CmdArgs& append(Arg &&arg, Args &&...args);
|
||||
|
||||
// All overloads of operator<< are for internal use only.
|
||||
CmdArgs& operator<<(const StringView &arg);
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type = 0>
|
||||
CmdArgs& operator<<(T &&arg);
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& operator<<(const std::pair<Iter, Iter> &range);
|
||||
|
||||
template <std::size_t N, typename ...Args>
|
||||
auto operator<<(const std::tuple<Args...> &) ->
|
||||
typename std::enable_if<N == sizeof...(Args), CmdArgs&>::type {
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <std::size_t N = 0, typename ...Args>
|
||||
auto operator<<(const std::tuple<Args...> &arg) ->
|
||||
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type;
|
||||
|
||||
const char** argv() {
|
||||
return _argv.data();
|
||||
}
|
||||
|
||||
const std::size_t* argv_len() {
|
||||
return _argv_len.data();
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return _argv.size();
|
||||
}
|
||||
|
||||
private:
|
||||
// Deep copy.
|
||||
CmdArgs& _append(std::string arg);
|
||||
|
||||
// Shallow copy.
|
||||
CmdArgs& _append(const StringView &arg);
|
||||
|
||||
// Shallow copy.
|
||||
CmdArgs& _append(const char *arg);
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type = 0>
|
||||
CmdArgs& _append(T &&arg) {
|
||||
return operator<<(std::forward<T>(arg));
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& _append(std::true_type, const std::pair<Iter, Iter> &range);
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& _append(std::false_type, const std::pair<Iter, Iter> &range);
|
||||
|
||||
std::vector<const char *> _argv;
|
||||
std::vector<std::size_t> _argv_len;
|
||||
|
||||
std::list<std::string> _args;
|
||||
};
|
||||
|
||||
template <typename Arg>
|
||||
inline CmdArgs& CmdArgs::append(Arg &&arg) {
|
||||
return _append(std::forward<Arg>(arg));
|
||||
}
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) {
|
||||
_append(std::forward<Arg>(arg));
|
||||
|
||||
return append(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::operator<<(const StringView &arg) {
|
||||
_argv.push_back(arg.data());
|
||||
_argv_len.push_back(arg.size());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
inline CmdArgs& CmdArgs::operator<<(const std::pair<Iter, Iter> &range) {
|
||||
return _append(IsKvPair<typename std::decay<decltype(*std::declval<Iter>())>::type>(), range);
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type>
|
||||
inline CmdArgs& CmdArgs::operator<<(T &&arg) {
|
||||
return _append(std::to_string(std::forward<T>(arg)));
|
||||
}
|
||||
|
||||
template <std::size_t N, typename ...Args>
|
||||
auto CmdArgs::operator<<(const std::tuple<Args...> &arg) ->
|
||||
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type {
|
||||
operator<<(std::get<N>(arg));
|
||||
|
||||
return operator<<<N + 1, Args...>(arg);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(std::string arg) {
|
||||
_args.push_back(std::move(arg));
|
||||
return operator<<(_args.back());
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(const StringView &arg) {
|
||||
return operator<<(arg);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(const char *arg) {
|
||||
return operator<<(arg);
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& CmdArgs::_append(std::false_type, const std::pair<Iter, Iter> &range) {
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
while (first != last) {
|
||||
*this << *first;
|
||||
++first;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& CmdArgs::_append(std::true_type, const std::pair<Iter, Iter> &range) {
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
while (first != last) {
|
||||
*this << first->first << first->second;
|
||||
++first;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
|
||||
#include <string>
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum class UpdateType {
|
||||
EXIST,
|
||||
NOT_EXIST,
|
||||
ALWAYS
|
||||
};
|
||||
|
||||
enum class InsertPosition {
|
||||
BEFORE,
|
||||
AFTER
|
||||
};
|
||||
|
||||
enum class BoundType {
|
||||
CLOSED,
|
||||
OPEN,
|
||||
LEFT_OPEN,
|
||||
RIGHT_OPEN
|
||||
};
|
||||
|
||||
// (-inf, +inf)
|
||||
template <typename T>
|
||||
class UnboundedInterval;
|
||||
|
||||
// [min, max], (min, max), (min, max], [min, max)
|
||||
template <typename T>
|
||||
class BoundedInterval;
|
||||
|
||||
// [min, +inf), (min, +inf)
|
||||
template <typename T>
|
||||
class LeftBoundedInterval;
|
||||
|
||||
// (-inf, max], (-inf, max)
|
||||
template <typename T>
|
||||
class RightBoundedInterval;
|
||||
|
||||
template <>
|
||||
class UnboundedInterval<double> {
|
||||
public:
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
class BoundedInterval<double> {
|
||||
public:
|
||||
BoundedInterval(double min, double max, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class LeftBoundedInterval<double> {
|
||||
public:
|
||||
LeftBoundedInterval(double min, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const;
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
};
|
||||
|
||||
template <>
|
||||
class RightBoundedInterval<double> {
|
||||
public:
|
||||
RightBoundedInterval(double max, BoundType type);
|
||||
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class UnboundedInterval<std::string> {
|
||||
public:
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
class BoundedInterval<std::string> {
|
||||
public:
|
||||
BoundedInterval(const std::string &min, const std::string &max, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class LeftBoundedInterval<std::string> {
|
||||
public:
|
||||
LeftBoundedInterval(const std::string &min, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const;
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
};
|
||||
|
||||
template <>
|
||||
class RightBoundedInterval<std::string> {
|
||||
public:
|
||||
RightBoundedInterval(const std::string &max, BoundType type);
|
||||
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
struct LimitOptions {
|
||||
long long offset = 0;
|
||||
long long count = -1;
|
||||
};
|
||||
|
||||
enum class Aggregation {
|
||||
SUM,
|
||||
MIN,
|
||||
MAX
|
||||
};
|
||||
|
||||
enum class BitOp {
|
||||
AND,
|
||||
OR,
|
||||
XOR,
|
||||
NOT
|
||||
};
|
||||
|
||||
enum class GeoUnit {
|
||||
M,
|
||||
KM,
|
||||
MI,
|
||||
FT
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct WithCoord : TupleWithType<std::pair<double, double>, T> {};
|
||||
|
||||
template <typename T>
|
||||
struct WithDist : TupleWithType<double, T> {};
|
||||
|
||||
template <typename T>
|
||||
struct WithHash : TupleWithType<long long, T> {};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
@@ -0,0 +1,237 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "errors.h"
|
||||
#include "reply.h"
|
||||
#include "utils.h"
|
||||
#include "tls.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum class ConnectionType {
|
||||
TCP = 0,
|
||||
UNIX
|
||||
};
|
||||
|
||||
struct ConnectionOptions {
|
||||
public:
|
||||
ConnectionOptions() = default;
|
||||
|
||||
explicit ConnectionOptions(const std::string &uri);
|
||||
|
||||
ConnectionOptions(const ConnectionOptions &) = default;
|
||||
ConnectionOptions& operator=(const ConnectionOptions &) = default;
|
||||
|
||||
ConnectionOptions(ConnectionOptions &&) = default;
|
||||
ConnectionOptions& operator=(ConnectionOptions &&) = default;
|
||||
|
||||
~ConnectionOptions() = default;
|
||||
|
||||
ConnectionType type = ConnectionType::TCP;
|
||||
|
||||
std::string host;
|
||||
|
||||
int port = 6379;
|
||||
|
||||
std::string path;
|
||||
|
||||
std::string user = "default";
|
||||
|
||||
std::string password;
|
||||
|
||||
int db = 0;
|
||||
|
||||
bool keep_alive = false;
|
||||
|
||||
std::chrono::milliseconds connect_timeout{0};
|
||||
|
||||
std::chrono::milliseconds socket_timeout{0};
|
||||
|
||||
tls::TlsOptions tls;
|
||||
|
||||
// `readonly` is only used for reading from a slave node in Redis Cluster mode.
|
||||
// Client code should never manually set/get it. This member might be removed in the future.
|
||||
bool readonly = false;
|
||||
|
||||
private:
|
||||
ConnectionOptions _parse_uri(const std::string &uri) const;
|
||||
|
||||
auto _split_uri(const std::string &uri) const
|
||||
-> std::tuple<std::string, std::string, std::string>;
|
||||
|
||||
auto _split_path(const std::string &path) const
|
||||
-> std::tuple<std::string, int, std::string>;
|
||||
|
||||
void _parse_parameters(const std::string ¶meter_string,
|
||||
ConnectionOptions &opts) const;
|
||||
|
||||
void _set_option(const std::string &key, const std::string &val, ConnectionOptions &opts) const;
|
||||
|
||||
bool _parse_bool_option(const std::string &str) const;
|
||||
|
||||
std::chrono::milliseconds _parse_timeout_option(const std::string &str) const;
|
||||
|
||||
std::vector<std::string> _split(const std::string &str, const std::string &delimiter) const;
|
||||
|
||||
void _set_auth_opts(const std::string &auth, ConnectionOptions &opts) const;
|
||||
|
||||
void _set_tcp_opts(const std::string &path, ConnectionOptions &opts) const;
|
||||
|
||||
void _set_unix_opts(const std::string &path, ConnectionOptions &opts) const;
|
||||
};
|
||||
|
||||
class CmdArgs;
|
||||
|
||||
class Connection {
|
||||
public:
|
||||
explicit Connection(const ConnectionOptions &opts);
|
||||
|
||||
Connection(const Connection &) = delete;
|
||||
Connection& operator=(const Connection &) = delete;
|
||||
|
||||
Connection(Connection &&) = default;
|
||||
Connection& operator=(Connection &&) = default;
|
||||
|
||||
~Connection() = default;
|
||||
|
||||
// Check if the connection is broken. Client needs to do this check
|
||||
// before sending some command to the connection. If it's broken,
|
||||
// client needs to reconnect it.
|
||||
bool broken() const noexcept {
|
||||
return _ctx->err != REDIS_OK;
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
_ctx->err = 0;
|
||||
}
|
||||
|
||||
void invalidate() noexcept {
|
||||
_ctx->err = REDIS_ERR;
|
||||
}
|
||||
|
||||
void reconnect();
|
||||
|
||||
auto create_time() const
|
||||
-> std::chrono::time_point<std::chrono::steady_clock> {
|
||||
return _create_time;
|
||||
}
|
||||
|
||||
auto last_active() const
|
||||
-> std::chrono::time_point<std::chrono::steady_clock> {
|
||||
return _last_active;
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
void send(const char *format, Args &&...args);
|
||||
|
||||
void send(int argc, const char **argv, const std::size_t *argv_len);
|
||||
|
||||
void send(CmdArgs &args);
|
||||
|
||||
ReplyUPtr recv(bool handle_error_reply = true);
|
||||
|
||||
const ConnectionOptions& options() const {
|
||||
return _opts;
|
||||
}
|
||||
|
||||
friend void swap(Connection &lhs, Connection &rhs) noexcept;
|
||||
|
||||
private:
|
||||
class Connector;
|
||||
|
||||
struct ContextDeleter {
|
||||
void operator()(redisContext *context) const {
|
||||
if (context != nullptr) {
|
||||
redisFree(context);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
using ContextUPtr = std::unique_ptr<redisContext, ContextDeleter>;
|
||||
|
||||
void _set_options();
|
||||
|
||||
void _auth();
|
||||
|
||||
void _select_db();
|
||||
|
||||
void _enable_readonly();
|
||||
|
||||
redisContext* _context();
|
||||
|
||||
ContextUPtr _ctx;
|
||||
|
||||
// The time that the connection is created.
|
||||
std::chrono::time_point<std::chrono::steady_clock> _create_time{};
|
||||
|
||||
// The time that the connection is created or the time that
|
||||
// the connection is recently used, i.e. `_context()` is called.
|
||||
std::chrono::time_point<std::chrono::steady_clock> _last_active{};
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
// TODO: define _tls_ctx before _ctx
|
||||
tls::TlsContextUPtr _tls_ctx;
|
||||
};
|
||||
|
||||
using ConnectionSPtr = std::shared_ptr<Connection>;
|
||||
|
||||
enum class Role {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// Inline implementaions.
|
||||
|
||||
template <typename ...Args>
|
||||
inline void Connection::send(const char *format, Args &&...args) {
|
||||
auto ctx = _context();
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
if (redisAppendCommand(ctx,
|
||||
format,
|
||||
std::forward<Args>(args)...) != REDIS_OK) {
|
||||
throw_error(*ctx, "Failed to send command");
|
||||
}
|
||||
|
||||
assert(!broken());
|
||||
}
|
||||
|
||||
inline redisContext* Connection::_context() {
|
||||
_last_active = std::chrono::steady_clock::now();
|
||||
|
||||
return _ctx.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include "connection.h"
|
||||
#include "sentinel.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
struct ConnectionPoolOptions {
|
||||
// Max number of connections, including both in-use and idle ones.
|
||||
std::size_t size = 1;
|
||||
|
||||
// Max time to wait for a connection. 0ms means client waits forever.
|
||||
std::chrono::milliseconds wait_timeout{0};
|
||||
|
||||
// Max lifetime of a connection. 0ms means we never expire the connection.
|
||||
std::chrono::milliseconds connection_lifetime{0};
|
||||
|
||||
// Max idle time of a connection. 0ms means we never expire the connection.
|
||||
std::chrono::milliseconds connection_idle_time{0};
|
||||
};
|
||||
|
||||
class ConnectionPool {
|
||||
public:
|
||||
ConnectionPool(const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
ConnectionPool(SimpleSentinel sentinel,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
ConnectionPool() = default;
|
||||
|
||||
ConnectionPool(ConnectionPool &&that);
|
||||
ConnectionPool& operator=(ConnectionPool &&that);
|
||||
|
||||
ConnectionPool(const ConnectionPool &) = delete;
|
||||
ConnectionPool& operator=(const ConnectionPool &) = delete;
|
||||
|
||||
~ConnectionPool() = default;
|
||||
|
||||
// Fetch a connection from pool.
|
||||
Connection fetch();
|
||||
|
||||
ConnectionOptions connection_options();
|
||||
|
||||
void release(Connection connection);
|
||||
|
||||
// Create a new connection.
|
||||
Connection create();
|
||||
|
||||
ConnectionPool clone();
|
||||
|
||||
private:
|
||||
void _move(ConnectionPool &&that);
|
||||
|
||||
// NOT thread-safe
|
||||
Connection _create();
|
||||
|
||||
Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked);
|
||||
|
||||
Connection _fetch();
|
||||
|
||||
void _wait_for_connection(std::unique_lock<std::mutex> &lock);
|
||||
|
||||
bool _need_reconnect(const Connection &connection,
|
||||
const std::chrono::milliseconds &connection_lifetime,
|
||||
const std::chrono::milliseconds &connection_idle_time) const;
|
||||
|
||||
void _update_connection_opts(const std::string &host, int port) {
|
||||
_opts.host = host;
|
||||
_opts.port = port;
|
||||
}
|
||||
|
||||
bool _role_changed(const ConnectionOptions &opts) const {
|
||||
return opts.port != _opts.port || opts.host != _opts.host;
|
||||
}
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
ConnectionPoolOptions _pool_opts;
|
||||
|
||||
std::deque<Connection> _pool;
|
||||
|
||||
std::size_t _used_connections = 0;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
std::condition_variable _cv;
|
||||
|
||||
SimpleSentinel _sentinel;
|
||||
};
|
||||
|
||||
using ConnectionPoolSPtr = std::shared_ptr<ConnectionPool>;
|
||||
|
||||
class SafeConnection {
|
||||
public:
|
||||
explicit SafeConnection(ConnectionPool &pool) : _pool(pool), _connection(_pool.fetch()) {
|
||||
assert(!_connection.broken());
|
||||
}
|
||||
|
||||
SafeConnection(const SafeConnection &) = delete;
|
||||
SafeConnection& operator=(const SafeConnection &) = delete;
|
||||
|
||||
SafeConnection(SafeConnection &&) = delete;
|
||||
SafeConnection& operator=(SafeConnection &&) = delete;
|
||||
|
||||
~SafeConnection() {
|
||||
_pool.release(std::move(_connection));
|
||||
}
|
||||
|
||||
Connection& connection() {
|
||||
return _connection;
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionPool &_pool;
|
||||
Connection _connection;
|
||||
};
|
||||
|
||||
// NOTE: This class is similar to `SafeConnection`.
|
||||
// The difference is that `SafeConnection` tries to avoid copying a std::shared_ptr.
|
||||
class GuardedConnection {
|
||||
public:
|
||||
explicit GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool),
|
||||
_connection(_pool->fetch()) {
|
||||
assert(!_connection.broken());
|
||||
}
|
||||
|
||||
GuardedConnection(const GuardedConnection &) = delete;
|
||||
GuardedConnection& operator=(const GuardedConnection &) = delete;
|
||||
|
||||
GuardedConnection(GuardedConnection &&) = default;
|
||||
GuardedConnection& operator=(GuardedConnection &&) = default;
|
||||
|
||||
~GuardedConnection() {
|
||||
// If `GuardedConnection` has been moved, `_pool` will be nullptr.
|
||||
if (_pool) {
|
||||
_pool->release(std::move(_connection));
|
||||
}
|
||||
}
|
||||
|
||||
Connection& connection() {
|
||||
return _connection;
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionPoolSPtr _pool;
|
||||
Connection _connection;
|
||||
};
|
||||
|
||||
using GuardedConnectionSPtr = std::shared_ptr<GuardedConnection>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
@@ -0,0 +1,46 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#define REDIS_PLUS_PLUS_HAS_OPTIONAL
|
||||
|
||||
#define REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
using StringView = std::string_view;
|
||||
|
||||
template <typename T>
|
||||
using Optional = std::optional<T>;
|
||||
|
||||
template <typename ...Args>
|
||||
using Variant = std::variant<Args...>;
|
||||
|
||||
using Monostate = std::monostate;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
@@ -0,0 +1,166 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <hiredis/hiredis.h>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum ReplyErrorType {
|
||||
ERR,
|
||||
MOVED,
|
||||
ASK
|
||||
};
|
||||
|
||||
class Error : public std::exception {
|
||||
public:
|
||||
explicit Error(const std::string &msg) : _msg(msg) {}
|
||||
|
||||
Error(const Error &) = default;
|
||||
Error& operator=(const Error &) = default;
|
||||
|
||||
Error(Error &&) = default;
|
||||
Error& operator=(Error &&) = default;
|
||||
|
||||
virtual ~Error() override = default;
|
||||
|
||||
virtual const char* what() const noexcept override {
|
||||
return _msg.data();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _msg;
|
||||
};
|
||||
|
||||
class IoError : public Error {
|
||||
public:
|
||||
explicit IoError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
IoError(const IoError &) = default;
|
||||
IoError& operator=(const IoError &) = default;
|
||||
|
||||
IoError(IoError &&) = default;
|
||||
IoError& operator=(IoError &&) = default;
|
||||
|
||||
virtual ~IoError() override = default;
|
||||
};
|
||||
|
||||
class TimeoutError : public IoError {
|
||||
public:
|
||||
explicit TimeoutError(const std::string &msg) : IoError(msg) {}
|
||||
|
||||
TimeoutError(const TimeoutError &) = default;
|
||||
TimeoutError& operator=(const TimeoutError &) = default;
|
||||
|
||||
TimeoutError(TimeoutError &&) = default;
|
||||
TimeoutError& operator=(TimeoutError &&) = default;
|
||||
|
||||
virtual ~TimeoutError() override = default;
|
||||
};
|
||||
|
||||
class ClosedError : public Error {
|
||||
public:
|
||||
explicit ClosedError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ClosedError(const ClosedError &) = default;
|
||||
ClosedError& operator=(const ClosedError &) = default;
|
||||
|
||||
ClosedError(ClosedError &&) = default;
|
||||
ClosedError& operator=(ClosedError &&) = default;
|
||||
|
||||
virtual ~ClosedError() override = default;
|
||||
};
|
||||
|
||||
class ProtoError : public Error {
|
||||
public:
|
||||
explicit ProtoError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ProtoError(const ProtoError &) = default;
|
||||
ProtoError& operator=(const ProtoError &) = default;
|
||||
|
||||
ProtoError(ProtoError &&) = default;
|
||||
ProtoError& operator=(ProtoError &&) = default;
|
||||
|
||||
virtual ~ProtoError() override = default;
|
||||
};
|
||||
|
||||
class OomError : public Error {
|
||||
public:
|
||||
explicit OomError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
OomError(const OomError &) = default;
|
||||
OomError& operator=(const OomError &) = default;
|
||||
|
||||
OomError(OomError &&) = default;
|
||||
OomError& operator=(OomError &&) = default;
|
||||
|
||||
virtual ~OomError() override = default;
|
||||
};
|
||||
|
||||
class ReplyError : public Error {
|
||||
public:
|
||||
explicit ReplyError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ReplyError(const ReplyError &) = default;
|
||||
ReplyError& operator=(const ReplyError &) = default;
|
||||
|
||||
ReplyError(ReplyError &&) = default;
|
||||
ReplyError& operator=(ReplyError &&) = default;
|
||||
|
||||
virtual ~ReplyError() override = default;
|
||||
};
|
||||
|
||||
class WatchError : public Error {
|
||||
public:
|
||||
explicit WatchError() : Error("Watched key has been modified") {}
|
||||
|
||||
WatchError(const WatchError &) = default;
|
||||
WatchError& operator=(const WatchError &) = default;
|
||||
|
||||
WatchError(WatchError &&) = default;
|
||||
WatchError& operator=(WatchError &&) = default;
|
||||
|
||||
virtual ~WatchError() override = default;
|
||||
};
|
||||
|
||||
|
||||
// MovedError and AskError are defined in shards.h
|
||||
class MovedError;
|
||||
|
||||
class AskError;
|
||||
|
||||
void throw_error(const redisContext &context, const std::string &err_info);
|
||||
|
||||
void throw_error(const redisReply &reply);
|
||||
|
||||
template <typename Input>
|
||||
inline void range_check(const char *cmd, Input first, Input last) {
|
||||
if (first == last) {
|
||||
throw Error(std::string(cmd) + ": no key specified");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
@@ -0,0 +1,49 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H
|
||||
#define SEWENEW_REDISPLUSPLUS_PIPELINE_H
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "connection.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class PipelineImpl {
|
||||
public:
|
||||
template <typename Cmd, typename ...Args>
|
||||
void command(Connection &connection, Cmd cmd, Args &&...args) {
|
||||
assert(!connection.broken());
|
||||
|
||||
cmd(connection, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
void discard(Connection &connection, std::size_t /*cmd_num*/) {
|
||||
// Reconnect to Redis to discard all commands.
|
||||
connection.reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H
|
||||
+2013
File diff suppressed because it is too large.
Load diff
+275
@@ -0,0 +1,275 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
|
||||
#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
template <typename Impl>
|
||||
template <typename ...Args>
|
||||
QueuedRedis<Impl>::QueuedRedis(const ConnectionPoolSPtr &pool,
|
||||
bool new_connection,
|
||||
Args &&...args) :
|
||||
_new_connection(new_connection),
|
||||
_impl(std::forward<Args>(args)...) {
|
||||
assert(pool);
|
||||
|
||||
if (_new_connection) {
|
||||
_connection_pool = std::make_shared<ConnectionPool>(pool->clone());
|
||||
} else {
|
||||
// Create a connection from the origin pool.
|
||||
_connection_pool = pool;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
QueuedRedis<Impl>::~QueuedRedis() {
|
||||
try {
|
||||
_clean_up();
|
||||
} catch (const Error &e) {
|
||||
// Ensure the destructor does not throw
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
Redis QueuedRedis<Impl>::redis() {
|
||||
_sanity_check();
|
||||
|
||||
assert(_guarded_connection);
|
||||
|
||||
return Redis(_guarded_connection);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename Cmd, typename ...Args>
|
||||
auto QueuedRedis<Impl>::command(Cmd cmd, Args &&...args)
|
||||
-> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value,
|
||||
QueuedRedis<Impl>&>::type {
|
||||
try {
|
||||
_sanity_check();
|
||||
|
||||
_impl.command(_connection(), cmd, std::forward<Args>(args)...);
|
||||
|
||||
++_cmd_num;
|
||||
} catch (const Error &e) {
|
||||
_invalidate();
|
||||
throw;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename ...Args>
|
||||
QueuedRedis<Impl>& QueuedRedis<Impl>::command(const StringView &cmd_name, Args &&...args) {
|
||||
auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) {
|
||||
CmdArgs cmd_args;
|
||||
cmd_args.append(cmd_name, std::forward<Args>(args)...);
|
||||
connection.send(cmd_args);
|
||||
};
|
||||
|
||||
return command(cmd, cmd_name, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename Input>
|
||||
auto QueuedRedis<Impl>::command(Input first, Input last)
|
||||
-> typename std::enable_if<IsIter<Input>::value, QueuedRedis<Impl>&>::type {
|
||||
if (first == last) {
|
||||
throw Error("command: empty range");
|
||||
}
|
||||
|
||||
auto cmd = [](Connection &connection, Input first, Input last) {
|
||||
CmdArgs cmd_args;
|
||||
while (first != last) {
|
||||
cmd_args.append(*first);
|
||||
++first;
|
||||
}
|
||||
connection.send(cmd_args);
|
||||
};
|
||||
|
||||
return command(cmd, first, last);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
QueuedReplies QueuedRedis<Impl>::exec() {
|
||||
try {
|
||||
_sanity_check();
|
||||
|
||||
auto replies = _impl.exec(_connection(), _cmd_num);
|
||||
|
||||
_rewrite_replies(replies);
|
||||
|
||||
_reset();
|
||||
|
||||
return QueuedReplies(std::move(replies));
|
||||
} catch (const WatchError &e) {
|
||||
// In this case, we only clear some states and keep the connection,
|
||||
// so that user can retry the transaction.
|
||||
_reset(false);
|
||||
throw;
|
||||
} catch (const Error &e) {
|
||||
_invalidate();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::discard() {
|
||||
try {
|
||||
_sanity_check();
|
||||
|
||||
_impl.discard(_connection(), _cmd_num);
|
||||
|
||||
_reset();
|
||||
} catch (const Error &e) {
|
||||
_invalidate();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
Connection& QueuedRedis<Impl>::_connection() {
|
||||
assert(_valid);
|
||||
|
||||
if (!_guarded_connection) {
|
||||
_guarded_connection = std::make_shared<GuardedConnection>(_connection_pool);
|
||||
}
|
||||
|
||||
return _guarded_connection->connection();
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_sanity_check() {
|
||||
if (!_valid) {
|
||||
throw Error("Not in valid state");
|
||||
}
|
||||
|
||||
if (_connection().broken()) {
|
||||
throw Error("Connection is broken");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
inline void QueuedRedis<Impl>::_reset(bool reset_connection) {
|
||||
if (reset_connection && !_new_connection) {
|
||||
_return_connection();
|
||||
}
|
||||
|
||||
_cmd_num = 0;
|
||||
|
||||
_set_cmd_indexes.clear();
|
||||
|
||||
_empty_array_cmd_indexes.clear();
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
inline void QueuedRedis<Impl>::_return_connection() {
|
||||
if (_guarded_connection.use_count() == 1) {
|
||||
// If no one else holding the connection, return it back to pool.
|
||||
// Instead, if some other `Redis` object holds the connection,
|
||||
// e.g. `auto redis = transaction.redis();`, we cannot return the connection.
|
||||
_guarded_connection.reset();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_invalidate() {
|
||||
_valid = false;
|
||||
|
||||
_clean_up();
|
||||
|
||||
_reset();
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_clean_up() {
|
||||
if (_guarded_connection && !_new_connection) {
|
||||
// Something bad happened, we need to close the current connection
|
||||
// before returning it back to pool.
|
||||
_guarded_connection->connection().invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void QueuedRedis<Impl>::_rewrite_replies(std::vector<ReplyUPtr> &replies) const {
|
||||
_rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies);
|
||||
|
||||
_rewrite_replies(_empty_array_cmd_indexes, reply::rewrite_empty_array_reply, replies);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
template <typename Func>
|
||||
void QueuedRedis<Impl>::_rewrite_replies(const std::vector<std::size_t> &indexes,
|
||||
Func rewriter,
|
||||
std::vector<ReplyUPtr> &replies) const {
|
||||
for (auto idx : indexes) {
|
||||
assert(idx < replies.size());
|
||||
|
||||
auto &reply = replies[idx];
|
||||
|
||||
assert(reply);
|
||||
|
||||
rewriter(*reply);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::size_t QueuedReplies::size() const {
|
||||
return _replies.size();
|
||||
}
|
||||
|
||||
inline redisReply& QueuedReplies::get(std::size_t idx) {
|
||||
_index_check(idx);
|
||||
|
||||
auto &reply = _replies[idx];
|
||||
|
||||
assert(reply);
|
||||
|
||||
if (reply::is_error(*reply)) {
|
||||
throw_error(*reply);
|
||||
}
|
||||
|
||||
return *reply;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
inline Result QueuedReplies::get(std::size_t idx) {
|
||||
auto &reply = get(idx);
|
||||
|
||||
return reply::parse<Result>(reply);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
inline void QueuedReplies::get(std::size_t idx, Output output) {
|
||||
auto &reply = get(idx);
|
||||
|
||||
reply::to_array(reply, output);
|
||||
}
|
||||
|
||||
inline void QueuedReplies::_index_check(std::size_t idx) const {
|
||||
if (idx >= size()) {
|
||||
throw Error("Out of range");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
|
||||
@@ -0,0 +1,25 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
|
||||
|
||||
#include "redis.h"
|
||||
#include "redis_cluster.h"
|
||||
#include "queued_redis.h"
|
||||
#include "sentinel.h"
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1439
File diff suppressed because it is too large.
Load diff
+1403
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,435 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H
|
||||
#define SEWENEW_REDISPLUSPLUS_REPLY_H
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "errors.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
struct ReplyDeleter {
|
||||
void operator()(redisReply *reply) const {
|
||||
if (reply != nullptr) {
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ReplyUPtr = std::unique_ptr<redisReply, ReplyDeleter>;
|
||||
|
||||
namespace reply {
|
||||
|
||||
template <typename T>
|
||||
struct ParseTag {};
|
||||
|
||||
template <typename T>
|
||||
inline T parse(redisReply &reply) {
|
||||
return parse(ParseTag<T>(), reply);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T parse_leniently(redisReply &reply);
|
||||
|
||||
void parse(ParseTag<void>, redisReply &reply);
|
||||
|
||||
std::string parse(ParseTag<std::string>, redisReply &reply);
|
||||
|
||||
long long parse(ParseTag<long long>, redisReply &reply);
|
||||
|
||||
double parse(ParseTag<double>, redisReply &reply);
|
||||
|
||||
bool parse(ParseTag<bool>, redisReply &reply);
|
||||
|
||||
template <typename T>
|
||||
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply);
|
||||
|
||||
template <typename T, typename U>
|
||||
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply);
|
||||
|
||||
template <typename ...Args>
|
||||
std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply);
|
||||
|
||||
#ifdef REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
inline Monostate parse(ParseTag<Monostate>, redisReply &) {
|
||||
// Just ignore the reply
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
Variant<Args...> parse(ParseTag<Variant<Args...>>, redisReply &reply);
|
||||
|
||||
#endif
|
||||
|
||||
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type = 0>
|
||||
T parse(ParseTag<T>, redisReply &reply);
|
||||
|
||||
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type = 0>
|
||||
T parse(ParseTag<T>, redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
long long parse_scan_reply(redisReply &reply, Output output);
|
||||
|
||||
inline bool is_error(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_ERROR;
|
||||
}
|
||||
|
||||
inline bool is_nil(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_NIL;
|
||||
}
|
||||
|
||||
inline bool is_string(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_STRING;
|
||||
}
|
||||
|
||||
inline bool is_status(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_STATUS;
|
||||
}
|
||||
|
||||
inline bool is_integer(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_INTEGER;
|
||||
}
|
||||
|
||||
inline bool is_array(redisReply &reply) {
|
||||
return reply.type == REDIS_REPLY_ARRAY;
|
||||
}
|
||||
|
||||
std::string to_status(redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
void to_array(redisReply &reply, Output output);
|
||||
|
||||
// Rewrite set reply to bool type
|
||||
void rewrite_set_reply(redisReply &reply);
|
||||
|
||||
// Some command might return an empty array reply as a nil reply,
|
||||
// e.g. georadius, zpopmin, zpopmax. In this case, we rewrite the
|
||||
// reply to a nil reply.
|
||||
void rewrite_empty_array_reply(redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
auto parse_xpending_reply(redisReply &reply, Output output)
|
||||
-> std::tuple<long long, OptionalString, OptionalString>;
|
||||
|
||||
}
|
||||
|
||||
// Inline implementations.
|
||||
|
||||
namespace reply {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Output>
|
||||
void to_array(redisReply &reply, Output output) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
if (reply.element == nullptr) {
|
||||
// Empty array.
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
|
||||
auto *sub_reply = reply.element[idx];
|
||||
if (sub_reply == nullptr) {
|
||||
throw ProtoError("Null array element reply");
|
||||
}
|
||||
|
||||
*output = parse<typename IterType<Output>::type>(*sub_reply);
|
||||
|
||||
++output;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_flat_array(redisReply &reply);
|
||||
|
||||
template <typename Output>
|
||||
void to_flat_array(redisReply &reply, Output output) {
|
||||
if (reply.element == nullptr) {
|
||||
// Empty array.
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply.elements % 2 != 0) {
|
||||
throw ProtoError("Not string pair array reply");
|
||||
}
|
||||
|
||||
for (std::size_t idx = 0; idx != reply.elements; idx += 2) {
|
||||
auto *key_reply = reply.element[idx];
|
||||
auto *val_reply = reply.element[idx + 1];
|
||||
if (key_reply == nullptr || val_reply == nullptr) {
|
||||
throw ProtoError("Null string array reply");
|
||||
}
|
||||
|
||||
using Pair = typename IterType<Output>::type;
|
||||
using FirstType = typename std::decay<typename Pair::first_type>::type;
|
||||
using SecondType = typename std::decay<typename Pair::second_type>::type;
|
||||
*output = std::make_pair(parse<FirstType>(*key_reply),
|
||||
parse<SecondType>(*val_reply));
|
||||
|
||||
++output;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
void to_array(std::true_type, redisReply &reply, Output output) {
|
||||
if (is_flat_array(reply)) {
|
||||
to_flat_array(reply, output);
|
||||
} else {
|
||||
to_array(reply, output);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
void to_array(std::false_type, redisReply &reply, Output output) {
|
||||
to_array(reply, output);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::tuple<T> parse_tuple(redisReply **reply, std::size_t idx) {
|
||||
assert(reply != nullptr);
|
||||
|
||||
auto *sub_reply = reply[idx];
|
||||
if (sub_reply == nullptr) {
|
||||
throw ProtoError("Null reply");
|
||||
}
|
||||
|
||||
return std::make_tuple(parse<T>(*sub_reply));
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
auto parse_tuple(redisReply **reply, std::size_t idx) ->
|
||||
typename std::enable_if<sizeof...(Args) != 0, std::tuple<T, Args...>>::type {
|
||||
assert(reply != nullptr);
|
||||
|
||||
return std::tuple_cat(parse_tuple<T>(reply, idx),
|
||||
parse_tuple<Args...>(reply, idx + 1));
|
||||
}
|
||||
|
||||
#ifdef REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
template <typename T>
|
||||
Variant<T> parse_variant(redisReply &reply) {
|
||||
return parse<T>(reply);
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
auto parse_variant(redisReply &reply) ->
|
||||
typename std::enable_if<sizeof...(Args) != 0, Variant<T, Args...>>::type {
|
||||
auto return_var = [](auto &&arg) {
|
||||
return Variant<T, Args...>(std::move(arg));
|
||||
};
|
||||
|
||||
try {
|
||||
return std::visit(return_var, parse_variant<T>(reply));
|
||||
} catch (const ProtoError &) {
|
||||
return std::visit(return_var, parse_variant<Args...>(reply));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T parse_leniently(redisReply &reply) {
|
||||
if (is_array(reply) && reply.elements == 1) {
|
||||
if (reply.element == nullptr) {
|
||||
throw ProtoError("null array reply");
|
||||
}
|
||||
|
||||
auto *ele = reply.element[0];
|
||||
if (ele != nullptr) {
|
||||
return parse<T>(*ele);
|
||||
} // else fall through
|
||||
}
|
||||
|
||||
return parse<T>(reply);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
|
||||
if (reply::is_nil(reply)) {
|
||||
// Because of a GCC bug, we cannot return {} for -std=c++17
|
||||
// Refer to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86465
|
||||
#if defined REDIS_PLUS_PLUS_HAS_OPTIONAL
|
||||
return std::nullopt;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
return Optional<T>(parse<T>(reply));
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
if (reply.elements != 2) {
|
||||
throw ProtoError("NOT key-value PAIR reply");
|
||||
}
|
||||
|
||||
if (reply.element == nullptr) {
|
||||
throw ProtoError("Null PAIR reply");
|
||||
}
|
||||
|
||||
auto *first = reply.element[0];
|
||||
auto *second = reply.element[1];
|
||||
if (first == nullptr || second == nullptr) {
|
||||
throw ProtoError("Null pair reply");
|
||||
}
|
||||
|
||||
return std::make_pair(parse<typename std::decay<T>::type>(*first),
|
||||
parse<typename std::decay<U>::type>(*second));
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
std::tuple<Args...> parse(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)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
if (reply.elements != size) {
|
||||
throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements");
|
||||
}
|
||||
|
||||
if (reply.element == nullptr) {
|
||||
throw ProtoError("Null TUPLE reply");
|
||||
}
|
||||
|
||||
return detail::parse_tuple<Args...>(reply.element, 0);
|
||||
}
|
||||
|
||||
#ifdef REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
template <typename ...Args>
|
||||
Variant<Args...> parse(ParseTag<Variant<Args...>>, redisReply &reply) {
|
||||
return detail::parse_variant<Args...>(reply);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type>
|
||||
T parse(ParseTag<T>, redisReply &reply) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
T container;
|
||||
|
||||
to_array(reply, std::back_inserter(container));
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type>
|
||||
T parse(ParseTag<T>, redisReply &reply) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
T container;
|
||||
|
||||
to_array(reply, std::inserter(container, container.end()));
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
long long parse_scan_reply(redisReply &reply, Output output) {
|
||||
if (reply.elements != 2 || reply.element == nullptr) {
|
||||
throw ProtoError("Invalid scan reply");
|
||||
}
|
||||
|
||||
auto *cursor_reply = reply.element[0];
|
||||
auto *data_reply = reply.element[1];
|
||||
if (cursor_reply == nullptr || data_reply == nullptr) {
|
||||
throw ProtoError("Invalid cursor reply or data reply");
|
||||
}
|
||||
|
||||
auto cursor_str = reply::parse<std::string>(*cursor_reply);
|
||||
long long new_cursor = 0;
|
||||
try {
|
||||
new_cursor = std::stoll(cursor_str);
|
||||
} catch (const std::exception &e) {
|
||||
throw ProtoError("Invalid cursor reply: " + cursor_str);
|
||||
}
|
||||
|
||||
reply::to_array(*data_reply, output);
|
||||
|
||||
return new_cursor;
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
void to_array(redisReply &reply, Output output) {
|
||||
if (!is_array(reply)) {
|
||||
throw ProtoError("Expect ARRAY reply");
|
||||
}
|
||||
|
||||
detail::to_array(typename IsKvPairIter<Output>::type(), reply, output);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
auto parse_xpending_reply(redisReply &reply, Output output)
|
||||
-> std::tuple<long long, OptionalString, OptionalString> {
|
||||
if (!is_array(reply) || reply.elements != 4) {
|
||||
throw ProtoError("expect array reply with 4 elements");
|
||||
}
|
||||
|
||||
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
|
||||
if (reply.element[idx] == nullptr) {
|
||||
throw ProtoError("null array reply");
|
||||
}
|
||||
}
|
||||
|
||||
auto num = parse<long long>(*(reply.element[0]));
|
||||
auto start = parse<OptionalString>(*(reply.element[1]));
|
||||
auto end = parse<OptionalString>(*(reply.element[2]));
|
||||
|
||||
auto &entry_reply = *(reply.element[3]);
|
||||
if (!is_nil(entry_reply)) {
|
||||
to_array(entry_reply, output);
|
||||
}
|
||||
|
||||
return std::make_tuple(num, std::move(start), std::move(end));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H
|
||||
@@ -0,0 +1,141 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SENTINEL_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "connection.h"
|
||||
#include "shards.h"
|
||||
#include "reply.h"
|
||||
#include "tls.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
struct SentinelOptions {
|
||||
std::vector<std::pair<std::string, int>> nodes;
|
||||
|
||||
std::string password;
|
||||
|
||||
bool keep_alive = true;
|
||||
|
||||
std::chrono::milliseconds connect_timeout{100};
|
||||
|
||||
std::chrono::milliseconds socket_timeout{100};
|
||||
|
||||
std::chrono::milliseconds retry_interval{100};
|
||||
|
||||
std::size_t max_retry = 2;
|
||||
|
||||
tls::TlsOptions tls;
|
||||
};
|
||||
|
||||
class Sentinel {
|
||||
public:
|
||||
explicit Sentinel(const SentinelOptions &sentinel_opts);
|
||||
|
||||
Sentinel(const Sentinel &) = delete;
|
||||
Sentinel& operator=(const Sentinel &) = delete;
|
||||
|
||||
Sentinel(Sentinel &&) = delete;
|
||||
Sentinel& operator=(Sentinel &&) = delete;
|
||||
|
||||
~Sentinel() = default;
|
||||
|
||||
private:
|
||||
Connection master(const std::string &master_name, const ConnectionOptions &opts);
|
||||
|
||||
Connection slave(const std::string &master_name, const ConnectionOptions &opts);
|
||||
|
||||
class Iterator;
|
||||
|
||||
friend class SimpleSentinel;
|
||||
|
||||
std::list<ConnectionOptions> _parse_options(const SentinelOptions &opts) const;
|
||||
|
||||
Optional<Node> _get_master_addr_by_name(Connection &connection, const StringView &name);
|
||||
|
||||
std::vector<Node> _get_slave_addr_by_name(Connection &connection, const StringView &name);
|
||||
|
||||
Connection _connect_redis(const Node &node, ConnectionOptions opts);
|
||||
|
||||
Role _get_role(Connection &connection);
|
||||
|
||||
std::vector<Node> _parse_slave_info(redisReply &reply) const;
|
||||
|
||||
std::list<Connection> _healthy_sentinels;
|
||||
|
||||
std::list<ConnectionOptions> _broken_sentinels;
|
||||
|
||||
SentinelOptions _sentinel_opts;
|
||||
|
||||
std::mutex _mutex;
|
||||
};
|
||||
|
||||
class SimpleSentinel {
|
||||
public:
|
||||
SimpleSentinel(const std::shared_ptr<Sentinel> &sentinel,
|
||||
const std::string &master_name,
|
||||
Role role);
|
||||
|
||||
SimpleSentinel() = default;
|
||||
|
||||
SimpleSentinel(const SimpleSentinel &) = default;
|
||||
SimpleSentinel& operator=(const SimpleSentinel &) = default;
|
||||
|
||||
SimpleSentinel(SimpleSentinel &&) = default;
|
||||
SimpleSentinel& operator=(SimpleSentinel &&) = default;
|
||||
|
||||
~SimpleSentinel() = default;
|
||||
|
||||
explicit operator bool() const {
|
||||
return bool(_sentinel);
|
||||
}
|
||||
|
||||
Connection create(const ConnectionOptions &opts);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Sentinel> _sentinel;
|
||||
|
||||
std::string _master_name;
|
||||
|
||||
Role _role = Role::MASTER;
|
||||
};
|
||||
|
||||
class StopIterError : public Error {
|
||||
public:
|
||||
StopIterError() : Error("StopIterError") {}
|
||||
|
||||
StopIterError(const StopIterError &) = default;
|
||||
StopIterError& operator=(const StopIterError &) = default;
|
||||
|
||||
StopIterError(StopIterError &&) = default;
|
||||
StopIterError& operator=(StopIterError &&) = default;
|
||||
|
||||
virtual ~StopIterError() override = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H
|
||||
@@ -0,0 +1,115 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SHARDS_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
using Slot = std::size_t;
|
||||
|
||||
struct SlotRange {
|
||||
Slot min;
|
||||
Slot max;
|
||||
};
|
||||
|
||||
inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) {
|
||||
return lhs.max < rhs.max;
|
||||
}
|
||||
|
||||
struct Node {
|
||||
std::string host;
|
||||
int port;
|
||||
};
|
||||
|
||||
inline bool operator==(const Node &lhs, const Node &rhs) {
|
||||
return lhs.host == rhs.host && lhs.port == rhs.port;
|
||||
}
|
||||
|
||||
struct NodeHash {
|
||||
std::size_t operator()(const Node &node) const noexcept {
|
||||
auto host_hash = std::hash<std::string>{}(node.host);
|
||||
auto port_hash = std::hash<int>{}(node.port);
|
||||
return host_hash ^ (port_hash << 1);
|
||||
}
|
||||
};
|
||||
|
||||
using Shards = std::map<SlotRange, Node>;
|
||||
|
||||
class RedirectionError : public ReplyError {
|
||||
public:
|
||||
RedirectionError(const std::string &msg);
|
||||
|
||||
RedirectionError(const RedirectionError &) = default;
|
||||
RedirectionError& operator=(const RedirectionError &) = default;
|
||||
|
||||
RedirectionError(RedirectionError &&) = default;
|
||||
RedirectionError& operator=(RedirectionError &&) = default;
|
||||
|
||||
virtual ~RedirectionError() override = default;
|
||||
|
||||
Slot slot() const {
|
||||
return _slot;
|
||||
}
|
||||
|
||||
const Node& node() const {
|
||||
return _node;
|
||||
}
|
||||
|
||||
private:
|
||||
std::pair<Slot, Node> _parse_error(const std::string &msg) const;
|
||||
|
||||
Slot _slot = 0;
|
||||
Node _node;
|
||||
};
|
||||
|
||||
class MovedError : public RedirectionError {
|
||||
public:
|
||||
explicit MovedError(const std::string &msg) : RedirectionError(msg) {}
|
||||
|
||||
MovedError(const MovedError &) = default;
|
||||
MovedError& operator=(const MovedError &) = default;
|
||||
|
||||
MovedError(MovedError &&) = default;
|
||||
MovedError& operator=(MovedError &&) = default;
|
||||
|
||||
virtual ~MovedError() override = default;
|
||||
};
|
||||
|
||||
class AskError : public RedirectionError {
|
||||
public:
|
||||
explicit AskError(const std::string &msg) : RedirectionError(msg) {}
|
||||
|
||||
AskError(const AskError &) = default;
|
||||
AskError& operator=(const AskError &) = default;
|
||||
|
||||
AskError(AskError &&) = default;
|
||||
AskError& operator=(AskError &&) = default;
|
||||
|
||||
virtual ~AskError() override = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H
|
||||
@@ -0,0 +1,121 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <random>
|
||||
#include <memory>
|
||||
#include "reply.h"
|
||||
#include "connection_pool.h"
|
||||
#include "shards.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class ShardsPool {
|
||||
public:
|
||||
ShardsPool() = default;
|
||||
|
||||
ShardsPool(const ShardsPool &that) = delete;
|
||||
ShardsPool& operator=(const ShardsPool &that) = delete;
|
||||
|
||||
ShardsPool(ShardsPool &&that);
|
||||
ShardsPool& operator=(ShardsPool &&that);
|
||||
|
||||
~ShardsPool() = default;
|
||||
|
||||
ShardsPool(const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts,
|
||||
Role role);
|
||||
|
||||
// Fetch a connection by key.
|
||||
ConnectionPoolSPtr fetch(const StringView &key);
|
||||
|
||||
// Randomly pick a connection.
|
||||
ConnectionPoolSPtr fetch();
|
||||
|
||||
// Fetch a connection by node.
|
||||
ConnectionPoolSPtr fetch(const Node &node);
|
||||
|
||||
void update();
|
||||
|
||||
ConnectionOptions connection_options(const StringView &key);
|
||||
|
||||
ConnectionOptions connection_options();
|
||||
|
||||
Shards shards();
|
||||
|
||||
private:
|
||||
void _move(ShardsPool &&that);
|
||||
|
||||
void _init_pool(const Shards &shards);
|
||||
|
||||
Shards _cluster_slots(Connection &connection) const;
|
||||
|
||||
ReplyUPtr _cluster_slots_command(Connection &connection) const;
|
||||
|
||||
Shards _parse_reply(redisReply &reply) const;
|
||||
|
||||
Slot _parse_slot(redisReply *reply) const;
|
||||
|
||||
Node _parse_node(redisReply *reply) const;
|
||||
|
||||
std::pair<SlotRange, Node> _parse_slot_info(redisReply &reply) const;
|
||||
|
||||
// Get slot by key.
|
||||
std::size_t _slot(const StringView &key) const;
|
||||
|
||||
// Randomly pick a slot.
|
||||
std::size_t _slot() const;
|
||||
|
||||
// Get a random number between [min, max]
|
||||
std::size_t _random(std::size_t min, std::size_t max) const;
|
||||
|
||||
ConnectionPoolSPtr& _get_pool(Slot slot);
|
||||
|
||||
ConnectionPoolSPtr _fetch(Slot slot);
|
||||
|
||||
ConnectionOptions _connection_options(Slot slot);
|
||||
|
||||
using NodeMap = std::unordered_map<Node, ConnectionPoolSPtr, NodeHash>;
|
||||
|
||||
NodeMap::iterator _add_node(const Node &node);
|
||||
|
||||
ConnectionPoolOptions _pool_opts;
|
||||
|
||||
ConnectionOptions _connection_opts;
|
||||
|
||||
Shards _shards;
|
||||
|
||||
NodeMap _pools;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
Role _role = Role::MASTER;
|
||||
|
||||
static const std::size_t SHARDS = 16383;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
|
||||
@@ -0,0 +1,231 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
|
||||
#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "connection.h"
|
||||
#include "reply.h"
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
// @NOTE: Subscriber is NOT thread-safe.
|
||||
// Subscriber uses callbacks to handle messages. There are 6 kinds of messages:
|
||||
// 1) MESSAGE: message sent to a channel.
|
||||
// 2) PMESSAGE: message sent to channels of a given pattern.
|
||||
// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel.
|
||||
// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel.
|
||||
// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern.
|
||||
// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern.
|
||||
//
|
||||
// Use Subscriber::on_message(MsgCallback) to set the callback function for message of
|
||||
// *MESSAGE* type, and the callback interface is:
|
||||
// void (std::string channel, std::string msg)
|
||||
//
|
||||
// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of
|
||||
// *PMESSAGE* type, and the callback interface is:
|
||||
// void (std::string pattern, std::string channel, std::string msg)
|
||||
//
|
||||
// Messages of other types are called *META MESSAGE*, they have the same callback interface.
|
||||
// Use Subscriber::on_meta(MetaCallback) to set the callback function:
|
||||
// void (Subscriber::MsgType type, OptionalString channel, long long num)
|
||||
//
|
||||
// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to
|
||||
// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all
|
||||
// channels/patterns, *channel* will be null. So the second parameter of meta callback
|
||||
// is of type *OptionalString*.
|
||||
//
|
||||
// All these callback interfaces pass std::string by value, and you can take their ownership
|
||||
// (i.e. std::move) safely.
|
||||
//
|
||||
// If you don't set callback for a specific kind of message, Subscriber::consume() will
|
||||
// receive the message, and ignore it, i.e. no callback will be called.
|
||||
class Subscriber {
|
||||
public:
|
||||
Subscriber(const Subscriber &) = delete;
|
||||
Subscriber& operator=(const Subscriber &) = delete;
|
||||
|
||||
Subscriber(Subscriber &&) = default;
|
||||
Subscriber& operator=(Subscriber &&) = default;
|
||||
|
||||
~Subscriber() = default;
|
||||
|
||||
enum class MsgType {
|
||||
SUBSCRIBE,
|
||||
UNSUBSCRIBE,
|
||||
PSUBSCRIBE,
|
||||
PUNSUBSCRIBE,
|
||||
MESSAGE,
|
||||
PMESSAGE
|
||||
};
|
||||
|
||||
template <typename MsgCb>
|
||||
void on_message(MsgCb msg_callback);
|
||||
|
||||
template <typename PMsgCb>
|
||||
void on_pmessage(PMsgCb pmsg_callback);
|
||||
|
||||
template <typename MetaCb>
|
||||
void on_meta(MetaCb meta_callback);
|
||||
|
||||
void subscribe(const StringView &channel);
|
||||
|
||||
template <typename Input>
|
||||
void subscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void subscribe(std::initializer_list<T> channels) {
|
||||
subscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void unsubscribe();
|
||||
|
||||
void unsubscribe(const StringView &channel);
|
||||
|
||||
template <typename Input>
|
||||
void unsubscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void unsubscribe(std::initializer_list<T> channels) {
|
||||
unsubscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void psubscribe(const StringView &pattern);
|
||||
|
||||
template <typename Input>
|
||||
void psubscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void psubscribe(std::initializer_list<T> channels) {
|
||||
psubscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void punsubscribe();
|
||||
|
||||
void punsubscribe(const StringView &channel);
|
||||
|
||||
template <typename Input>
|
||||
void punsubscribe(Input first, Input last);
|
||||
|
||||
template <typename T>
|
||||
void punsubscribe(std::initializer_list<T> channels) {
|
||||
punsubscribe(channels.begin(), channels.end());
|
||||
}
|
||||
|
||||
void consume();
|
||||
|
||||
private:
|
||||
friend class Redis;
|
||||
|
||||
friend class RedisCluster;
|
||||
|
||||
explicit Subscriber(Connection connection);
|
||||
|
||||
MsgType _msg_type(redisReply *reply) const;
|
||||
|
||||
void _check_connection();
|
||||
|
||||
void _handle_message(redisReply &reply);
|
||||
|
||||
void _handle_pmessage(redisReply &reply);
|
||||
|
||||
void _handle_meta(MsgType type, redisReply &reply);
|
||||
|
||||
using MsgCallback = std::function<void (std::string channel, std::string msg)>;
|
||||
|
||||
using PatternMsgCallback = std::function<void (std::string pattern,
|
||||
std::string channel,
|
||||
std::string msg)>;
|
||||
|
||||
using MetaCallback = std::function<void (MsgType type,
|
||||
OptionalString channel,
|
||||
long long num)>;
|
||||
|
||||
using TypeIndex = std::unordered_map<std::string, MsgType>;
|
||||
static const TypeIndex _msg_type_index;
|
||||
|
||||
Connection _connection;
|
||||
|
||||
MsgCallback _msg_callback = nullptr;
|
||||
|
||||
PatternMsgCallback _pmsg_callback = nullptr;
|
||||
|
||||
MetaCallback _meta_callback = nullptr;
|
||||
};
|
||||
|
||||
template <typename MsgCb>
|
||||
void Subscriber::on_message(MsgCb msg_callback) {
|
||||
_msg_callback = msg_callback;
|
||||
}
|
||||
|
||||
template <typename PMsgCb>
|
||||
void Subscriber::on_pmessage(PMsgCb pmsg_callback) {
|
||||
_pmsg_callback = pmsg_callback;
|
||||
}
|
||||
|
||||
template <typename MetaCb>
|
||||
void Subscriber::on_meta(MetaCb meta_callback) {
|
||||
_meta_callback = meta_callback;
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::subscribe(Input first, Input last) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
_check_connection();
|
||||
|
||||
cmd::subscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::unsubscribe(Input first, Input last) {
|
||||
_check_connection();
|
||||
|
||||
cmd::unsubscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::psubscribe(Input first, Input last) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
_check_connection();
|
||||
|
||||
cmd::psubscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
void Subscriber::punsubscribe(Input first, Input last) {
|
||||
_check_connection();
|
||||
|
||||
cmd::punsubscribe_range(_connection, first, last);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
|
||||
@@ -0,0 +1,47 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2020 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_NO_TLS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_NO_TLS_H
|
||||
|
||||
#include <hiredis/hiredis.h>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
namespace tls {
|
||||
|
||||
struct TlsOptions {};
|
||||
|
||||
struct TlsContextUPtr {};
|
||||
|
||||
inline TlsContextUPtr secure_connection(redisContext &/*ctx*/, const TlsOptions &/*opts*/) {
|
||||
// Do nothing
|
||||
return {};
|
||||
}
|
||||
|
||||
inline bool enabled(const TlsOptions &/*opts*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_NO_TLS_H
|
||||
@@ -0,0 +1,77 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H
|
||||
#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "connection.h"
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class TransactionImpl {
|
||||
public:
|
||||
explicit TransactionImpl(bool piped) : _piped(piped) {}
|
||||
|
||||
template <typename Cmd, typename ...Args>
|
||||
void command(Connection &connection, Cmd cmd, Args &&...args);
|
||||
|
||||
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
void discard(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
private:
|
||||
void _open_transaction(Connection &connection);
|
||||
|
||||
void _close_transaction();
|
||||
|
||||
void _get_queued_reply(Connection &connection);
|
||||
|
||||
void _get_queued_replies(Connection &connection, std::size_t cmd_num);
|
||||
|
||||
std::vector<ReplyUPtr> _exec(Connection &connection);
|
||||
|
||||
void _discard(Connection &connection);
|
||||
|
||||
bool _in_transaction = false;
|
||||
|
||||
bool _piped;
|
||||
};
|
||||
|
||||
template <typename Cmd, typename ...Args>
|
||||
void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) {
|
||||
assert(!connection.broken());
|
||||
|
||||
if (!_in_transaction) {
|
||||
_open_transaction(connection);
|
||||
}
|
||||
|
||||
cmd(connection, std::forward<Args>(args)...);
|
||||
|
||||
if (!_piped) {
|
||||
_get_queued_reply(connection);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H
|
||||
@@ -0,0 +1,193 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_UTILS_H
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include "cxx_utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
using OptionalString = Optional<std::string>;
|
||||
|
||||
using OptionalLongLong = Optional<long long>;
|
||||
|
||||
using OptionalDouble = Optional<double>;
|
||||
|
||||
using OptionalStringPair = Optional<std::pair<std::string, std::string>>;
|
||||
|
||||
template <typename ...>
|
||||
struct IsKvPair : std::false_type {};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct IsKvPair<std::pair<T, U>> : std::true_type {};
|
||||
|
||||
template <typename ...>
|
||||
using Void = void;
|
||||
|
||||
template <typename T, typename U = Void<>>
|
||||
struct IsInserter : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
//struct IsInserter<T, Void<typename T::container_type>> : std::true_type {};
|
||||
struct IsInserter<T,
|
||||
typename std::enable_if<!std::is_void<typename T::container_type>::value>::type>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename Iter, typename T = Void<>>
|
||||
struct IterType {
|
||||
using type = typename std::iterator_traits<Iter>::value_type;
|
||||
};
|
||||
|
||||
template <typename Iter>
|
||||
//struct IterType<Iter, Void<typename Iter::container_type>> {
|
||||
struct IterType<Iter,
|
||||
//typename std::enable_if<std::is_void<typename Iter::value_type>::value>::type> {
|
||||
typename std::enable_if<IsInserter<Iter>::value>::type> {
|
||||
using type = typename std::decay<typename Iter::container_type::value_type>::type;
|
||||
};
|
||||
|
||||
template <typename Iter, typename T = Void<>>
|
||||
struct IsIter : std::false_type {};
|
||||
|
||||
template <typename Iter>
|
||||
struct IsIter<Iter, typename std::enable_if<IsInserter<Iter>::value>::type> : std::true_type {};
|
||||
|
||||
template <typename Iter>
|
||||
//struct IsIter<Iter, Void<typename std::iterator_traits<Iter>::iterator_category>>
|
||||
struct IsIter<Iter,
|
||||
typename std::enable_if<!std::is_void<
|
||||
typename std::iterator_traits<Iter>::value_type>::value>::type>
|
||||
: std::integral_constant<bool, !std::is_convertible<Iter, StringView>::value> {};
|
||||
|
||||
template <typename T>
|
||||
struct IsKvPairIter : IsKvPair<typename IterType<T>::type> {};
|
||||
|
||||
template <typename T, typename Tuple>
|
||||
struct TupleWithType : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct TupleWithType<T, std::tuple<>> : std::false_type {};
|
||||
|
||||
template <typename T, typename U, typename ...Args>
|
||||
struct TupleWithType<T, std::tuple<U, Args...>> : TupleWithType<T, std::tuple<Args...>> {};
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
struct TupleWithType<T, std::tuple<T, Args...>> : std::true_type {};
|
||||
|
||||
template <std::size_t ...Is>
|
||||
struct IndexSequence {};
|
||||
|
||||
template <std::size_t I, std::size_t ...Is>
|
||||
struct MakeIndexSequence : MakeIndexSequence<I - 1, I - 1, Is...> {};
|
||||
|
||||
template <std::size_t ...Is>
|
||||
struct MakeIndexSequence<0, Is...> : IndexSequence<Is...> {};
|
||||
|
||||
// NthType and NthValue are taken from
|
||||
// https://stackoverflow.com/questions/14261183
|
||||
template <std::size_t I, typename ...Args>
|
||||
struct NthType {};
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
struct NthType<0, Arg, Args...> {
|
||||
using type = Arg;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename Arg, typename ...Args>
|
||||
struct NthType<I, Arg, Args...> {
|
||||
using type = typename NthType<I - 1, Args...>::type;
|
||||
};
|
||||
|
||||
template <typename ...Args>
|
||||
struct LastType {
|
||||
using type = typename NthType<sizeof...(Args) - 1, Args...>::type;
|
||||
};
|
||||
|
||||
struct InvalidLastType {};
|
||||
|
||||
template <>
|
||||
struct LastType<> {
|
||||
using type = InvalidLastType;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename Arg, typename ...Args>
|
||||
auto NthValue(Arg &&arg, Args &&...)
|
||||
-> typename std::enable_if<(I == 0), decltype(std::forward<Arg>(arg))>::type {
|
||||
return std::forward<Arg>(arg);
|
||||
}
|
||||
|
||||
template <std::size_t I, typename Arg, typename ...Args>
|
||||
auto NthValue(Arg &&, Args &&...args)
|
||||
-> typename std::enable_if<(I > 0),
|
||||
decltype(std::forward<typename NthType<I, Arg, Args...>::type>(
|
||||
std::declval<typename NthType<I, Arg, Args...>::type>()))>::type {
|
||||
return std::forward<typename NthType<I, Arg, Args...>::type>(
|
||||
NthValue<I - 1>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
auto LastValue(Args &&...args)
|
||||
-> decltype(std::forward<typename LastType<Args...>::type>(
|
||||
std::declval<typename LastType<Args...>::type>())) {
|
||||
return std::forward<typename LastType<Args...>::type>(
|
||||
NthValue<sizeof...(Args) - 1>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T, typename = Void<>>
|
||||
struct HasPushBack : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct HasPushBack<T,
|
||||
typename std::enable_if<
|
||||
std::is_void<decltype(
|
||||
std::declval<T>().push_back(std::declval<typename T::value_type>())
|
||||
)>::value>::type> : std::true_type {};
|
||||
|
||||
template <typename T, typename = Void<>>
|
||||
struct HasInsert : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct HasInsert<T,
|
||||
typename std::enable_if<
|
||||
std::is_same<
|
||||
decltype(std::declval<T>().insert(std::declval<typename T::const_iterator>(),
|
||||
std::declval<typename T::value_type>())),
|
||||
typename T::iterator>::value>::type> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct IsSequenceContainer
|
||||
: std::integral_constant<bool,
|
||||
HasPushBack<T>::value
|
||||
&& !std::is_same<typename std::decay<T>::type, std::string>::value> {};
|
||||
|
||||
template <typename T>
|
||||
struct IsAssociativeContainer
|
||||
: std::integral_constant<bool,
|
||||
HasInsert<T>::value && !HasPushBack<T>::value> {};
|
||||
|
||||
uint16_t crc16(const char *buf, int len);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H
|
||||
@@ -0,0 +1,12 @@
|
||||
prefix=/home/grant/dev/ZeroTierOne/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: redis++
|
||||
Description: This is a Redis client, based on hiredis and written in C++11. It supports scritpting, pub/sub, pipeline, transaction, Redis Cluster, Redis Sentinel, connection pool, ACL, SSL and thread safety.
|
||||
Version: 1.3.3
|
||||
URL: https://github.com/sewenew/redis-plus-plus
|
||||
Requires: hiredis
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lredis++
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# This is a basic version file for the Config-mode of find_package().
|
||||
# It is used by write_basic_package_version_file() as input file for configure_file()
|
||||
# to create a version-file which can be installed along a config.cmake file.
|
||||
#
|
||||
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
|
||||
# the requested version string are exactly the same and it sets
|
||||
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version.
|
||||
# The variable CVF_VERSION must be set before calling configure_file().
|
||||
|
||||
set(PACKAGE_VERSION "1.3.3")
|
||||
|
||||
if (PACKAGE_FIND_VERSION_RANGE)
|
||||
# Package version must be in the requested version range
|
||||
if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN)
|
||||
OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX)
|
||||
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX)))
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
else()
|
||||
if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
|
||||
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
|
||||
set(PACKAGE_VERSION_EXACT TRUE)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# if the installed project requested no architecture check, don't perform the check
|
||||
if("FALSE")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
|
||||
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8")
|
||||
math(EXPR installedBits "8 * 8")
|
||||
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
|
||||
set(PACKAGE_VERSION_UNSUITABLE TRUE)
|
||||
endif()
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
|
||||
####### Any changes to this file will be overwritten by the next CMake run ####
|
||||
####### The input file was redis++-config.cmake.in ########
|
||||
|
||||
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
|
||||
|
||||
macro(set_and_check _var _file)
|
||||
set(${_var} "${_file}")
|
||||
if(NOT EXISTS "${_file}")
|
||||
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(check_required_components _NAME)
|
||||
foreach(comp ${${_NAME}_FIND_COMPONENTS})
|
||||
if(NOT ${_NAME}_${comp}_FOUND)
|
||||
if(${_NAME}_FIND_REQUIRED_${comp})
|
||||
set(${_NAME}_FOUND FALSE)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
####################################################################################
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
string(REPLACE "," ";" REDIS_PLUS_PLUS_DEPENDS_LIST hiredis)
|
||||
foreach(REDIS_PLUS_PLUS_DEP ${REDIS_PLUS_PLUS_DEPENDS_LIST})
|
||||
find_dependency(${REDIS_PLUS_PLUS_DEP} REQUIRED)
|
||||
endforeach()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/redis++-targets.cmake")
|
||||
|
||||
check_required_components(redis++)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file for configuration "Release".
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Import target "redis++::redis++_static" for configuration "Release"
|
||||
set_property(TARGET redis++::redis++_static APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(redis++::redis++_static PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
|
||||
IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libredis++.a"
|
||||
)
|
||||
|
||||
list(APPEND _IMPORT_CHECK_TARGETS redis++::redis++_static )
|
||||
list(APPEND _IMPORT_CHECK_FILES_FOR_redis++::redis++_static "${_IMPORT_PREFIX}/lib/libredis++.a" )
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# Generated by CMake
|
||||
|
||||
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.6)
|
||||
message(FATAL_ERROR "CMake >= 2.6.0 required")
|
||||
endif()
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(VERSION 2.6...3.20)
|
||||
#----------------------------------------------------------------
|
||||
# Generated CMake target import file.
|
||||
#----------------------------------------------------------------
|
||||
|
||||
# Commands may need to know the format version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION 1)
|
||||
|
||||
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
|
||||
set(_targetsDefined)
|
||||
set(_targetsNotDefined)
|
||||
set(_expectedTargets)
|
||||
foreach(_expectedTarget redis++::redis++_static)
|
||||
list(APPEND _expectedTargets ${_expectedTarget})
|
||||
if(NOT TARGET ${_expectedTarget})
|
||||
list(APPEND _targetsNotDefined ${_expectedTarget})
|
||||
endif()
|
||||
if(TARGET ${_expectedTarget})
|
||||
list(APPEND _targetsDefined ${_expectedTarget})
|
||||
endif()
|
||||
endforeach()
|
||||
if("${_targetsDefined}" STREQUAL "${_expectedTargets}")
|
||||
unset(_targetsDefined)
|
||||
unset(_targetsNotDefined)
|
||||
unset(_expectedTargets)
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
return()
|
||||
endif()
|
||||
if(NOT "${_targetsDefined}" STREQUAL "")
|
||||
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n")
|
||||
endif()
|
||||
unset(_targetsDefined)
|
||||
unset(_targetsNotDefined)
|
||||
unset(_expectedTargets)
|
||||
|
||||
|
||||
# Compute the installation prefix relative to this file.
|
||||
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
|
||||
if(_IMPORT_PREFIX STREQUAL "/")
|
||||
set(_IMPORT_PREFIX "")
|
||||
endif()
|
||||
|
||||
# Create imported target redis++::redis++_static
|
||||
add_library(redis++::redis++_static STATIC IMPORTED)
|
||||
|
||||
set_target_properties(redis++::redis++_static PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include"
|
||||
)
|
||||
|
||||
# Load information for each installed configuration.
|
||||
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
file(GLOB CONFIG_FILES "${_DIR}/redis++-targets-*.cmake")
|
||||
foreach(f ${CONFIG_FILES})
|
||||
include(${f})
|
||||
endforeach()
|
||||
|
||||
# Cleanup temporary variables.
|
||||
set(_IMPORT_PREFIX)
|
||||
|
||||
# Loop over all imported files and verify that they actually exist
|
||||
foreach(target ${_IMPORT_CHECK_TARGETS} )
|
||||
foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} )
|
||||
if(NOT EXISTS "${file}" )
|
||||
message(FATAL_ERROR "The imported target \"${target}\" references the file
|
||||
\"${file}\"
|
||||
but this file does not exist. Possible reasons include:
|
||||
* The file was deleted, renamed, or moved to another location.
|
||||
* An install or uninstall procedure did not complete successfully.
|
||||
* The installation package was faulty and contained
|
||||
\"${CMAKE_CURRENT_LIST_FILE}\"
|
||||
but not all the files it references.
|
||||
")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_IMPORT_CHECK_FILES_FOR_${target})
|
||||
endforeach()
|
||||
unset(_IMPORT_CHECK_TARGETS)
|
||||
|
||||
# This file does not depend on other imported targets which have
|
||||
# been exported from the same project but in a separate export set.
|
||||
|
||||
# Commands beyond this point should not need to know the version.
|
||||
set(CMAKE_IMPORT_FILE_VERSION)
|
||||
cmake_policy(POP)
|
||||
@@ -0,0 +1,448 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "async_connection.h"
|
||||
#include <hiredis/async.h>
|
||||
#include "errors.h"
|
||||
#include "async_shards_pool.h"
|
||||
#include "cmd_formatter.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace sw::redis;
|
||||
|
||||
void set_options_callback(redisAsyncContext *ctx, void *r, void *) {
|
||||
assert(ctx != nullptr);
|
||||
|
||||
auto *context = static_cast<AsyncContext *>(ctx->data);
|
||||
assert(context != nullptr);
|
||||
|
||||
auto &connection = context->connection;
|
||||
assert(connection);
|
||||
|
||||
redisReply *reply = static_cast<redisReply *>(r);
|
||||
if (reply == nullptr) {
|
||||
// Connection has bee closed.
|
||||
// TODO: not sure if we should set this to be State::BROKEN
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (reply::is_error(*reply)) {
|
||||
throw_error(*reply);
|
||||
}
|
||||
|
||||
reply::parse<void>(*reply);
|
||||
} catch (const Error &e) {
|
||||
// TODO: disconnect and connect_callback might throw
|
||||
connection->disconnect(std::make_exception_ptr(e));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
connection->connect_callback();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
AsyncConnection::AsyncConnection(const ConnectionOptions &opts,
|
||||
EventLoop *loop,
|
||||
AsyncConnectionMode mode) :
|
||||
_opts(opts),
|
||||
_loop(loop),
|
||||
_create_time(std::chrono::steady_clock::now()),
|
||||
_last_active(std::chrono::steady_clock::now().time_since_epoch()) {
|
||||
assert(_loop != nullptr);
|
||||
|
||||
switch (mode) {
|
||||
case AsyncConnectionMode::SINGLE:
|
||||
_state = State::NOT_CONNECTED;
|
||||
break;
|
||||
|
||||
case AsyncConnectionMode::SENTINEL:
|
||||
_state = State::WAIT_SENTINEL;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("not supporeted async connection mode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncConnection::~AsyncConnection() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void AsyncConnection::send(AsyncEventUPtr event) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
_events.push_back(std::move(event));
|
||||
}
|
||||
|
||||
_loop->add(shared_from_this());
|
||||
}
|
||||
|
||||
void AsyncConnection::event_callback() {
|
||||
// NOTE: we should try our best not throw in these callbacks
|
||||
switch (_state.load()) {
|
||||
case State::WAIT_SENTINEL:
|
||||
_connect_with_sentinel();
|
||||
break;
|
||||
|
||||
case State::NOT_CONNECTED:
|
||||
_connect();
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
_send();
|
||||
break;
|
||||
|
||||
case State::BROKEN:
|
||||
_clean_up();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnection::connect_callback(std::exception_ptr err) {
|
||||
if (err) {
|
||||
// Failed to connect to Redis, fail all pending events.
|
||||
_fail_events(err);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect OK.
|
||||
try {
|
||||
switch (_state.load()) {
|
||||
case State::CONNECTING:
|
||||
_connecting_callback();
|
||||
break;
|
||||
|
||||
case State::AUTHING:
|
||||
_authing_callback();
|
||||
break;
|
||||
|
||||
case State::SELECTING_DB:
|
||||
_select_db_callback();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(_state == State::ENABLE_READONLY);
|
||||
|
||||
_set_ready();
|
||||
}
|
||||
} catch (const Error &e) {
|
||||
disconnect(std::make_exception_ptr(e));
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnection::disconnect(std::exception_ptr err) {
|
||||
if (_ctx != nullptr) {
|
||||
_disable_disconnect_callback();
|
||||
|
||||
redisAsyncDisconnect(_ctx);
|
||||
}
|
||||
|
||||
_fail_events(err);
|
||||
}
|
||||
|
||||
void AsyncConnection::disconnect_callback(std::exception_ptr err) {
|
||||
_fail_events(err);
|
||||
}
|
||||
|
||||
ConnectionOptions AsyncConnection::options() {
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
return _opts;
|
||||
}
|
||||
|
||||
void AsyncConnection::update_node_info(const std::string &host, int port) {
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
_opts.host = host;
|
||||
_opts.port = port;
|
||||
}
|
||||
|
||||
void AsyncConnection::_disable_disconnect_callback() {
|
||||
assert(_ctx != nullptr);
|
||||
|
||||
auto *ctx = static_cast<AsyncContext *>(_ctx->data);
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
ctx->run_disconnect_callback = false;
|
||||
}
|
||||
|
||||
void AsyncConnection::_send() {
|
||||
auto events = _get_events();
|
||||
auto &ctx = _context();
|
||||
for (auto idx = 0U; idx != events.size(); ++idx) {
|
||||
auto &event = events[idx];
|
||||
try {
|
||||
event->handle(ctx);
|
||||
|
||||
// CommandEvent::_reply_callback will release the memory.
|
||||
event.release();
|
||||
} catch (...) {
|
||||
// Failed to send command, fail subsequent events.
|
||||
auto err = std::current_exception();
|
||||
for (; idx != events.size(); ++idx) {
|
||||
auto &event = events[idx];
|
||||
event->set_exception(err);
|
||||
}
|
||||
|
||||
disconnect(err);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AsyncEventUPtr> AsyncConnection::_get_events() {
|
||||
std::vector<AsyncEventUPtr> events;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
events.swap(_events);
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
void AsyncConnection::_clean_up() {
|
||||
if (!_err) {
|
||||
_err = std::make_exception_ptr(Error("connection is closing"));
|
||||
}
|
||||
|
||||
auto events = _get_events();
|
||||
for (auto &event : events) {
|
||||
assert(event);
|
||||
|
||||
event->set_exception(_err);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnection::_fail_events(std::exception_ptr err) {
|
||||
_ctx = nullptr;
|
||||
|
||||
_err = err;
|
||||
|
||||
_state = State::BROKEN;
|
||||
|
||||
// Must call _clean_up after `_err` has been set.
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void AsyncConnection::_connecting_callback() {
|
||||
if (_need_auth()) {
|
||||
_auth();
|
||||
} else if (_need_select_db()) {
|
||||
_select_db();
|
||||
} else if (_need_enable_readonly()) {
|
||||
_enable_readonly();
|
||||
} else {
|
||||
_set_ready();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnection::_authing_callback() {
|
||||
if (_need_select_db()) {
|
||||
_select_db();
|
||||
} else if (_need_enable_readonly()) {
|
||||
_enable_readonly();
|
||||
} else {
|
||||
_set_ready();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnection::_select_db_callback() {
|
||||
if (_need_enable_readonly()) {
|
||||
_enable_readonly();
|
||||
} else {
|
||||
_set_ready();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnection::_auth() {
|
||||
assert(!broken());
|
||||
|
||||
if (_opts.user == "default") {
|
||||
if (redisAsyncCommand(_ctx, set_options_callback, nullptr, "AUTH %b",
|
||||
_opts.password.data(), _opts.password.size()) != REDIS_OK) {
|
||||
throw Error("failed to send auth command");
|
||||
}
|
||||
} else {
|
||||
// Redis 6.0 or latter
|
||||
if (redisAsyncCommand(_ctx, set_options_callback, nullptr, "AUTH %b %b",
|
||||
_opts.user.data(), _opts.user.size(),
|
||||
_opts.password.data(), _opts.password.size()) != REDIS_OK) {
|
||||
throw Error("failed to send auth command");
|
||||
}
|
||||
}
|
||||
|
||||
_state = State::AUTHING;
|
||||
}
|
||||
|
||||
void AsyncConnection::_select_db() {
|
||||
assert(!broken());
|
||||
|
||||
if (redisAsyncCommand(_ctx, set_options_callback, nullptr, "SELECT %d",
|
||||
_opts.db) != REDIS_OK) {
|
||||
throw Error("failed to send select command");
|
||||
}
|
||||
|
||||
_state = State::SELECTING_DB;
|
||||
}
|
||||
|
||||
void AsyncConnection::_enable_readonly() {
|
||||
assert(!broken());
|
||||
|
||||
if (redisAsyncCommand(_ctx, set_options_callback, nullptr, "READONLY") != REDIS_OK) {
|
||||
throw Error("failed to send readonly command");
|
||||
}
|
||||
|
||||
_state = State::ENABLE_READONLY;
|
||||
}
|
||||
|
||||
void AsyncConnection::_set_ready() {
|
||||
_state = State::READY;
|
||||
|
||||
// Send pending commands.
|
||||
_send();
|
||||
}
|
||||
|
||||
void AsyncConnection::_connect_with_sentinel() {
|
||||
try {
|
||||
auto opts = options();
|
||||
if (opts.host.empty()) {
|
||||
// Still waiting for sentinel.
|
||||
return;
|
||||
}
|
||||
|
||||
// Already got node info from sentinel
|
||||
_state = State::NOT_CONNECTED;
|
||||
|
||||
_connect();
|
||||
} catch (const Error &err) {
|
||||
_fail_events(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnection::_connect() {
|
||||
try {
|
||||
auto opts = options();
|
||||
|
||||
auto ctx = _connect(opts);
|
||||
|
||||
assert(ctx && ctx->err == REDIS_OK);
|
||||
|
||||
const auto &tls_opts = opts.tls;
|
||||
tls::TlsContextUPtr tls_ctx;
|
||||
if (tls::enabled(tls_opts)) {
|
||||
tls_ctx = tls::secure_connection(ctx->c, tls_opts);
|
||||
}
|
||||
|
||||
_loop->watch(*ctx);
|
||||
|
||||
_tls_ctx = std::move(tls_ctx);
|
||||
_ctx = ctx.release();
|
||||
|
||||
_state = State::CONNECTING;
|
||||
} catch (const Error &err) {
|
||||
_fail_events(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncConnection::_need_auth() const {
|
||||
return !_opts.password.empty() || _opts.user != "default";
|
||||
}
|
||||
|
||||
bool AsyncConnection::_need_select_db() const {
|
||||
return _opts.db != 0;
|
||||
}
|
||||
|
||||
bool AsyncConnection::_need_enable_readonly() const {
|
||||
return _opts.readonly;
|
||||
}
|
||||
|
||||
void AsyncConnection::_clean_async_context(void *data) {
|
||||
auto *ctx = static_cast<AsyncContext *>(data);
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
AsyncConnection::AsyncContextUPtr AsyncConnection::_connect(const ConnectionOptions &opts) {
|
||||
redisAsyncContext *context = nullptr;
|
||||
switch (opts.type) {
|
||||
case ConnectionType::TCP:
|
||||
context = redisAsyncConnect(opts.host.c_str(), opts.port);
|
||||
break;
|
||||
|
||||
case ConnectionType::UNIX:
|
||||
context = redisAsyncConnectUnix(opts.path.c_str());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Never goes here.
|
||||
throw Error("Unknown connection type");
|
||||
}
|
||||
|
||||
if (context == nullptr) {
|
||||
throw Error("Failed to allocate memory for connection.");
|
||||
}
|
||||
|
||||
auto ctx = AsyncContextUPtr(context);
|
||||
if (ctx->err != REDIS_OK) {
|
||||
throw_error(ctx->c, "failed to connect to server");
|
||||
}
|
||||
|
||||
ctx->data = new AsyncContext(shared_from_this());
|
||||
ctx->dataCleanup = _clean_async_context;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
GuardedAsyncConnection::GuardedAsyncConnection(const AsyncConnectionPoolSPtr &pool) :
|
||||
_pool(pool), _connection(_pool->fetch()) {
|
||||
assert(!_connection->broken());
|
||||
}
|
||||
|
||||
GuardedAsyncConnection::~GuardedAsyncConnection() {
|
||||
// If `GuardedAsyncConnection` has been moved, `_pool` will be nullptr.
|
||||
if (_pool) {
|
||||
_pool->release(std::move(_connection));
|
||||
}
|
||||
}
|
||||
|
||||
AsyncConnection& GuardedAsyncConnection::connection() {
|
||||
assert(_connection);
|
||||
|
||||
return *_connection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_CONNECTION_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_CONNECTION_H
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <vector>
|
||||
#include <hiredis/async.h>
|
||||
#include "connection.h"
|
||||
#include "command_args.h"
|
||||
#include "event_loop.h"
|
||||
#include "async_utils.h"
|
||||
#include "tls.h"
|
||||
#include "shards.h"
|
||||
#include "cmd_formatter.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
template <typename Result>
|
||||
struct DefaultResultParser {
|
||||
Result operator()(redisReply &reply) const {
|
||||
return reply::parse<Result>(reply);
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncConnection;
|
||||
class AsyncConnectionPool;
|
||||
class AsyncShardsPool;
|
||||
|
||||
class AsyncEvent {
|
||||
public:
|
||||
virtual ~AsyncEvent() = default;
|
||||
|
||||
virtual void handle(redisAsyncContext &ctx) = 0;
|
||||
|
||||
virtual void set_exception(std::exception_ptr err) = 0;
|
||||
};
|
||||
|
||||
using AsyncEventUPtr = std::unique_ptr<AsyncEvent>;
|
||||
|
||||
enum class AsyncConnectionMode {
|
||||
SINGLE = 0,
|
||||
SENTINEL,
|
||||
CLUSTER
|
||||
};
|
||||
|
||||
class AsyncConnection : public std::enable_shared_from_this<AsyncConnection> {
|
||||
public:
|
||||
AsyncConnection(const ConnectionOptions &opts,
|
||||
EventLoop *loop,
|
||||
AsyncConnectionMode = AsyncConnectionMode::SINGLE);
|
||||
|
||||
AsyncConnection(const AsyncConnection &) = delete;
|
||||
AsyncConnection& operator=(const AsyncConnection &) = delete;
|
||||
|
||||
AsyncConnection(AsyncConnection &&) = delete;
|
||||
AsyncConnection& operator=(AsyncConnection &&) = delete;
|
||||
|
||||
~AsyncConnection();
|
||||
|
||||
bool broken() const noexcept {
|
||||
return _state == State::BROKEN;
|
||||
}
|
||||
|
||||
auto create_time() const
|
||||
-> std::chrono::time_point<std::chrono::steady_clock> {
|
||||
return _create_time;
|
||||
}
|
||||
|
||||
auto last_active() const
|
||||
-> std::chrono::steady_clock::duration {
|
||||
return _last_active;
|
||||
}
|
||||
|
||||
void disconnect(std::exception_ptr err);
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
Future<Result> send(FormattedCommand cmd);
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
Future<Result> send(const std::shared_ptr<AsyncShardsPool> &pool,
|
||||
const StringView &key,
|
||||
FormattedCommand cmd);
|
||||
|
||||
void send(AsyncEventUPtr event);
|
||||
|
||||
void event_callback();
|
||||
|
||||
void connect_callback(std::exception_ptr err = nullptr);
|
||||
|
||||
void disconnect_callback(std::exception_ptr err);
|
||||
|
||||
ConnectionOptions options();
|
||||
|
||||
void update_node_info(const std::string &host, int port);
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
BROKEN = 0,
|
||||
NOT_CONNECTED,
|
||||
CONNECTING,
|
||||
AUTHING,
|
||||
SELECTING_DB,
|
||||
READY,
|
||||
WAIT_SENTINEL,
|
||||
ENABLE_READONLY
|
||||
};
|
||||
|
||||
redisAsyncContext& _context() {
|
||||
assert(_ctx != nullptr);
|
||||
|
||||
_last_active = std::chrono::steady_clock::now().time_since_epoch();
|
||||
|
||||
return *_ctx;
|
||||
}
|
||||
|
||||
void _connecting_callback();
|
||||
|
||||
void _authing_callback();
|
||||
|
||||
void _select_db_callback();
|
||||
|
||||
bool _need_auth() const;
|
||||
|
||||
void _auth();
|
||||
|
||||
bool _need_select_db() const;
|
||||
|
||||
void _select_db();
|
||||
|
||||
bool _need_enable_readonly() const;
|
||||
|
||||
void _enable_readonly();
|
||||
|
||||
void _set_ready();
|
||||
|
||||
void _connect_with_sentinel();
|
||||
|
||||
void _connect();
|
||||
|
||||
void _disable_disconnect_callback();
|
||||
|
||||
void _send();
|
||||
|
||||
std::vector<std::unique_ptr<AsyncEvent>> _get_events();
|
||||
|
||||
void _clean_up();
|
||||
|
||||
void _fail_events(std::exception_ptr err);
|
||||
|
||||
static void _clean_async_context(void *data);
|
||||
|
||||
struct AsyncContextDeleter {
|
||||
void operator()(redisAsyncContext *ctx) const {
|
||||
if (ctx != nullptr) {
|
||||
redisAsyncFree(ctx);
|
||||
}
|
||||
}
|
||||
};
|
||||
using AsyncContextUPtr = std::unique_ptr<redisAsyncContext, AsyncContextDeleter>;
|
||||
|
||||
AsyncContextUPtr _connect(const ConnectionOptions &opts);
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
EventLoop *_loop = nullptr;
|
||||
|
||||
tls::TlsContextUPtr _tls_ctx;
|
||||
|
||||
// _ctx will be release by EventLoop after attached.
|
||||
redisAsyncContext *_ctx = nullptr;
|
||||
|
||||
// The time that the connection is created.
|
||||
std::chrono::time_point<std::chrono::steady_clock> _create_time{};
|
||||
|
||||
// The time that the connection is created or the time that
|
||||
// the connection is recently used, i.e. `_context()` is called.
|
||||
// NOTE: `_last_active` is `std::atomic`, and we cannot make it of type time_point,
|
||||
// since time_point's constructor is non-trival.
|
||||
std::atomic<std::chrono::steady_clock::duration> _last_active{};
|
||||
|
||||
std::vector<std::unique_ptr<AsyncEvent>> _events;
|
||||
|
||||
std::atomic<State> _state{State::NOT_CONNECTED};
|
||||
|
||||
std::exception_ptr _err;
|
||||
|
||||
std::mutex _mtx;
|
||||
};
|
||||
|
||||
using AsyncConnectionSPtr = std::shared_ptr<AsyncConnection>;
|
||||
|
||||
struct AsyncContext {
|
||||
AsyncContext(AsyncConnectionSPtr conn) : connection(std::move(conn)) {}
|
||||
|
||||
AsyncConnectionSPtr connection;
|
||||
|
||||
bool run_disconnect_callback = true;
|
||||
};
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
class CommandEvent : public AsyncEvent {
|
||||
public:
|
||||
explicit CommandEvent(FormattedCommand cmd) : _cmd(std::move(cmd)) {}
|
||||
|
||||
Future<Result> get_future() {
|
||||
return _pro.get_future();
|
||||
}
|
||||
|
||||
virtual void handle(redisAsyncContext &ctx) override {
|
||||
_handle(ctx, _reply_callback);
|
||||
}
|
||||
|
||||
virtual void set_exception(std::exception_ptr err) override {
|
||||
_pro.set_exception(err);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct ResultType {};
|
||||
|
||||
void set_value(redisReply &reply) {
|
||||
_set_value(reply, ResultType<Result>{});
|
||||
}
|
||||
|
||||
protected:
|
||||
using Callback = void (*)(redisAsyncContext *, void *, void *);
|
||||
|
||||
void _handle(redisAsyncContext &ctx, Callback callback) {
|
||||
if (redisAsyncFormattedCommand(&ctx,
|
||||
callback, this, _cmd.data(), _cmd.size()) != REDIS_OK) {
|
||||
throw_error(ctx.c, "failed to send command");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void _reply_callback(redisAsyncContext * /*ctx*/, void *r, void *privdata) {
|
||||
auto event = static_cast<CommandEvent<Result, ResultParser> *>(privdata);
|
||||
|
||||
assert(event != nullptr);
|
||||
|
||||
try {
|
||||
redisReply *reply = static_cast<redisReply *>(r);
|
||||
if (reply == nullptr) {
|
||||
event->set_exception(std::make_exception_ptr(Error("connection has been closed")));
|
||||
} else if (reply::is_error(*reply)) {
|
||||
try {
|
||||
throw_error(*reply);
|
||||
} catch (const Error &e) {
|
||||
event->set_exception(std::current_exception());
|
||||
}
|
||||
} else {
|
||||
event->set_value(*reply);
|
||||
}
|
||||
} catch (...) {
|
||||
event->set_exception(std::current_exception());
|
||||
}
|
||||
|
||||
delete event;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void _set_value(redisReply &reply, ResultType<T>) {
|
||||
ResultParser parser;
|
||||
_pro.set_value(parser(reply));
|
||||
}
|
||||
|
||||
void _set_value(redisReply &reply, ResultType<void>) {
|
||||
ResultParser parser;
|
||||
parser(reply);
|
||||
|
||||
_pro.set_value();
|
||||
}
|
||||
|
||||
FormattedCommand _cmd;
|
||||
|
||||
Promise<Result> _pro;
|
||||
};
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
using CommandEventUPtr = std::unique_ptr<CommandEvent<Result, ResultParser>>;
|
||||
|
||||
class AskingEvent : public AsyncEvent {
|
||||
public:
|
||||
explicit AskingEvent(AsyncEvent *event) : _event(event) {}
|
||||
|
||||
~AskingEvent() {
|
||||
if (_event != nullptr) {
|
||||
delete _event;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void handle(redisAsyncContext &ctx) override {
|
||||
if (redisAsyncCommand(&ctx, _asking_callback, this, "ASKING") != REDIS_OK) {
|
||||
throw_error(ctx.c, "failed to send ASKING command");
|
||||
}
|
||||
|
||||
assert(_event != nullptr);
|
||||
|
||||
_event->handle(ctx);
|
||||
|
||||
_event = nullptr;
|
||||
}
|
||||
|
||||
virtual void set_exception(std::exception_ptr err) override {
|
||||
if (_event != nullptr) {
|
||||
_event->set_exception(err);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void _asking_callback(redisAsyncContext * /*ctx*/, void *r, void *privdata) {
|
||||
auto event = static_cast<AskingEvent *>(privdata);
|
||||
|
||||
assert(event != nullptr);
|
||||
|
||||
// TODO: No need to check the reply. It seems that we can simply ignore the reply,
|
||||
// and delete the event.
|
||||
try {
|
||||
redisReply *reply = static_cast<redisReply *>(r);
|
||||
if (reply == nullptr) {
|
||||
event->set_exception(std::make_exception_ptr(Error("connection has been closed")));
|
||||
} else if (reply::is_error(*reply)) {
|
||||
try {
|
||||
throw_error(*reply);
|
||||
} catch (const Error &e) {
|
||||
event->set_exception(std::current_exception());
|
||||
}
|
||||
} else {
|
||||
reply::parse<void>(*reply);
|
||||
}
|
||||
} catch (...) {
|
||||
event->set_exception(std::current_exception());
|
||||
}
|
||||
|
||||
delete event;
|
||||
}
|
||||
|
||||
AsyncEvent *_event = nullptr;
|
||||
};
|
||||
|
||||
// NOTE: This class is similar to `SafeAsyncConnection`.
|
||||
// The difference is that `SafeAsyncConnection` tries to avoid copying a std::shared_ptr.
|
||||
class GuardedAsyncConnection {
|
||||
public:
|
||||
explicit GuardedAsyncConnection(const std::shared_ptr<AsyncConnectionPool> &pool);
|
||||
|
||||
GuardedAsyncConnection(const GuardedAsyncConnection &) = delete;
|
||||
GuardedAsyncConnection& operator=(const GuardedAsyncConnection &) = delete;
|
||||
|
||||
GuardedAsyncConnection(GuardedAsyncConnection &&) = default;
|
||||
GuardedAsyncConnection& operator=(GuardedAsyncConnection &&) = default;
|
||||
|
||||
~GuardedAsyncConnection();
|
||||
|
||||
AsyncConnection& connection();
|
||||
|
||||
private:
|
||||
std::shared_ptr<AsyncConnectionPool> _pool;
|
||||
std::shared_ptr<AsyncConnection> _connection;
|
||||
};
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
class ClusterEvent : public CommandEvent<Result, ResultParser> {
|
||||
public:
|
||||
explicit ClusterEvent(const std::shared_ptr<AsyncShardsPool> &pool,
|
||||
const StringView &key,
|
||||
FormattedCommand cmd) :
|
||||
CommandEvent<Result, ResultParser>(std::move(cmd)),
|
||||
_pool(pool),
|
||||
_key(key.data(), key.size()) {}
|
||||
|
||||
virtual void handle(redisAsyncContext &ctx) override {
|
||||
CommandEvent<Result, ResultParser>::_handle(ctx, _cluster_reply_callback);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
NORMAL = 0,
|
||||
MOVED,
|
||||
ASKING
|
||||
};
|
||||
|
||||
static void _cluster_reply_callback(redisAsyncContext * /*ctx*/, void *r, void *privdata) {
|
||||
auto event = static_cast<ClusterEvent<Result, ResultParser> *>(privdata);
|
||||
|
||||
assert(event != nullptr);
|
||||
|
||||
try {
|
||||
redisReply *reply = static_cast<redisReply *>(r);
|
||||
if (reply == nullptr) {
|
||||
event->set_exception(std::make_exception_ptr(Error("connection has been closed")));
|
||||
} else if (reply::is_error(*reply)) {
|
||||
try {
|
||||
throw_error(*reply);
|
||||
} catch (const IoError &err) {
|
||||
event->_pool->update(event->_key, AsyncEventUPtr(event));
|
||||
return;
|
||||
} catch (const ClosedError &err) {
|
||||
event->_pool->update(event->_key, AsyncEventUPtr(event));
|
||||
return;
|
||||
} catch (const MovedError &err) {
|
||||
switch (event->_state) {
|
||||
case State::MOVED:
|
||||
throw Error("too many moved error");
|
||||
break;
|
||||
|
||||
case State::ASKING:
|
||||
throw Error("Slot migrating...");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
event->_state = State::MOVED;
|
||||
event->_pool->update(event->_key, AsyncEventUPtr(event));
|
||||
return;
|
||||
} catch (const AskError &err) {
|
||||
event->_state = State::ASKING;
|
||||
auto pool = event->_pool->fetch(err.node());
|
||||
assert(pool);
|
||||
GuardedAsyncConnection connection(pool);
|
||||
connection.connection().send(AsyncEventUPtr(new AskingEvent(event)));
|
||||
return;
|
||||
} catch (const Error &e) {
|
||||
event->set_exception(std::current_exception());
|
||||
}
|
||||
} else {
|
||||
event->set_value(*reply);
|
||||
}
|
||||
} catch (...) {
|
||||
event->set_exception(std::current_exception());
|
||||
}
|
||||
|
||||
delete event;
|
||||
}
|
||||
|
||||
std::shared_ptr<AsyncShardsPool> _pool;
|
||||
|
||||
std::string _key;
|
||||
|
||||
State _state = State::NORMAL;
|
||||
};
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
using ClusterEventUPtr = std::unique_ptr<ClusterEvent<Result, ResultParser>>;
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
Future<Result> AsyncConnection::send(FormattedCommand cmd) {
|
||||
auto event = CommandEventUPtr<Result, ResultParser>(
|
||||
new CommandEvent<Result, ResultParser>(std::move(cmd)));
|
||||
|
||||
auto fut = event->get_future();
|
||||
|
||||
send(std::move(event));
|
||||
|
||||
return fut;
|
||||
}
|
||||
|
||||
template <typename Result, typename ResultParser>
|
||||
Future<Result> AsyncConnection::send(const std::shared_ptr<AsyncShardsPool> &pool,
|
||||
const StringView &key,
|
||||
FormattedCommand cmd) {
|
||||
auto event = ClusterEventUPtr<Result, ResultParser>(
|
||||
new ClusterEvent<Result, ResultParser>(pool, key, std::move(cmd)));
|
||||
|
||||
auto fut = event->get_future();
|
||||
|
||||
send(std::move(event));
|
||||
|
||||
return fut;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_CONNECTION_H
|
||||
@@ -0,0 +1,347 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "async_connection_pool.h"
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
SimpleAsyncSentinel::SimpleAsyncSentinel(const AsyncSentinelSPtr &sentinel,
|
||||
const std::string &master_name,
|
||||
Role role) :
|
||||
_sentinel(sentinel),
|
||||
_master_name(master_name),
|
||||
_role(role) {
|
||||
if (!_sentinel) {
|
||||
throw Error("Sentinel cannot be null");
|
||||
}
|
||||
|
||||
if (_role != Role::MASTER && _role != Role::SLAVE) {
|
||||
throw Error("Role must be Role::MASTER or Role::SLAVE");
|
||||
}
|
||||
}
|
||||
|
||||
AsyncConnectionSPtr SimpleAsyncSentinel::create(const ConnectionOptions &opts,
|
||||
const std::shared_ptr<AsyncConnectionPool> &pool,
|
||||
EventLoop *loop) {
|
||||
auto connection = std::make_shared<AsyncConnection>(opts, loop, AsyncConnectionMode::SENTINEL);
|
||||
|
||||
AsyncSentinel::AsyncSentinelTask task;
|
||||
task.pool = pool;
|
||||
task.connection = connection;
|
||||
task.master_name = _master_name;
|
||||
task.role = _role;
|
||||
|
||||
_sentinel->add(std::move(task));
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
AsyncConnectionPool::AsyncConnectionPool(const EventLoopSPtr &loop,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts) :
|
||||
_loop(loop),
|
||||
_opts(connection_opts),
|
||||
_pool_opts(pool_opts) {
|
||||
if (_pool_opts.size == 0) {
|
||||
throw Error("CANNOT create an empty pool");
|
||||
}
|
||||
|
||||
// Lazily create connections.
|
||||
}
|
||||
|
||||
AsyncConnectionPool::AsyncConnectionPool(SimpleAsyncSentinel sentinel,
|
||||
const EventLoopSPtr &loop,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts) :
|
||||
_loop(loop),
|
||||
_opts(connection_opts),
|
||||
_pool_opts(pool_opts),
|
||||
_sentinel(std::move(sentinel)) {
|
||||
// In this case, the connection must be of TCP type.
|
||||
if (_opts.type != ConnectionType::TCP) {
|
||||
throw Error("Sentinel only supports TCP connection");
|
||||
}
|
||||
|
||||
if (_opts.connect_timeout == std::chrono::milliseconds(0)
|
||||
|| _opts.socket_timeout == std::chrono::milliseconds(0)) {
|
||||
throw Error("With sentinel, connection timeout and socket timeout cannot be 0");
|
||||
}
|
||||
|
||||
// Cleanup connection options.
|
||||
_update_connection_opts("", -1);
|
||||
|
||||
assert(_sentinel);
|
||||
}
|
||||
|
||||
AsyncConnectionPool::AsyncConnectionPool(AsyncConnectionPool &&that) {
|
||||
std::lock_guard<std::mutex> lock(that._mutex);
|
||||
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
AsyncConnectionPool& AsyncConnectionPool::operator=(AsyncConnectionPool &&that) {
|
||||
if (this != &that) {
|
||||
std::lock(_mutex, that._mutex);
|
||||
std::lock_guard<std::mutex> lock_this(_mutex, std::adopt_lock);
|
||||
std::lock_guard<std::mutex> lock_that(that._mutex, std::adopt_lock);
|
||||
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncConnectionPool::~AsyncConnectionPool() {
|
||||
assert(_loop);
|
||||
|
||||
// TODO: what if the connection has been borrowed but not returned?
|
||||
// Or we dont' need to worry about that, since it's destructing and
|
||||
// all borrowed connections should have been returned.
|
||||
for (auto &connection : _pool) {
|
||||
// TODO: what if some connection has never been watched? Is it possible?
|
||||
_loop->unwatch(std::move(connection));
|
||||
}
|
||||
}
|
||||
|
||||
AsyncConnectionSPtr AsyncConnectionPool::fetch() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
if (_pool.empty()) {
|
||||
if (_used_connections == _pool_opts.size) {
|
||||
_wait_for_connection(lock);
|
||||
} else {
|
||||
// Lazily create a new connection.
|
||||
auto connection = _create();
|
||||
|
||||
++_used_connections;
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
// _pool is NOT empty.
|
||||
auto connection = _fetch();
|
||||
|
||||
auto connection_lifetime = _pool_opts.connection_lifetime;
|
||||
auto connection_idle_time = _pool_opts.connection_idle_time;
|
||||
|
||||
if (_sentinel) {
|
||||
auto opts = _opts;
|
||||
auto role_changed = _role_changed(connection->options());
|
||||
auto sentinel = _sentinel;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (role_changed || _need_reconnect(*connection, connection_lifetime, connection_idle_time)) {
|
||||
try {
|
||||
auto tmp_connection = sentinel.create(opts, shared_from_this(), _loop.get());
|
||||
|
||||
std::swap(tmp_connection, connection);
|
||||
|
||||
// Release expired connection.
|
||||
// TODO: If `unwatch` throw, we will leak the connection.
|
||||
_loop->unwatch(std::move(tmp_connection));
|
||||
} catch (const Error &e) {
|
||||
// Failed to reconnect, return it to the pool, and retry latter.
|
||||
release(std::move(connection));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
assert(connection);
|
||||
|
||||
if (_need_reconnect(*connection, connection_lifetime, connection_idle_time)) {
|
||||
try {
|
||||
auto tmp_connection = _create();
|
||||
|
||||
std::swap(tmp_connection, connection);
|
||||
|
||||
// Release expired connection.
|
||||
// TODO: If `unwatch` throw, we will leak the connection.
|
||||
_loop->unwatch(std::move(tmp_connection));
|
||||
} catch (const Error &e) {
|
||||
// Failed, return it to the pool, and retry latter.
|
||||
release(std::move(connection));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
ConnectionOptions AsyncConnectionPool::connection_options() {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
return _opts;
|
||||
}
|
||||
|
||||
void AsyncConnectionPool::release(AsyncConnectionSPtr connection) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
_pool.push_back(std::move(connection));
|
||||
}
|
||||
|
||||
_cv.notify_one();
|
||||
}
|
||||
|
||||
AsyncConnectionSPtr AsyncConnectionPool::create() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
auto opts = _opts;
|
||||
|
||||
if (_sentinel) {
|
||||
// TODO: it seems that we don't need to copy sentinel,
|
||||
// since it's thread-safe.
|
||||
auto sentinel = _sentinel;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
return sentinel.create(opts, shared_from_this(), _loop.get());
|
||||
} else {
|
||||
lock.unlock();
|
||||
|
||||
return std::make_shared<AsyncConnection>(opts, _loop.get());
|
||||
}
|
||||
}
|
||||
|
||||
AsyncConnectionPool AsyncConnectionPool::clone() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
auto opts = _opts;
|
||||
auto pool_opts = _pool_opts;
|
||||
|
||||
if (_sentinel) {
|
||||
auto sentinel = _sentinel;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
return AsyncConnectionPool(sentinel, _loop, pool_opts, opts);
|
||||
} else {
|
||||
lock.unlock();
|
||||
|
||||
return AsyncConnectionPool(_loop, pool_opts, opts);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncConnectionPool::update_node_info(const std::string &host,
|
||||
int port,
|
||||
AsyncConnectionSPtr &connection) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
_update_connection_opts(host, port);
|
||||
}
|
||||
|
||||
connection->update_node_info(host, port);
|
||||
|
||||
_loop->add(connection);
|
||||
}
|
||||
|
||||
void AsyncConnectionPool::update_node_info(AsyncConnectionSPtr &connection,
|
||||
std::exception_ptr err) {
|
||||
_loop->unwatch(connection, err);
|
||||
}
|
||||
|
||||
void AsyncConnectionPool::_move(AsyncConnectionPool &&that) {
|
||||
_loop = std::move(that._loop);
|
||||
_opts = std::move(that._opts);
|
||||
_pool_opts = std::move(that._pool_opts);
|
||||
_pool = std::move(that._pool);
|
||||
_used_connections = that._used_connections;
|
||||
_sentinel = std::move(that._sentinel);
|
||||
}
|
||||
|
||||
AsyncConnectionSPtr AsyncConnectionPool::_create() {
|
||||
if (_sentinel) {
|
||||
// Get Redis host and port info from sentinel.
|
||||
// In this case, the mutex has been locked.
|
||||
return _sentinel.create(_opts, shared_from_this(), _loop.get());
|
||||
}
|
||||
|
||||
return std::make_shared<AsyncConnection>(_opts, _loop.get());
|
||||
}
|
||||
|
||||
AsyncConnectionSPtr AsyncConnectionPool::_fetch() {
|
||||
assert(!_pool.empty());
|
||||
|
||||
auto connection = std::move(_pool.front());
|
||||
_pool.pop_front();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
void AsyncConnectionPool::_wait_for_connection(std::unique_lock<std::mutex> &lock) {
|
||||
auto timeout = _pool_opts.wait_timeout;
|
||||
if (timeout > std::chrono::milliseconds(0)) {
|
||||
// Wait until _pool is no longer empty or timeout.
|
||||
if (!_cv.wait_for(lock,
|
||||
timeout,
|
||||
[this] { return !(this->_pool).empty(); })) {
|
||||
throw Error("Failed to fetch a connection in "
|
||||
+ std::to_string(timeout.count()) + " milliseconds");
|
||||
}
|
||||
} else {
|
||||
// Wait forever.
|
||||
_cv.wait(lock, [this] { return !(this->_pool).empty(); });
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncConnectionPool::_need_reconnect(const AsyncConnection &connection,
|
||||
const std::chrono::milliseconds &connection_lifetime,
|
||||
const std::chrono::milliseconds &connection_idle_time) const {
|
||||
if (connection.broken()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (connection_lifetime > std::chrono::milliseconds(0)) {
|
||||
if (now - connection.create_time() > connection_lifetime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (connection_idle_time > std::chrono::milliseconds(0)) {
|
||||
if (now.time_since_epoch() - connection.last_active() > connection_idle_time) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsyncConnectionPool::_role_changed(const ConnectionOptions &opts) const {
|
||||
if (opts.host.empty()) {
|
||||
// Still waiting for sentinel.
|
||||
return false;
|
||||
}
|
||||
|
||||
return opts.port != _opts.port || opts.host != _opts.host;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_CONNECTION_POOL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_CONNECTION_POOL_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_set>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include "connection.h"
|
||||
#include "connection_pool.h"
|
||||
#include "async_connection.h"
|
||||
#include "async_sentinel.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class AsyncConnectionPool;
|
||||
|
||||
class SimpleAsyncSentinel {
|
||||
public:
|
||||
SimpleAsyncSentinel(const AsyncSentinelSPtr &sentinel,
|
||||
const std::string &master_name,
|
||||
Role role);
|
||||
|
||||
SimpleAsyncSentinel() = default;
|
||||
|
||||
SimpleAsyncSentinel(const SimpleAsyncSentinel &) = default;
|
||||
SimpleAsyncSentinel& operator=(const SimpleAsyncSentinel &) = default;
|
||||
|
||||
SimpleAsyncSentinel(SimpleAsyncSentinel &&) = default;
|
||||
SimpleAsyncSentinel& operator=(SimpleAsyncSentinel &&) = default;
|
||||
|
||||
~SimpleAsyncSentinel() = default;
|
||||
|
||||
explicit operator bool() const {
|
||||
return bool(_sentinel);
|
||||
}
|
||||
|
||||
AsyncConnectionSPtr create(const ConnectionOptions &opts,
|
||||
const std::shared_ptr<AsyncConnectionPool> &pool,
|
||||
EventLoop *loop);
|
||||
|
||||
private:
|
||||
AsyncSentinelSPtr _sentinel;
|
||||
|
||||
std::string _master_name;
|
||||
|
||||
Role _role = Role::MASTER;
|
||||
};
|
||||
|
||||
class AsyncConnectionPool : public std::enable_shared_from_this<AsyncConnectionPool> {
|
||||
public:
|
||||
AsyncConnectionPool(const EventLoopSPtr &loop,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
AsyncConnectionPool(SimpleAsyncSentinel sentinel,
|
||||
const EventLoopSPtr &loop,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
AsyncConnectionPool(AsyncConnectionPool &&that);
|
||||
AsyncConnectionPool& operator=(AsyncConnectionPool &&that);
|
||||
|
||||
AsyncConnectionPool(const AsyncConnectionPool &) = delete;
|
||||
AsyncConnectionPool& operator=(const AsyncConnectionPool &) = delete;
|
||||
|
||||
~AsyncConnectionPool();
|
||||
|
||||
// Fetch a connection from pool.
|
||||
AsyncConnectionSPtr fetch();
|
||||
|
||||
ConnectionOptions connection_options();
|
||||
|
||||
void release(AsyncConnectionSPtr connection);
|
||||
|
||||
// Create a new connection.
|
||||
AsyncConnectionSPtr create();
|
||||
|
||||
AsyncConnectionPool clone();
|
||||
|
||||
// These update_node_info overloads are called by AsyncSentinel.
|
||||
void update_node_info(const std::string &host,
|
||||
int port,
|
||||
AsyncConnectionSPtr &connection);
|
||||
|
||||
void update_node_info(AsyncConnectionSPtr &connection,
|
||||
std::exception_ptr err);
|
||||
|
||||
private:
|
||||
void _move(AsyncConnectionPool &&that);
|
||||
|
||||
// NOT thread-safe
|
||||
AsyncConnectionSPtr _create();
|
||||
|
||||
AsyncConnectionSPtr _fetch();
|
||||
|
||||
void _wait_for_connection(std::unique_lock<std::mutex> &lock);
|
||||
|
||||
bool _need_reconnect(const AsyncConnection &connection,
|
||||
const std::chrono::milliseconds &connection_lifetime,
|
||||
const std::chrono::milliseconds &connection_idle_time) const;
|
||||
|
||||
void _update_connection_opts(const std::string &host, int port) {
|
||||
_opts.host = host;
|
||||
_opts.port = port;
|
||||
}
|
||||
|
||||
bool _role_changed(const ConnectionOptions &opts) const;
|
||||
|
||||
EventLoopSPtr _loop;
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
ConnectionPoolOptions _pool_opts;
|
||||
|
||||
std::deque<AsyncConnectionSPtr> _pool;
|
||||
|
||||
std::size_t _used_connections = 0;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
std::condition_variable _cv;
|
||||
|
||||
SimpleAsyncSentinel _sentinel;
|
||||
};
|
||||
|
||||
using AsyncConnectionPoolSPtr = std::shared_ptr<AsyncConnectionPool>;
|
||||
|
||||
class SafeAsyncConnection {
|
||||
public:
|
||||
explicit SafeAsyncConnection(AsyncConnectionPool &pool) : _pool(pool), _connection(_pool.fetch()) {
|
||||
assert(_connection);
|
||||
}
|
||||
|
||||
SafeAsyncConnection(const SafeAsyncConnection &) = delete;
|
||||
SafeAsyncConnection& operator=(const SafeAsyncConnection &) = delete;
|
||||
|
||||
SafeAsyncConnection(SafeAsyncConnection &&) = delete;
|
||||
SafeAsyncConnection& operator=(SafeAsyncConnection &&) = delete;
|
||||
|
||||
~SafeAsyncConnection() {
|
||||
_pool.release(std::move(_connection));
|
||||
}
|
||||
|
||||
AsyncConnection& connection() {
|
||||
assert(_connection);
|
||||
|
||||
return *_connection;
|
||||
}
|
||||
|
||||
private:
|
||||
AsyncConnectionPool &_pool;
|
||||
AsyncConnectionSPtr _connection;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_CONNECTION_POOL_H
|
||||
@@ -0,0 +1,23 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_REDISPLUSPLUS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_REDISPLUSPLUS_H
|
||||
|
||||
#include "async_redis.h"
|
||||
#include "async_redis_cluster.h"
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_REDISPLUSPLUS_H
|
||||
@@ -0,0 +1,52 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "async_redis.h"
|
||||
#include "reply.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
AsyncRedis::AsyncRedis(const ConnectionOptions &opts,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const EventLoopSPtr &loop) : _loop(loop) {
|
||||
if (!_loop) {
|
||||
_loop = std::make_shared<EventLoop>();
|
||||
}
|
||||
|
||||
_pool = std::make_shared<AsyncConnectionPool>(_loop, pool_opts, opts);
|
||||
}
|
||||
|
||||
AsyncRedis::AsyncRedis(const std::shared_ptr<AsyncSentinel> &sentinel,
|
||||
const std::string &master_name,
|
||||
Role role,
|
||||
const ConnectionOptions &opts,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const EventLoopSPtr &loop) : _loop(loop) {
|
||||
if (!_loop) {
|
||||
_loop = std::make_shared<EventLoop>();
|
||||
}
|
||||
|
||||
_pool = std::make_shared<AsyncConnectionPool>(SimpleAsyncSentinel(sentinel, master_name, role),
|
||||
_loop,
|
||||
pool_opts,
|
||||
opts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,789 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_REDIS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_REDIS_H
|
||||
|
||||
#include "async_connection.h"
|
||||
#include "async_connection_pool.h"
|
||||
#include "async_sentinel.h"
|
||||
#include "event_loop.h"
|
||||
#include "utils.h"
|
||||
#include "command.h"
|
||||
#include "command_args.h"
|
||||
#include "command_options.h"
|
||||
#include "cmd_formatter.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class AsyncRedis {
|
||||
public:
|
||||
AsyncRedis(const ConnectionOptions &opts,
|
||||
const ConnectionPoolOptions &pool_opts = {},
|
||||
const EventLoopSPtr &loop = nullptr);
|
||||
|
||||
AsyncRedis(const std::shared_ptr<AsyncSentinel> &sentinel,
|
||||
const std::string &master_name,
|
||||
Role role,
|
||||
const ConnectionOptions &connection_opts,
|
||||
const ConnectionPoolOptions &pool_opts = {},
|
||||
const EventLoopSPtr &loop = nullptr);
|
||||
|
||||
AsyncRedis(const AsyncRedis &) = delete;
|
||||
AsyncRedis& operator=(const AsyncRedis &) = delete;
|
||||
|
||||
AsyncRedis(AsyncRedis &&) = default;
|
||||
AsyncRedis& operator=(AsyncRedis &&) = default;
|
||||
|
||||
~AsyncRedis() = default;
|
||||
|
||||
template <typename Result, typename ...Args>
|
||||
Future<Result> command(const StringView &cmd_name, Args &&...args) {
|
||||
auto formatter = [](const StringView &cmd_name, Args &&...args) {
|
||||
CmdArgs cmd_args;
|
||||
cmd_args.append(cmd_name, std::forward<Args>(args)...);
|
||||
return fmt::format_cmd(cmd_args);
|
||||
};
|
||||
|
||||
return _command<Result>(formatter, cmd_name, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename Input>
|
||||
auto command(Input first, Input last)
|
||||
-> typename std::enable_if<IsIter<Input>::value, Future<Result>>::type {
|
||||
auto formatter = [](Input first, Input last) {
|
||||
CmdArgs cmd_args;
|
||||
while (first != last) {
|
||||
cmd_args.append(*first);
|
||||
++first;
|
||||
}
|
||||
return fmt::format_cmd(cmd_args);
|
||||
};
|
||||
|
||||
return _command<Result>(formatter, first, last);
|
||||
}
|
||||
|
||||
// CONNECTION commands.
|
||||
|
||||
Future<std::string> echo(const StringView &msg) {
|
||||
return _command<std::string>(fmt::echo, msg);
|
||||
}
|
||||
|
||||
Future<std::string> ping() {
|
||||
return _command<std::string, FormattedCommand (*)()>(fmt::ping);
|
||||
}
|
||||
|
||||
Future<std::string> ping(const StringView &msg) {
|
||||
return _command<std::string,
|
||||
FormattedCommand (*)(const StringView &)>(fmt::ping, msg);
|
||||
}
|
||||
|
||||
// KEY commands.
|
||||
|
||||
Future<long long> del(const StringView &key) {
|
||||
return _command<long long>(fmt::del, key);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> del(Input first, Input last) {
|
||||
range_check("DEL", first, last);
|
||||
|
||||
return _command<long long>(fmt::del_range<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> del(std::initializer_list<T> il) {
|
||||
return del(il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<long long> exists(const StringView &key) {
|
||||
return _command<long long>(fmt::exists, key);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> exists(Input first, Input last) {
|
||||
range_check("EXISTS", first, last);
|
||||
|
||||
return _command<long long>(fmt::exists_range<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> exists(std::initializer_list<T> il) {
|
||||
return exists(il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> expire(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return _command<bool>(fmt::expire, key, timeout);
|
||||
}
|
||||
|
||||
Future<bool> expireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::seconds> &tp) {
|
||||
return _command<bool>(fmt::expireat, key, tp);
|
||||
}
|
||||
|
||||
Future<bool> pexpire(const StringView &key, const std::chrono::milliseconds &timeout) {
|
||||
return _command<bool>(fmt::pexpire, key, timeout);
|
||||
}
|
||||
|
||||
Future<bool> pexpireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds> &tp) {
|
||||
return _command<bool>(fmt::pexpireat, key, tp);
|
||||
}
|
||||
|
||||
Future<long long> pttl(const StringView &key) {
|
||||
return _command<long long>(fmt::pttl, key);
|
||||
}
|
||||
|
||||
Future<void> rename(const StringView &key, const StringView &newkey) {
|
||||
return _command<void>(fmt::rename, key, newkey);
|
||||
}
|
||||
|
||||
Future<bool> renamenx(const StringView &key, const StringView &newkey) {
|
||||
return _command<bool>(fmt::renamenx, key, newkey);
|
||||
}
|
||||
|
||||
Future<long long> ttl(const StringView &key) {
|
||||
return _command<long long>(fmt::ttl, key);
|
||||
}
|
||||
|
||||
Future<long long> unlink(const StringView &key) {
|
||||
return _command<long long>(fmt::unlink, key);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> unlink(Input first, Input last) {
|
||||
range_check("UNLINK", first, last);
|
||||
|
||||
return _command<long long>(fmt::unlink_range<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> unlink(std::initializer_list<T> il) {
|
||||
return unlink(il.begin(), il.end());
|
||||
}
|
||||
|
||||
// STRING commands.
|
||||
|
||||
Future<OptionalString> get(const StringView &key) {
|
||||
return _command<OptionalString>(fmt::get, key);
|
||||
}
|
||||
|
||||
Future<long long> incr(const StringView &key) {
|
||||
return _command<long long>(fmt::incr, key);
|
||||
}
|
||||
|
||||
Future<long long> incrby(const StringView &key, long long increment) {
|
||||
return _command<long long>(fmt::incrby, key, increment);
|
||||
}
|
||||
|
||||
Future<double> incrbyfloat(const StringView &key, double increment) {
|
||||
return _command<double>(fmt::incrbyfloat, key, increment);
|
||||
}
|
||||
|
||||
template <typename Output, typename Input>
|
||||
Future<Output> mget(Input first, Input last) {
|
||||
range_check("MGET", first, last);
|
||||
|
||||
return _command<Output>(fmt::mget<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename Output, typename T>
|
||||
Future<Output> mget(std::initializer_list<T> il) {
|
||||
return mget<Output>(il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<void> mset(Input first, Input last) {
|
||||
range_check("MSET", first, last);
|
||||
|
||||
return _command<void>(fmt::mset<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<void> mset(std::initializer_list<T> il) {
|
||||
return mset(il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<bool> msetnx(Input first, Input last) {
|
||||
range_check("MSETNX", first, last);
|
||||
|
||||
return _command<bool>(fmt::msetnx<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<bool> msetnx(std::initializer_list<T> il) {
|
||||
return msetnx(il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> set(const StringView &key,
|
||||
const StringView &val,
|
||||
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
|
||||
UpdateType type = UpdateType::ALWAYS) {
|
||||
return _command_with_parser<bool, fmt::SetResultParser>(fmt::set, key, val, ttl, type);
|
||||
}
|
||||
|
||||
Future<long long> strlen(const StringView &key) {
|
||||
return _command<long long>(fmt::strlen, key);
|
||||
}
|
||||
|
||||
// LIST commands.
|
||||
|
||||
Future<OptionalStringPair> blpop(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return _command<OptionalStringPair>(fmt::blpop, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<OptionalStringPair> blpop(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
range_check("BLPOP", first, last);
|
||||
|
||||
return _command<OptionalStringPair>(fmt::blpop_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<OptionalStringPair> blpop(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return blpop(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
Future<OptionalStringPair> brpop(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return _command<OptionalStringPair>(fmt::brpop, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<OptionalStringPair> brpop(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
range_check("BRPOP", first, last);
|
||||
|
||||
return _command<OptionalStringPair>(fmt::brpop_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<OptionalStringPair> brpop(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return brpop(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
Future<OptionalString> brpoplpush(const StringView &source,
|
||||
const StringView &destination,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return _command<OptionalString>(fmt::brpoplpush, source, destination, timeout);
|
||||
}
|
||||
|
||||
Future<long long> llen(const StringView &key) {
|
||||
return _command<long long>(fmt::llen, key);
|
||||
}
|
||||
|
||||
Future<OptionalString> lpop(const StringView &key) {
|
||||
return _command<OptionalString>(fmt::lpop, key);
|
||||
}
|
||||
|
||||
Future<long long> lpush(const StringView &key, const StringView &val) {
|
||||
return _command<long long>(fmt::lpush, key, val);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> lpush(const StringView &key, Input first, Input last) {
|
||||
range_check("LPUSH", first, last);
|
||||
|
||||
return _command<long long>(fmt::lpush_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> lpush(const StringView &key, std::initializer_list<T> il) {
|
||||
return lpush(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> lrange(const StringView &key, long long start, long long stop) {
|
||||
return _command<Output>(fmt::lrange, key, start, stop);
|
||||
}
|
||||
|
||||
Future<long long> lrem(const StringView &key, long long count, const StringView &val) {
|
||||
return _command<long long>(fmt::lrem, key, count, val);
|
||||
}
|
||||
|
||||
Future<void> ltrim(const StringView &key, long long start, long long stop) {
|
||||
return _command<void>(fmt::ltrim, key, start, stop);
|
||||
}
|
||||
|
||||
Future<OptionalString> rpop(const StringView &key) {
|
||||
return _command<OptionalString>(fmt::rpop, key);
|
||||
}
|
||||
|
||||
Future<OptionalString> rpoplpush(const StringView &source, const StringView &destination) {
|
||||
return _command<OptionalString>(fmt::rpoplpush, source, destination);
|
||||
}
|
||||
|
||||
Future<long long> rpush(const StringView &key, const StringView &val) {
|
||||
return _command<long long>(fmt::rpush, key, val);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> rpush(const StringView &key, Input first, Input last) {
|
||||
range_check("RPUSH", first, last);
|
||||
|
||||
return _command<long long>(fmt::rpush_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> rpush(const StringView &key, std::initializer_list<T> il) {
|
||||
return rpush(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
// HASH commands.
|
||||
|
||||
Future<long long> hdel(const StringView &key, const StringView &field) {
|
||||
return _command<long long>(fmt::hdel, key, field);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> hdel(const StringView &key, Input first, Input last) {
|
||||
range_check("HDEL", first, last);
|
||||
|
||||
return _command<long long>(fmt::hdel_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> hdel(const StringView &key, std::initializer_list<T> il) {
|
||||
return hdel(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> hexists(const StringView &key, const StringView &field) {
|
||||
return _command<bool>(fmt::hexists, key, field);
|
||||
}
|
||||
|
||||
Future<OptionalString> hget(const StringView &key, const StringView &field) {
|
||||
return _command<OptionalString>(fmt::hget, key, field);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> hgetall(const StringView &key) {
|
||||
return _command<Output>(fmt::hgetall, key);
|
||||
}
|
||||
|
||||
Future<long long> hincrby(const StringView &key, const StringView &field, long long increment) {
|
||||
return _command<long long>(fmt::hincrby, key, field, increment);
|
||||
}
|
||||
|
||||
Future<double> hincrbyfloat(const StringView &key, const StringView &field, double increment) {
|
||||
return _command<double>(fmt::hincrbyfloat, key, field, increment);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> hkeys(const StringView &key) {
|
||||
return _command<Output>(fmt::hkeys, key);
|
||||
}
|
||||
|
||||
Future<long long> hlen(const StringView &key) {
|
||||
return _command<long long>(fmt::hlen, key);
|
||||
}
|
||||
|
||||
template <typename Output, typename Input>
|
||||
Future<Output> hmget(const StringView &key, Input first, Input last) {
|
||||
range_check("HMGET", first, last);
|
||||
|
||||
return _command<Output>(fmt::hmget<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename Output, typename T>
|
||||
Future<Output> hmget(const StringView &key, std::initializer_list<T> il) {
|
||||
return hmget<Output>(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<void> hmset(const StringView &key, Input first, Input last) {
|
||||
range_check("HMSET", first, last);
|
||||
|
||||
return _command<void>(fmt::hmset<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<void> hmset(const StringView &key, std::initializer_list<T> il) {
|
||||
return hmset(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> hset(const StringView &key, const StringView &field, const StringView &val) {
|
||||
return _command<bool>(fmt::hset, key, field, val);
|
||||
}
|
||||
|
||||
Future<bool> hset(const StringView &key, const std::pair<StringView, StringView> &item) {
|
||||
return hset(key, item.first, item.second);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto hset(const StringView &key, Input first, Input last)
|
||||
-> typename std::enable_if<!std::is_convertible<Input, StringView>::value, Future<long long>>::type {
|
||||
range_check("HSET", first, last);
|
||||
|
||||
return _command<long long>(fmt::hset_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> hset(const StringView &key, std::initializer_list<T> il) {
|
||||
return hset(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> hvals(const StringView &key) {
|
||||
return _command<Output>(fmt::hvals, key);
|
||||
}
|
||||
|
||||
// SET commands.
|
||||
|
||||
Future<long long> sadd(const StringView &key, const StringView &member) {
|
||||
return _command<long long>(fmt::sadd, key, member);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> sadd(const StringView &key, Input first, Input last) {
|
||||
range_check("SADD", first, last);
|
||||
|
||||
return _command<long long>(fmt::sadd_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> sadd(const StringView &key, std::initializer_list<T> il) {
|
||||
return sadd(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<long long> scard(const StringView &key) {
|
||||
return _command<long long>(fmt::scard, key);
|
||||
}
|
||||
|
||||
Future<bool> sismember(const StringView &key, const StringView &member) {
|
||||
return _command<bool>(fmt::sismember, key, member);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> smembers(const StringView &key) {
|
||||
return _command<Output>(fmt::smembers, key);
|
||||
}
|
||||
|
||||
Future<OptionalString> spop(const StringView &key) {
|
||||
return _command<OptionalString,
|
||||
FormattedCommand (*)(const StringView &)>(fmt::spop, key);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> spop(const StringView &key, long long count) {
|
||||
return _command<Output,
|
||||
FormattedCommand (*)(const StringView &, long long)>(fmt::spop, key, count);
|
||||
}
|
||||
|
||||
Future<long long> srem(const StringView &key, const StringView &member) {
|
||||
return _command<long long>(fmt::srem, key, member);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> srem(const StringView &key, Input first, Input last) {
|
||||
range_check("SREM", first, last);
|
||||
|
||||
return _command<long long>(fmt::srem_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> srem(const StringView &key, std::initializer_list<T> il) {
|
||||
return srem(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
// SORTED SET commands.
|
||||
|
||||
auto bzpopmax(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmax, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto bzpopmax(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
range_check("BZPOPMAX", first, last);
|
||||
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmax_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto bzpopmax(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return bzpopmax(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
auto bzpopmin(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmin, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto bzpopmin(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
range_check("BZPOPMIN", first, last);
|
||||
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmin_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto bzpopmin(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return bzpopmin(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
Future<long long> zadd(const StringView &key,
|
||||
const StringView &member,
|
||||
double score,
|
||||
UpdateType type = UpdateType::ALWAYS,
|
||||
bool changed = false) {
|
||||
return _command<long long>(fmt::zadd, key, member, score, type, changed);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> zadd(const StringView &key,
|
||||
Input first,
|
||||
Input last,
|
||||
UpdateType type = UpdateType::ALWAYS,
|
||||
bool changed = false) {
|
||||
range_check("ZADD", first, last);
|
||||
|
||||
return _command<long long>(fmt::zadd_range<Input>,
|
||||
key,
|
||||
first,
|
||||
last,
|
||||
type,
|
||||
changed);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> zadd(const StringView &key,
|
||||
std::initializer_list<T> il,
|
||||
UpdateType type = UpdateType::ALWAYS,
|
||||
bool changed = false) {
|
||||
return zadd(key, il.begin(), il.end(), type, changed);
|
||||
}
|
||||
|
||||
Future<long long> zcard(const StringView &key) {
|
||||
return _command<long long>(fmt::zcard, key);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zcount(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zcount<Interval>, key, interval);
|
||||
}
|
||||
|
||||
Future<double> zincrby(const StringView &key, double increment, const StringView &member) {
|
||||
return _command<double>(fmt::zincrby, key, increment, member);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zlexcount(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zlexcount<Interval>, key, interval);
|
||||
}
|
||||
|
||||
Future<Optional<std::pair<std::string, double>>> zpopmax(const StringView &key) {
|
||||
return _command<Optional<std::pair<std::string, double>>,
|
||||
FormattedCommand (*)(const StringView &)>(fmt::zpopmax, key);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> zpopmax(const StringView &key, long long count) {
|
||||
return _command<Output, FormattedCommand (*)(const StringView &, long long)>(
|
||||
fmt::zpopmax, key, count);
|
||||
}
|
||||
|
||||
Future<Optional<std::pair<std::string, double>>> zpopmin(const StringView &key) {
|
||||
return _command<Optional<std::pair<std::string, double>>,
|
||||
FormattedCommand (*)(const StringView &)>(fmt::zpopmin, key);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> zpopmin(const StringView &key, long long count) {
|
||||
return _command<Output, FormattedCommand (*)(const StringView &, long long)>(
|
||||
fmt::zpopmin, key, count);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> zrange(const StringView &key, long long start, long long stop) {
|
||||
return _command<Output>(fmt::zrange, key, start, stop);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
return _command<Output>(fmt::zrangebylex<Interval>,
|
||||
key, interval, opts);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebylex(const StringView &key, const Interval &interval) {
|
||||
return zrangebylex<Output>(key, interval, {});
|
||||
}
|
||||
|
||||
// TODO: withscores parameter
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebyscore(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
return _command<Output>(fmt::zrangebyscore<Interval>,
|
||||
key, interval, opts);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebyscore(const StringView &key, const Interval &interval) {
|
||||
return zrangebyscore(key, interval, {});
|
||||
}
|
||||
|
||||
Future<OptionalLongLong> zrank(const StringView &key, const StringView &member) {
|
||||
return _command<OptionalLongLong>(fmt::zrank, key, member);
|
||||
}
|
||||
|
||||
Future<long long> zrem(const StringView &key, const StringView &member) {
|
||||
return _command<long long>(fmt::zrem, key, member);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> zrem(const StringView &key, Input first, Input last) {
|
||||
range_check("ZREM", first, last);
|
||||
|
||||
return _command<long long>(fmt::zrem_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> zrem(const StringView &key, std::initializer_list<T> il) {
|
||||
return zrem(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zremrangebylex(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zremrangebylex<Interval>, key, interval);
|
||||
}
|
||||
|
||||
Future<long long> zremrangebyrank(const StringView &key, long long start, long long stop) {
|
||||
return _command<long long>(fmt::zremrangebyrank, key, start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zremrangebyscore(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zremrangebyscore<Interval>, key, interval);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrevrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
return _command<Output>(fmt::zrevrangebylex<Interval>,
|
||||
key, interval, opts);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrevrangebylex(const StringView &key, const Interval &interval) {
|
||||
return zrevrangebylex<Output>(key, interval, {});
|
||||
}
|
||||
|
||||
Future<OptionalLongLong> zrevrank(const StringView &key, const StringView &member) {
|
||||
return _command<OptionalLongLong>(fmt::zrevrank, key, member);
|
||||
}
|
||||
|
||||
Future<OptionalDouble> zscore(const StringView &key, const StringView &member) {
|
||||
return _command<OptionalDouble>(fmt::zscore, key, member);
|
||||
}
|
||||
|
||||
// SCRIPTING commands.
|
||||
|
||||
template <typename Result, typename Keys, typename Args>
|
||||
Future<Result> eval(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
return _command<Result>(fmt::eval<Keys, Args>,
|
||||
script, keys_first, keys_last, args_first, args_last);
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
Future<Result> eval(const StringView &script,
|
||||
std::initializer_list<StringView> keys,
|
||||
std::initializer_list<StringView> args) {
|
||||
return eval<Result>(script,
|
||||
keys.begin(), keys.end(),
|
||||
args.begin(), args.end());
|
||||
}
|
||||
|
||||
template <typename Result, typename Keys, typename Args>
|
||||
Future<Result> evalsha(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
return _command<Result>(fmt::evalsha<Keys, Args>,
|
||||
script, keys_first, keys_last, args_first, args_last);
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
Future<Result> evalsha(const StringView &script,
|
||||
std::initializer_list<StringView> keys,
|
||||
std::initializer_list<StringView> args) {
|
||||
return evalsha<Result>(script,
|
||||
keys.begin(), keys.end(),
|
||||
args.begin(), args.end());
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Result, typename Formatter, typename ...Args>
|
||||
Future<Result> _command(Formatter formatter, Args &&...args) {
|
||||
return _command_with_parser<Result, DefaultResultParser<Result>>(
|
||||
formatter, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename ResultParser, typename Formatter, typename ...Args>
|
||||
Future<Result> _command_with_parser(Formatter formatter, Args &&...args) {
|
||||
auto formatted_cmd = formatter(std::forward<Args>(args)...);
|
||||
|
||||
assert(_pool);
|
||||
SafeAsyncConnection connection(*_pool);
|
||||
|
||||
return connection.connection().send<Result, ResultParser>(std::move(formatted_cmd));
|
||||
}
|
||||
|
||||
EventLoopSPtr _loop;
|
||||
|
||||
AsyncConnectionPoolSPtr _pool;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_REDIS_H
|
||||
@@ -0,0 +1,37 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "async_redis_cluster.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
AsyncRedisCluster::AsyncRedisCluster(const ConnectionOptions &opts,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
Role role,
|
||||
const EventLoopSPtr &loop) : _loop(loop) {
|
||||
if (!_loop) {
|
||||
_loop = std::make_shared<EventLoop>();
|
||||
}
|
||||
|
||||
_pool = std::make_shared<AsyncShardsPool>(_loop, pool_opts, opts, role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,819 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_REDIS_CLUSTER_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_REDIS_CLUSTER_H
|
||||
|
||||
#include <cassert>
|
||||
#include "utils.h"
|
||||
#include "async_connection.h"
|
||||
#include "async_connection_pool.h"
|
||||
#include "async_shards_pool.h"
|
||||
#include "event_loop.h"
|
||||
#include "cmd_formatter.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class AsyncRedisCluster {
|
||||
public:
|
||||
AsyncRedisCluster(const ConnectionOptions &opts,
|
||||
const ConnectionPoolOptions &pool_opts = {},
|
||||
Role role = Role::MASTER,
|
||||
const EventLoopSPtr &loop = nullptr);
|
||||
|
||||
AsyncRedisCluster(const AsyncRedisCluster &) = delete;
|
||||
AsyncRedisCluster& operator=(const AsyncRedisCluster &) = delete;
|
||||
|
||||
AsyncRedisCluster(AsyncRedisCluster &&) = default;
|
||||
AsyncRedisCluster& operator=(AsyncRedisCluster &&) = default;
|
||||
|
||||
~AsyncRedisCluster() = default;
|
||||
|
||||
template <typename Result, typename ...Args>
|
||||
Future<Result> command(const StringView &cmd_name, const StringView &key, Args &&...args) {
|
||||
auto formatter = [&cmd_name](const StringView &key, Args &&...args) {
|
||||
CmdArgs cmd_args;
|
||||
cmd_args.append(cmd_name, key, std::forward<Args>(args)...);
|
||||
return fmt::format_cmd(cmd_args);
|
||||
};
|
||||
|
||||
return _command<Result>(formatter, key, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename Input>
|
||||
auto command(Input first, Input last)
|
||||
-> typename std::enable_if<IsIter<Input>::value, Future<Result>>::type {
|
||||
if (first == last || std::next(first) == last) {
|
||||
throw Error("command: invalid range");
|
||||
}
|
||||
|
||||
const auto &cmd_name = *first;
|
||||
++first;
|
||||
|
||||
auto formatter = [&cmd_name](Input first, Input last) {
|
||||
CmdArgs cmd_args;
|
||||
cmd_args.append(cmd_name);
|
||||
while (first != last) {
|
||||
cmd_args.append(*first);
|
||||
++first;
|
||||
}
|
||||
return fmt::format_cmd(cmd_args);
|
||||
};
|
||||
|
||||
return _command<Result>(formatter, first, last);
|
||||
}
|
||||
|
||||
// CONNECTION commands.
|
||||
|
||||
Future<long long> del(const StringView &key) {
|
||||
return _command<long long>(fmt::del, key);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> del(Input first, Input last) {
|
||||
range_check("DEL", first, last);
|
||||
|
||||
return _command<long long>(fmt::del_range<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> del(std::initializer_list<T> il) {
|
||||
return del(il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<long long> exists(const StringView &key) {
|
||||
return _command<long long>(fmt::exists, key);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> exists(Input first, Input last) {
|
||||
range_check("EXISTS", first, last);
|
||||
|
||||
return _command<long long>(fmt::exists_range<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> exists(std::initializer_list<T> il) {
|
||||
return exists(il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> expire(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return _command<bool>(fmt::expire, key, timeout);
|
||||
}
|
||||
|
||||
Future<bool> expireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::seconds> &tp) {
|
||||
return _command<bool>(fmt::expireat, key, tp);
|
||||
}
|
||||
|
||||
Future<bool> pexpire(const StringView &key, const std::chrono::milliseconds &timeout) {
|
||||
return _command<bool>(fmt::pexpire, key, timeout);
|
||||
}
|
||||
|
||||
Future<bool> pexpireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds> &tp) {
|
||||
return _command<bool>(fmt::pexpireat, key, tp);
|
||||
}
|
||||
|
||||
Future<long long> pttl(const StringView &key) {
|
||||
return _command<long long>(fmt::pttl, key);
|
||||
}
|
||||
|
||||
Future<void> rename(const StringView &key, const StringView &newkey) {
|
||||
return _command<void>(fmt::rename, key, newkey);
|
||||
}
|
||||
|
||||
Future<bool> renamenx(const StringView &key, const StringView &newkey) {
|
||||
return _command<bool>(fmt::renamenx, key, newkey);
|
||||
}
|
||||
|
||||
Future<long long> ttl(const StringView &key) {
|
||||
return _command<long long>(fmt::ttl, key);
|
||||
}
|
||||
|
||||
Future<long long> unlink(const StringView &key) {
|
||||
return _command<long long>(fmt::unlink, key);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> unlink(Input first, Input last) {
|
||||
range_check("UNLINK", first, last);
|
||||
|
||||
return _command<long long>(fmt::unlink_range<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> unlink(std::initializer_list<T> il) {
|
||||
return unlink(il.begin(), il.end());
|
||||
}
|
||||
|
||||
// STRING commands.
|
||||
|
||||
Future<OptionalString> get(const StringView &key) {
|
||||
return _command<OptionalString>(fmt::get, key);
|
||||
}
|
||||
|
||||
Future<long long> incr(const StringView &key) {
|
||||
return _command<long long>(fmt::incr, key);
|
||||
}
|
||||
|
||||
Future<long long> incrby(const StringView &key, long long increment) {
|
||||
return _command<long long>(fmt::incrby, key, increment);
|
||||
}
|
||||
|
||||
Future<double> incrbyfloat(const StringView &key, double increment) {
|
||||
return _command<double>(fmt::incrbyfloat, key, increment);
|
||||
}
|
||||
|
||||
template <typename Output, typename Input>
|
||||
Future<Output> mget(Input first, Input last) {
|
||||
range_check("MGET", first, last);
|
||||
|
||||
return _command<Output>(fmt::mget<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename Output, typename T>
|
||||
Future<Output> mget(std::initializer_list<T> il) {
|
||||
return mget<Output>(il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<void> mset(Input first, Input last) {
|
||||
range_check("MSET", first, last);
|
||||
|
||||
return _command<void>(fmt::mset<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<void> mset(std::initializer_list<T> il) {
|
||||
return mset(il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<bool> msetnx(Input first, Input last) {
|
||||
range_check("MSETNX", first, last);
|
||||
|
||||
return _command<bool>(fmt::msetnx<Input>, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<bool> msetnx(std::initializer_list<T> il) {
|
||||
return msetnx(il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> set(const StringView &key,
|
||||
const StringView &val,
|
||||
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
|
||||
UpdateType type = UpdateType::ALWAYS) {
|
||||
return _command_with_parser<bool, fmt::SetResultParser>(fmt::set,
|
||||
key, key, val, ttl, type);
|
||||
}
|
||||
|
||||
Future<long long> strlen(const StringView &key) {
|
||||
return _command<long long>(fmt::strlen, key);
|
||||
}
|
||||
|
||||
// LIST commands.
|
||||
|
||||
Future<OptionalStringPair> blpop(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return _command<OptionalStringPair>(fmt::blpop, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<OptionalStringPair> blpop(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
range_check("BLPOP", first, last);
|
||||
|
||||
return _command<OptionalStringPair>(fmt::blpop_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<OptionalStringPair> blpop(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return blpop(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
Future<OptionalStringPair> brpop(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return _command<OptionalStringPair>(fmt::brpop, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<OptionalStringPair> brpop(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
range_check("BRPOP", first, last);
|
||||
|
||||
return _command<OptionalStringPair>(fmt::brpop_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<OptionalStringPair> brpop(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return brpop(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
Future<OptionalString> brpoplpush(const StringView &source,
|
||||
const StringView &destination,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
|
||||
return _command<OptionalString>(fmt::brpoplpush, source, destination, timeout);
|
||||
}
|
||||
|
||||
Future<long long> llen(const StringView &key) {
|
||||
return _command<long long>(fmt::llen, key);
|
||||
}
|
||||
|
||||
Future<OptionalString> lpop(const StringView &key) {
|
||||
return _command<OptionalString>(fmt::lpop, key);
|
||||
}
|
||||
|
||||
Future<long long> lpush(const StringView &key, const StringView &val) {
|
||||
return _command<long long>(fmt::lpush, key, val);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> lpush(const StringView &key, Input first, Input last) {
|
||||
range_check("LPUSH", first, last);
|
||||
|
||||
return _command<long long>(fmt::lpush_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> lpush(const StringView &key, std::initializer_list<T> il) {
|
||||
return lpush(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> lrange(const StringView &key, long long start, long long stop) {
|
||||
return _command<Output>(fmt::lrange, key, start, stop);
|
||||
}
|
||||
|
||||
Future<long long> lrem(const StringView &key, long long count, const StringView &val) {
|
||||
return _command<long long>(fmt::lrem, key, count, val);
|
||||
}
|
||||
|
||||
Future<void> ltrim(const StringView &key, long long start, long long stop) {
|
||||
return _command<void>(fmt::ltrim, key, start, stop);
|
||||
}
|
||||
|
||||
Future<OptionalString> rpop(const StringView &key) {
|
||||
return _command<OptionalString>(fmt::rpop, key);
|
||||
}
|
||||
|
||||
Future<OptionalString> rpoplpush(const StringView &source, const StringView &destination) {
|
||||
return _command<OptionalString>(fmt::rpoplpush, source, destination);
|
||||
}
|
||||
|
||||
Future<long long> rpush(const StringView &key, const StringView &val) {
|
||||
return _command<long long>(fmt::rpush, key, val);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> rpush(const StringView &key, Input first, Input last) {
|
||||
range_check("RPUSH", first, last);
|
||||
|
||||
return _command<long long>(fmt::rpush_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> rpush(const StringView &key, std::initializer_list<T> il) {
|
||||
return rpush(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
// HASH commands.
|
||||
|
||||
Future<long long> hdel(const StringView &key, const StringView &field) {
|
||||
return _command<long long>(fmt::hdel, key, field);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> hdel(const StringView &key, Input first, Input last) {
|
||||
range_check("HDEL", first, last);
|
||||
|
||||
return _command<long long>(fmt::hdel_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> hdel(const StringView &key, std::initializer_list<T> il) {
|
||||
return hdel(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> hexists(const StringView &key, const StringView &field) {
|
||||
return _command<bool>(fmt::hexists, key, field);
|
||||
}
|
||||
|
||||
Future<OptionalString> hget(const StringView &key, const StringView &field) {
|
||||
return _command<OptionalString>(fmt::hget, key, field);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> hgetall(const StringView &key) {
|
||||
return _command<Output>(fmt::hgetall, key);
|
||||
}
|
||||
|
||||
Future<long long> hincrby(const StringView &key, const StringView &field, long long increment) {
|
||||
return _command<long long>(fmt::hincrby, key, field, increment);
|
||||
}
|
||||
|
||||
Future<double> hincrbyfloat(const StringView &key, const StringView &field, double increment) {
|
||||
return _command<double>(fmt::hincrbyfloat, key, field, increment);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> hkeys(const StringView &key) {
|
||||
return _command<Output>(fmt::hkeys, key);
|
||||
}
|
||||
|
||||
Future<long long> hlen(const StringView &key) {
|
||||
return _command<long long>(fmt::hlen, key);
|
||||
}
|
||||
|
||||
template <typename Output, typename Input>
|
||||
Future<Output> hmget(const StringView &key, Input first, Input last) {
|
||||
range_check("HMGET", first, last);
|
||||
|
||||
return _command<Output>(fmt::hmget<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename Output, typename T>
|
||||
Future<Output> hmget(const StringView &key, std::initializer_list<T> il) {
|
||||
return hmget<Output>(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<void> hmset(const StringView &key, Input first, Input last) {
|
||||
range_check("HMSET", first, last);
|
||||
|
||||
return _command<void>(fmt::hmset<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<void> hmset(const StringView &key, std::initializer_list<T> il) {
|
||||
return hmset(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<bool> hset(const StringView &key, const StringView &field, const StringView &val) {
|
||||
return _command<bool>(fmt::hset, key, field, val);
|
||||
}
|
||||
|
||||
Future<bool> hset(const StringView &key, const std::pair<StringView, StringView> &item) {
|
||||
return hset(key, item.first, item.second);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto hset(const StringView &key, Input first, Input last)
|
||||
-> typename std::enable_if<!std::is_convertible<Input, StringView>::value, Future<long long>>::type {
|
||||
range_check("HSET", first, last);
|
||||
|
||||
return _command<long long>(fmt::hset_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> hset(const StringView &key, std::initializer_list<T> il) {
|
||||
return hset(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> hvals(const StringView &key) {
|
||||
return _command<Output>(fmt::hvals, key);
|
||||
}
|
||||
|
||||
// SET commands.
|
||||
|
||||
Future<long long> sadd(const StringView &key, const StringView &member) {
|
||||
return _command<long long>(fmt::sadd, key, member);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> sadd(const StringView &key, Input first, Input last) {
|
||||
range_check("SADD", first, last);
|
||||
|
||||
return _command<long long>(fmt::sadd_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> sadd(const StringView &key, std::initializer_list<T> il) {
|
||||
return sadd(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
Future<long long> scard(const StringView &key) {
|
||||
return _command<long long>(fmt::scard, key);
|
||||
}
|
||||
|
||||
Future<bool> sismember(const StringView &key, const StringView &member) {
|
||||
return _command<bool>(fmt::sismember, key, member);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> smembers(const StringView &key) {
|
||||
return _command<Output>(fmt::smembers, key);
|
||||
}
|
||||
|
||||
Future<OptionalString> spop(const StringView &key) {
|
||||
return _command<OptionalString, FormattedCommand (*)(const StringView &)>(fmt::spop, key);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> spop(const StringView &key, long long count) {
|
||||
return _command<Output,
|
||||
FormattedCommand (*)(const StringView &, long long)>(fmt::spop, key, count);
|
||||
}
|
||||
|
||||
Future<long long> srem(const StringView &key, const StringView &member) {
|
||||
return _command<long long>(fmt::srem, key, member);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> srem(const StringView &key, Input first, Input last) {
|
||||
range_check("SREM", first, last);
|
||||
|
||||
return _command<long long>(fmt::srem_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> srem(const StringView &key, std::initializer_list<T> il) {
|
||||
return srem(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
// SORTED SET commands.
|
||||
|
||||
auto bzpopmax(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmax, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto bzpopmax(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
range_check("BZPOPMAX", first, last);
|
||||
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmax_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto bzpopmax(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return bzpopmax(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
auto bzpopmin(const StringView &key,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmin, key, timeout);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto bzpopmin(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
range_check("BZPOPMIN", first, last);
|
||||
|
||||
return _command<Optional<std::tuple<std::string, std::string, double>>>(
|
||||
fmt::bzpopmin_range<Input>, first, last, timeout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto bzpopmin(std::initializer_list<T> il,
|
||||
const std::chrono::seconds &timeout = std::chrono::seconds{0})
|
||||
-> Future<Optional<std::tuple<std::string, std::string, double>>> {
|
||||
return bzpopmin(il.begin(), il.end(), timeout);
|
||||
}
|
||||
|
||||
Future<long long> zadd(const StringView &key,
|
||||
const StringView &member,
|
||||
double score,
|
||||
UpdateType type = UpdateType::ALWAYS,
|
||||
bool changed = false) {
|
||||
return _command<long long>(fmt::zadd, key, member, score, type, changed);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> zadd(const StringView &key,
|
||||
Input first,
|
||||
Input last,
|
||||
UpdateType type = UpdateType::ALWAYS,
|
||||
bool changed = false) {
|
||||
range_check("ZADD", first, last);
|
||||
|
||||
return _command<long long>(fmt::zadd_range<Input>, key, first, last, type, changed);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> zadd(const StringView &key,
|
||||
std::initializer_list<T> il,
|
||||
UpdateType type = UpdateType::ALWAYS,
|
||||
bool changed = false) {
|
||||
return zadd(key, il.begin(), il.end(), type, changed);
|
||||
}
|
||||
|
||||
Future<long long> zcard(const StringView &key) {
|
||||
return _command<long long>(fmt::zcard, key);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zcount(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zcount<Interval>, key, interval);
|
||||
}
|
||||
|
||||
Future<double> zincrby(const StringView &key, double increment, const StringView &member) {
|
||||
return _command<double>(fmt::zincrby, key, increment, member);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zlexcount(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zlexcount<Interval>, key, interval);
|
||||
}
|
||||
|
||||
Future<Optional<std::pair<std::string, double>>> zpopmax(const StringView &key) {
|
||||
return _command<Optional<std::pair<std::string, double>>,
|
||||
FormattedCommand (*)(const StringView &)>(fmt::zpopmax, key);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> zpopmax(const StringView &key, long long count) {
|
||||
return _command<Output, FormattedCommand (*)(const StringView &, long long)>(
|
||||
fmt::zpopmax, key, count);
|
||||
}
|
||||
|
||||
Future<Optional<std::pair<std::string, double>>> zpopmin(const StringView &key) {
|
||||
return _command<Optional<std::pair<std::string, double>>,
|
||||
FormattedCommand (*)(const StringView &)>(fmt::zpopmin, key);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> zpopmin(const StringView &key, long long count) {
|
||||
return _command<Output, FormattedCommand (*)(const StringView &, long long)>(
|
||||
fmt::zpopmin, key, count);
|
||||
}
|
||||
|
||||
template <typename Output>
|
||||
Future<Output> zrange(const StringView &key, long long start, long long stop) {
|
||||
return _command<Output>(fmt::zrange, key, start, stop);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
return _command<Output>(fmt::zrangebylex<Interval>, key, interval, opts);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebylex(const StringView &key, const Interval &interval) {
|
||||
return zrangebylex<Output>(key, interval, {});
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebyscore(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
return _command<Output>(fmt::zrangebyscore<Interval>,
|
||||
key, interval, opts);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrangebyscore(const StringView &key, const Interval &interval) {
|
||||
return zrangebyscore(key, interval, {});
|
||||
}
|
||||
|
||||
Future<OptionalLongLong> zrank(const StringView &key, const StringView &member) {
|
||||
return _command<OptionalLongLong>(fmt::zrank, key, member);
|
||||
}
|
||||
|
||||
Future<long long> zrem(const StringView &key, const StringView &member) {
|
||||
return _command<long long>(fmt::zrem, key, member);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
Future<long long> zrem(const StringView &key, Input first, Input last) {
|
||||
range_check("ZREM", first, last);
|
||||
|
||||
return _command<long long>(fmt::zrem_range<Input>, key, first, last);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<long long> zrem(const StringView &key, std::initializer_list<T> il) {
|
||||
return zrem(key, il.begin(), il.end());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zremrangebylex(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zremrangebylex<Interval>, key, interval);
|
||||
}
|
||||
|
||||
Future<long long> zremrangebyrank(const StringView &key, long long start, long long stop) {
|
||||
return _command<long long>(fmt::zremrangebyrank, key, start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
Future<long long> zremrangebyscore(const StringView &key, const Interval &interval) {
|
||||
return _command<long long>(fmt::zremrangebyscore<Interval>, key, interval);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrevrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
return _command<Output>(fmt::zrevrangebylex<Interval>, key, interval, opts);
|
||||
}
|
||||
|
||||
template <typename Output, typename Interval>
|
||||
Future<Output> zrevrangebylex(const StringView &key, const Interval &interval) {
|
||||
return zrevrangebylex<Output>(key, interval, {});
|
||||
}
|
||||
|
||||
Future<OptionalLongLong> zrevrank(const StringView &key, const StringView &member) {
|
||||
return _command<OptionalLongLong>(fmt::zrevrank, key, member);
|
||||
}
|
||||
|
||||
Future<OptionalDouble> zscore(const StringView &key, const StringView &member) {
|
||||
return _command<OptionalDouble>(fmt::zscore, key, member);
|
||||
}
|
||||
|
||||
// SCRIPTING commands.
|
||||
|
||||
template <typename Result, typename Keys, typename Args>
|
||||
Future<Result> eval(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
if (keys_first == keys_last) {
|
||||
throw Error("DO NOT support Lua script without key");
|
||||
}
|
||||
|
||||
return _generic_command<Result>(fmt::eval<Keys, Args>, *keys_first, script,
|
||||
keys_first, keys_last,
|
||||
args_first, args_last);
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
Future<Result> eval(const StringView &script,
|
||||
std::initializer_list<StringView> keys,
|
||||
std::initializer_list<StringView> args) {
|
||||
return eval<Result>(script,
|
||||
keys.begin(), keys.end(),
|
||||
args.begin(), args.end());
|
||||
}
|
||||
|
||||
template <typename Result, typename Keys, typename Args>
|
||||
Future<Result> evalsha(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
if (keys_first == keys_last) {
|
||||
throw Error("DO NOT support Lua script without key");
|
||||
}
|
||||
|
||||
return _generic_command<Result>(fmt::evalsha<Keys, Args>, *keys_first, script,
|
||||
keys_first, keys_last, args_first, args_last);
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
Future<Result> evalsha(const StringView &script,
|
||||
std::initializer_list<StringView> keys,
|
||||
std::initializer_list<StringView> args) {
|
||||
return evalsha<Result>(script,
|
||||
keys.begin(), keys.end(),
|
||||
args.begin(), args.end());
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Result, typename ResultParser,
|
||||
typename Formatter, typename ...Args>
|
||||
Future<Result> _command_with_parser(Formatter formatter,
|
||||
const StringView &key, Args &&...args) {
|
||||
auto formatted_cmd = formatter(std::forward<Args>(args)...);
|
||||
|
||||
assert(_pool);
|
||||
|
||||
auto pool = _pool->fetch(key);
|
||||
assert(pool);
|
||||
|
||||
GuardedAsyncConnection connection(pool);
|
||||
|
||||
return connection.connection().send<Result, ResultParser>(
|
||||
_pool, key, std::move(formatted_cmd));
|
||||
}
|
||||
|
||||
template <typename Result, typename Formatter, typename ...Args>
|
||||
Future<Result> _generic_command(Formatter formatter, const StringView &key, Args &&...args) {
|
||||
return _command_with_parser<Result, DefaultResultParser<Result>>(
|
||||
formatter, key, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename Formatter, typename Key, typename ...Args>
|
||||
Future<Result> _command(Formatter formatter, Key &&key, Args &&...args) {
|
||||
return _generic_command<Result>(formatter,
|
||||
std::is_convertible<typename std::decay<Key>::type, StringView>(),
|
||||
std::forward<Key>(key),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename Formatter, typename ...Args>
|
||||
Future<Result> _generic_command(Formatter formatter, std::true_type,
|
||||
const StringView &key, Args &&...args) {
|
||||
return _generic_command<Result>(formatter, key, key, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename Formatter, typename Input, typename ...Args>
|
||||
Future<Result> _generic_command(Formatter formatter, std::false_type, Input &&input, Args &&...args) {
|
||||
return _range_command<Result>(formatter,
|
||||
std::is_convertible<typename std::decay<
|
||||
decltype(*std::declval<Input>())>::type, StringView>(),
|
||||
std::forward<Input>(input),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename Formatter, typename Input, typename ...Args>
|
||||
Future<Result> _range_command(Formatter formatter, std::true_type,
|
||||
Input &&input, Args &&...args) {
|
||||
return _generic_command<Result>(formatter, *input,
|
||||
std::forward<Input>(input), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Result, typename Formatter, typename Input, typename ...Args>
|
||||
Future<Result> _range_command(Formatter formatter, std::false_type,
|
||||
Input &&input, Args &&...args) {
|
||||
return _generic_command<Result>(formatter, std::get<0>(*input),
|
||||
std::forward<Input>(input), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
EventLoopSPtr _loop;
|
||||
|
||||
AsyncShardsPoolSPtr _pool;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_REDIS_CLUSTER_H
|
||||
@@ -0,0 +1,110 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "async_sentinel.h"
|
||||
#include <cassert>
|
||||
#include "errors.h"
|
||||
#include "async_connection_pool.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
AsyncSentinel::AsyncSentinel(const SentinelOptions &sentinel_opts) :
|
||||
_sentinel(std::make_shared<Sentinel>(sentinel_opts)),
|
||||
_worker([this]() { this->_run(); }) {}
|
||||
|
||||
AsyncSentinel::~AsyncSentinel() {
|
||||
_stop_worker();
|
||||
|
||||
if (_worker.joinable()) {
|
||||
_worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSentinel::add(AsyncSentinelTask task) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
_tasks.push(std::move(task));
|
||||
}
|
||||
|
||||
_cv.notify_one();
|
||||
}
|
||||
|
||||
void AsyncSentinel::_stop_worker() {
|
||||
// Add an empty task to stop the worker thread.
|
||||
add(AsyncSentinelTask{});
|
||||
}
|
||||
|
||||
void AsyncSentinel::_run() {
|
||||
while (true) {
|
||||
auto tasks = _fetch_tasks();
|
||||
|
||||
assert(!tasks.empty());
|
||||
|
||||
while (!tasks.empty()) {
|
||||
auto &task = tasks.front();
|
||||
if (!task) {
|
||||
// Got a stopping task.
|
||||
return;
|
||||
}
|
||||
|
||||
_run_task(task);
|
||||
|
||||
tasks.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSentinel::_run_task(AsyncSentinelTask &task) {
|
||||
auto pool = task.pool.lock();
|
||||
if (!pool) {
|
||||
// AsyncConnectionPool has been destroyed, give up the task.
|
||||
return;
|
||||
}
|
||||
|
||||
auto &connection = task.connection;
|
||||
try {
|
||||
SimpleSentinel sentinel(_sentinel, task.master_name, task.role);
|
||||
auto sync_connection = sentinel.create(connection->options());
|
||||
|
||||
const auto &opts = sync_connection.options();
|
||||
pool->update_node_info(opts.host, opts.port, connection);
|
||||
} catch (const StopIterError &e) {
|
||||
pool->update_node_info(connection,
|
||||
std::make_exception_ptr(Error("Failed to create connection with sentinel")));
|
||||
} catch (const Error &e) {
|
||||
pool->update_node_info(connection, std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
auto AsyncSentinel::_fetch_tasks() -> std::queue<AsyncSentinelTask> {
|
||||
std::queue<AsyncSentinelTask> tasks;
|
||||
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
if (_tasks.empty()) {
|
||||
_cv.wait(lock, [this]() { return !(this->_tasks).empty(); } );
|
||||
}
|
||||
|
||||
tasks.swap(_tasks);
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_SENTINEL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_SENTINEL_H
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <condition_variable>
|
||||
#include "sentinel.h"
|
||||
#include "async_connection.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class AsyncConnectionPool;
|
||||
|
||||
class AsyncSentinel {
|
||||
public:
|
||||
explicit AsyncSentinel(const SentinelOptions &sentinel_opts);
|
||||
|
||||
AsyncSentinel(const AsyncSentinel &) = delete;
|
||||
AsyncSentinel& operator=(const AsyncSentinel &) = delete;
|
||||
|
||||
AsyncSentinel(AsyncSentinel &&) = delete;
|
||||
AsyncSentinel& operator=(AsyncSentinel &&) = delete;
|
||||
|
||||
~AsyncSentinel();
|
||||
|
||||
private:
|
||||
friend class SimpleAsyncSentinel;
|
||||
|
||||
struct AsyncSentinelTask {
|
||||
operator bool() const noexcept {
|
||||
return bool(connection);
|
||||
}
|
||||
|
||||
std::weak_ptr<AsyncConnectionPool> pool;
|
||||
|
||||
AsyncConnectionSPtr connection;
|
||||
|
||||
std::string master_name;
|
||||
|
||||
Role role = Role::MASTER;
|
||||
};
|
||||
|
||||
void add(AsyncSentinelTask task);
|
||||
|
||||
void _stop_worker();
|
||||
|
||||
void _run_task(AsyncSentinelTask &task);
|
||||
|
||||
std::queue<AsyncSentinelTask> _fetch_tasks();
|
||||
|
||||
void _run();
|
||||
|
||||
std::queue<AsyncSentinelTask> _tasks;
|
||||
|
||||
std::shared_ptr<Sentinel> _sentinel;
|
||||
|
||||
std::thread _worker;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
std::condition_variable _cv;
|
||||
};
|
||||
|
||||
using AsyncSentinelSPtr = std::shared_ptr<AsyncSentinel>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_SENTINEL_H
|
||||
@@ -0,0 +1,374 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "async_shards_pool.h"
|
||||
#include <cassert>
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
const std::size_t AsyncShardsPool::SHARDS;
|
||||
|
||||
AsyncShardsPool::AsyncShardsPool(const EventLoopSPtr &loop,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts,
|
||||
Role role) :
|
||||
_pool_opts(pool_opts),
|
||||
_connection_opts(connection_opts),
|
||||
_role(role),
|
||||
_loop(loop) {
|
||||
assert(_loop);
|
||||
|
||||
if (_connection_opts.type != ConnectionType::TCP) {
|
||||
throw Error("Only support TCP connection for Redis Cluster");
|
||||
}
|
||||
|
||||
auto node = Node{_connection_opts.host, _connection_opts.port};
|
||||
_shards.emplace(SlotRange{0U, SHARDS}, node);
|
||||
_pools.emplace(node,
|
||||
std::make_shared<AsyncConnectionPool>(_loop, _pool_opts, _connection_opts));
|
||||
|
||||
_worker = std::thread([this]() { this->_run(); });
|
||||
}
|
||||
|
||||
AsyncShardsPool::AsyncShardsPool(AsyncShardsPool &&that) {
|
||||
std::lock_guard<std::mutex> lock(that._mutex);
|
||||
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
AsyncShardsPool& AsyncShardsPool::operator=(AsyncShardsPool &&that) {
|
||||
if (this != &that) {
|
||||
std::lock(_mutex, that._mutex);
|
||||
std::lock_guard<std::mutex> lock_this(_mutex, std::adopt_lock);
|
||||
std::lock_guard<std::mutex> lock_that(that._mutex, std::adopt_lock);
|
||||
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncShardsPool::~AsyncShardsPool() {
|
||||
update({}, nullptr);
|
||||
|
||||
if (_worker.joinable()) {
|
||||
_worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
AsyncConnectionPoolSPtr AsyncShardsPool::fetch(const StringView &key) {
|
||||
auto slot = _slot(key);
|
||||
|
||||
return _fetch(slot);
|
||||
}
|
||||
|
||||
AsyncConnectionPoolSPtr AsyncShardsPool::fetch() {
|
||||
auto slot = _slot();
|
||||
|
||||
return _fetch(slot);
|
||||
}
|
||||
|
||||
AsyncConnectionPoolSPtr AsyncShardsPool::fetch(const Node &node) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
auto iter = _pools.find(node);
|
||||
if (iter == _pools.end()) {
|
||||
// Node doesn't exist, and it should be a newly created node.
|
||||
// So add a new connection pool.
|
||||
iter = _add_node(node);
|
||||
}
|
||||
|
||||
assert(iter != _pools.end());
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void AsyncShardsPool::update(const std::string &key, AsyncEventUPtr event) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
_events.push(RedeliverEvent{key, std::move(event)});
|
||||
}
|
||||
|
||||
_cv.notify_one();
|
||||
}
|
||||
|
||||
ConnectionOptions AsyncShardsPool::connection_options(const StringView &key) {
|
||||
auto slot = _slot(key);
|
||||
|
||||
return _connection_options(slot);
|
||||
}
|
||||
|
||||
ConnectionOptions AsyncShardsPool::connection_options() {
|
||||
auto slot = _slot();
|
||||
|
||||
return _connection_options(slot);
|
||||
}
|
||||
|
||||
void AsyncShardsPool::_move(AsyncShardsPool &&that) {
|
||||
_pool_opts = that._pool_opts;
|
||||
_connection_opts = that._connection_opts;
|
||||
_role = that._role;
|
||||
_shards = std::move(that._shards);
|
||||
_pools = std::move(that._pools);
|
||||
_loop = std::move(that._loop);
|
||||
_worker = std::move(that._worker);
|
||||
_events = std::move(that._events);
|
||||
}
|
||||
|
||||
ConnectionOptions AsyncShardsPool::_connection_options(Slot slot) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
auto &pool = _get_pool(slot);
|
||||
|
||||
assert(pool);
|
||||
|
||||
return pool->connection_options();
|
||||
}
|
||||
|
||||
Slot AsyncShardsPool::_slot(const StringView &key) const {
|
||||
// The following code is copied from: https://redis.io/topics/cluster-spec
|
||||
// And I did some minor changes.
|
||||
|
||||
const auto *k = key.data();
|
||||
auto keylen = key.size();
|
||||
|
||||
// start-end indexes of { and }.
|
||||
std::size_t s = 0;
|
||||
std::size_t e = 0;
|
||||
|
||||
// Search the first occurrence of '{'.
|
||||
for (s = 0; s < keylen; s++)
|
||||
if (k[s] == '{') break;
|
||||
|
||||
// No '{' ? Hash the whole key. This is the base case.
|
||||
if (s == keylen) return crc16(k, keylen) & SHARDS;
|
||||
|
||||
// '{' found? Check if we have the corresponding '}'.
|
||||
for (e = s + 1; e < keylen; e++)
|
||||
if (k[e] == '}') break;
|
||||
|
||||
// No '}' or nothing between {} ? Hash the whole key.
|
||||
if (e == keylen || e == s + 1) return crc16(k, keylen) & SHARDS;
|
||||
|
||||
// If we are here there is both a { and a } on its right. Hash
|
||||
// what is in the middle between { and }.
|
||||
return crc16(k + s + 1, e - s - 1) & SHARDS;
|
||||
}
|
||||
|
||||
Slot AsyncShardsPool::_slot() const {
|
||||
return _random(0, SHARDS);
|
||||
}
|
||||
|
||||
AsyncConnectionPoolSPtr AsyncShardsPool::_fetch(Slot slot) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
return _get_pool(slot);
|
||||
}
|
||||
|
||||
AsyncConnectionPoolSPtr& AsyncShardsPool::_get_pool(Slot slot) {
|
||||
const auto &node = _get_node(slot);
|
||||
|
||||
auto node_iter = _pools.find(node);
|
||||
if (node_iter == _pools.end()) {
|
||||
throw Error("Slot is NOT covered: " + std::to_string(slot));
|
||||
}
|
||||
|
||||
return node_iter->second;
|
||||
}
|
||||
|
||||
void AsyncShardsPool::_run() {
|
||||
while (true) {
|
||||
auto events = _fetch_events();
|
||||
|
||||
assert(!events.empty());
|
||||
|
||||
try {
|
||||
// TODO: when we try to stop the worker thread,
|
||||
// we don't need to call `_update_shards`
|
||||
_update_shards();
|
||||
|
||||
// if _redeliver_events or _fail_events returns true if there's a null event,
|
||||
// and we exit the thread loop.
|
||||
if (_redeliver_events(events)) {
|
||||
break;
|
||||
}
|
||||
} catch (...) {
|
||||
if (_fail_events(events, std::current_exception())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto AsyncShardsPool::_fetch_events() -> std::queue<RedeliverEvent> {
|
||||
std::queue<RedeliverEvent> events;
|
||||
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
if (_events.empty()) {
|
||||
_cv.wait(lock, [this]() { return !(this->_events).empty(); } );
|
||||
}
|
||||
|
||||
events.swap(_events);
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
std::size_t AsyncShardsPool::_random(std::size_t min, std::size_t max) const {
|
||||
static thread_local std::default_random_engine engine;
|
||||
|
||||
std::uniform_int_distribution<std::size_t> uniform_dist(min, max);
|
||||
|
||||
return uniform_dist(engine);
|
||||
}
|
||||
|
||||
const Node& AsyncShardsPool::_get_node(Slot slot) const {
|
||||
auto shards_iter = _shards.lower_bound(SlotRange{slot, slot});
|
||||
if (shards_iter == _shards.end() || slot < shards_iter->first.min) {
|
||||
throw Error("Slot is out of range: " + std::to_string(slot));
|
||||
}
|
||||
|
||||
return shards_iter->second;
|
||||
}
|
||||
|
||||
Shards AsyncShardsPool::_get_shards(const std::string &host, int port) {
|
||||
auto opts = _connection_opts;
|
||||
opts.host = host;
|
||||
opts.port = port;
|
||||
ShardsPool pool(_pool_opts, opts, _role);
|
||||
|
||||
return pool.shards();
|
||||
}
|
||||
|
||||
void AsyncShardsPool::_update_shards() {
|
||||
for (int idx = 0; idx < 4; ++idx) {
|
||||
try {
|
||||
Shards shards;
|
||||
if (idx < 3) {
|
||||
Node node;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
// Randomly pick a node.
|
||||
node = _get_node(_slot());
|
||||
}
|
||||
|
||||
shards = _get_shards(node.host, node.port);
|
||||
} else {
|
||||
shards = _get_shards(_connection_opts.host, _connection_opts.port);
|
||||
}
|
||||
|
||||
std::unordered_set<Node, NodeHash> nodes;
|
||||
for (const auto &shard : shards) {
|
||||
nodes.insert(shard.second);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
_shards = std::move(shards);
|
||||
|
||||
// Remove non-existent nodes.
|
||||
for (auto iter = _pools.begin(); iter != _pools.end(); ) {
|
||||
if (nodes.find(iter->first) == nodes.end()) {
|
||||
// Node has been removed.
|
||||
_pools.erase(iter++);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Add connection pool for new nodes.
|
||||
// In fact, connections will be created lazily.
|
||||
for (const auto &node : nodes) {
|
||||
if (_pools.find(node) == _pools.end()) {
|
||||
_add_node(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Update successfully.
|
||||
return;
|
||||
} catch (const Error &e) {
|
||||
// continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw Error("Failed to update shards info");
|
||||
}
|
||||
|
||||
auto AsyncShardsPool::_add_node(const Node &node) -> NodeMap::iterator {
|
||||
auto opts = _connection_opts;
|
||||
opts.host = node.host;
|
||||
opts.port = node.port;
|
||||
|
||||
// TODO: Better set readonly an attribute of `Node`.
|
||||
if (_role == Role::SLAVE) {
|
||||
opts.readonly = true;
|
||||
}
|
||||
|
||||
return _pools.emplace(node,
|
||||
std::make_shared<AsyncConnectionPool>(_loop, _pool_opts, opts)).first;
|
||||
}
|
||||
|
||||
bool AsyncShardsPool::_redeliver_events(std::queue<RedeliverEvent> &events) {
|
||||
bool should_stop_worker = false;
|
||||
while (!events.empty()) {
|
||||
auto &event = events.front();
|
||||
try {
|
||||
auto &async_event = event.event;
|
||||
if (!async_event) {
|
||||
should_stop_worker = true;
|
||||
events.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pool = fetch(event.key);
|
||||
assert(pool);
|
||||
|
||||
GuardedAsyncConnection connection(pool);
|
||||
|
||||
connection.connection().send(std::move(async_event));
|
||||
} catch (...) {
|
||||
event.event->set_exception(std::current_exception());
|
||||
}
|
||||
events.pop();
|
||||
}
|
||||
|
||||
return should_stop_worker;
|
||||
}
|
||||
|
||||
bool AsyncShardsPool::_fail_events(std::queue<RedeliverEvent> &events,
|
||||
std::exception_ptr err) {
|
||||
bool should_stop_worker = false;
|
||||
while (!events.empty()) {
|
||||
auto &event = events.front();
|
||||
auto &async_event = event.event;
|
||||
if (!async_event) {
|
||||
should_stop_worker = true;
|
||||
} else {
|
||||
async_event->set_exception(err);
|
||||
}
|
||||
events.pop();
|
||||
}
|
||||
|
||||
return should_stop_worker;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_SHARDS_POOL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_SHARDS_POOL_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <exception>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include "shards_pool.h"
|
||||
#include "async_connection_pool.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class AsyncShardsPool {
|
||||
public:
|
||||
AsyncShardsPool(const AsyncShardsPool &) = delete;
|
||||
AsyncShardsPool& operator=(const AsyncShardsPool &) = delete;
|
||||
|
||||
AsyncShardsPool(AsyncShardsPool &&that);
|
||||
AsyncShardsPool& operator=(AsyncShardsPool &&that);
|
||||
|
||||
~AsyncShardsPool();
|
||||
|
||||
AsyncShardsPool(const EventLoopSPtr &loop,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts,
|
||||
Role role);
|
||||
|
||||
AsyncConnectionPoolSPtr fetch(const StringView &key);
|
||||
|
||||
AsyncConnectionPoolSPtr fetch();
|
||||
|
||||
AsyncConnectionPoolSPtr fetch(const Node &node);
|
||||
|
||||
void update(const std::string &key, AsyncEventUPtr event);
|
||||
|
||||
ConnectionOptions connection_options(const StringView &key);
|
||||
|
||||
ConnectionOptions connection_options();
|
||||
|
||||
private:
|
||||
struct RedeliverEvent {
|
||||
std::string key;
|
||||
AsyncEventUPtr event;
|
||||
};
|
||||
|
||||
void _run();
|
||||
|
||||
void _move(AsyncShardsPool &&that);
|
||||
|
||||
Slot _slot(const StringView &key) const;
|
||||
|
||||
AsyncConnectionPoolSPtr _fetch(Slot slot);
|
||||
|
||||
Slot _slot() const;
|
||||
|
||||
std::queue<RedeliverEvent> _fetch_events();
|
||||
|
||||
void _update_shards();
|
||||
|
||||
bool _redeliver_events(std::queue<RedeliverEvent> &events);
|
||||
|
||||
bool _fail_events(std::queue<RedeliverEvent> &events, std::exception_ptr err);
|
||||
|
||||
using NodeMap = std::unordered_map<Node, AsyncConnectionPoolSPtr, NodeHash>;
|
||||
|
||||
NodeMap::iterator _add_node(const Node &node);
|
||||
|
||||
std::size_t _random(std::size_t min, std::size_t max) const;
|
||||
|
||||
const Node& _get_node(Slot slot) const;
|
||||
|
||||
AsyncConnectionPoolSPtr& _get_pool(Slot slot);
|
||||
|
||||
ConnectionOptions _connection_options(Slot slot);
|
||||
|
||||
Shards _get_shards(const std::string &host, int port);
|
||||
|
||||
ConnectionPoolOptions _pool_opts;
|
||||
|
||||
ConnectionOptions _connection_opts;
|
||||
|
||||
Role _role = Role::MASTER;
|
||||
|
||||
Shards _shards;
|
||||
|
||||
NodeMap _pools;
|
||||
|
||||
EventLoopSPtr _loop;
|
||||
|
||||
std::thread _worker;
|
||||
|
||||
std::condition_variable _cv;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
std::queue<RedeliverEvent> _events;
|
||||
|
||||
static const std::size_t SHARDS = 16383;
|
||||
};
|
||||
|
||||
using AsyncShardsPoolSPtr = std::shared_ptr<AsyncShardsPool>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_SHARDS_POOL_H
|
||||
@@ -0,0 +1,775 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "command_options.h"
|
||||
#include "command_args.h"
|
||||
#include "command.h"
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class FormattedCommand {
|
||||
public:
|
||||
FormattedCommand(char *data, int len) : _data(data), _size(len) {
|
||||
if (data == nullptr || len < 0) {
|
||||
throw Error("failed to format command");
|
||||
}
|
||||
}
|
||||
|
||||
FormattedCommand(const FormattedCommand &) = delete;
|
||||
FormattedCommand& operator=(const FormattedCommand &) = delete;
|
||||
|
||||
FormattedCommand(FormattedCommand &&that) noexcept {
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
FormattedCommand& operator=(FormattedCommand &&that) noexcept {
|
||||
if (this != &that) {
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~FormattedCommand() noexcept {
|
||||
if (_data != nullptr) {
|
||||
redisFreeCommand(_data);
|
||||
}
|
||||
}
|
||||
|
||||
const char* data() const noexcept {
|
||||
return _data;
|
||||
}
|
||||
|
||||
int size() const noexcept {
|
||||
return _size;
|
||||
}
|
||||
|
||||
private:
|
||||
void _move(FormattedCommand &&that) noexcept {
|
||||
_data = that._data;
|
||||
_size = that._size;
|
||||
that._data = nullptr;
|
||||
that._size = 0;
|
||||
}
|
||||
|
||||
char *_data = nullptr;
|
||||
int _size = 0;
|
||||
};
|
||||
|
||||
namespace fmt {
|
||||
|
||||
template <typename ...Args>
|
||||
FormattedCommand format_cmd(const char *format, Args &&...args) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommand(&data, format, std::forward<Args>(args)...);
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
inline FormattedCommand format_cmd(int argc, const char **argv, const std::size_t *argv_len) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommandArgv(&data, argc, argv, argv_len);
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
inline FormattedCommand format_cmd(CmdArgs &args) {
|
||||
char *data = nullptr;
|
||||
auto len = redisFormatCommandArgv(&data, args.size(), args.argv(), args.argv_len());
|
||||
|
||||
return FormattedCommand(data, len);
|
||||
}
|
||||
|
||||
struct SetResultParser {
|
||||
bool operator()(redisReply &reply) const {
|
||||
sw::redis::reply::rewrite_set_reply(reply);
|
||||
return sw::redis::reply::parse<bool>(reply);
|
||||
}
|
||||
};
|
||||
|
||||
// CONNECTION commands.
|
||||
|
||||
inline FormattedCommand echo(const StringView &msg) {
|
||||
return format_cmd("ECHO %b", msg.data(), msg.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ping() {
|
||||
return format_cmd("PING");
|
||||
}
|
||||
|
||||
inline FormattedCommand ping(const StringView &msg) {
|
||||
return format_cmd("PING %b", msg.data(), msg.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand del(const StringView &key) {
|
||||
return format_cmd("DEL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand del_range(Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "DEL" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand exists(const StringView &key) {
|
||||
return format_cmd("EXISTS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand exists_range(Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "EXISTS" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand expire(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("EXPIRE %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand expireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::seconds> &tp) {
|
||||
return format_cmd("EXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pexpire(const StringView &key,
|
||||
const std::chrono::milliseconds &timeout) {
|
||||
return format_cmd("PEXPIRE %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pexpireat(const StringView &key,
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds> &tp) {
|
||||
return format_cmd("PEXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
inline FormattedCommand pttl(const StringView &key) {
|
||||
return format_cmd("PTTL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rename(const StringView &key, const StringView &newkey) {
|
||||
return format_cmd("RENAME %b %b", key.data(), key.size(), newkey.data(), newkey.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand renamenx(const StringView &key, const StringView &newkey) {
|
||||
return format_cmd("RENAMENX %b %b", key.data(), key.size(), newkey.data(), newkey.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ttl(const StringView &key) {
|
||||
return format_cmd("TTL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand unlink(const StringView &key) {
|
||||
return format_cmd("UNLINK %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand unlink_range(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "UNLINK" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// STRING commands.
|
||||
|
||||
inline FormattedCommand get(const StringView &key) {
|
||||
return format_cmd("GET %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand incr(const StringView &key) {
|
||||
return format_cmd("INCR %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand incrby(const StringView &key, long long increment) {
|
||||
return format_cmd("INCRBY %b %lld", key.data(), key.size(), increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand incrbyfloat(const StringView &key, double increment) {
|
||||
return format_cmd("INCRBYFLOAT %b %f", key.data(), key.size(), increment);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand mget(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MGET" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand mset(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MSET" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand msetnx(Input first, Input last) {
|
||||
CmdArgs args;
|
||||
args << "MSETNX" << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand set(const StringView &key,
|
||||
const StringView &val,
|
||||
const std::chrono::milliseconds &ttl,
|
||||
UpdateType type) {
|
||||
CmdArgs args;
|
||||
args << "SET" << key << val;
|
||||
|
||||
if (ttl > std::chrono::milliseconds(0)) {
|
||||
args << "PX" << ttl.count();
|
||||
}
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand strlen(const StringView &key) {
|
||||
return format_cmd("STRLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand blpop(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BLPOP %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand blpop_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BLPOP" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand brpop(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BRPOP %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand brpop_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BRPOP" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand brpoplpush(const StringView &source,
|
||||
const StringView &destination,
|
||||
const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BRPOPLPUSH %b %b %lld",
|
||||
source.data(), source.size(),
|
||||
destination.data(), destination.size(),
|
||||
timeout.count());
|
||||
}
|
||||
|
||||
inline FormattedCommand llen(const StringView &key) {
|
||||
return format_cmd("LLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand lpop(const StringView &key) {
|
||||
return format_cmd("LPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand lpush(const StringView &key, const StringView &val) {
|
||||
return format_cmd("LPUSH %b %b", key.data(), key.size(), val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand lpush_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "LPUSH" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand lrange(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("LRANGE %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
inline FormattedCommand lrem(const StringView &key, long long count, const StringView &val) {
|
||||
return format_cmd("LREM %b %lld %b", key.data(), key.size(), count, val.data(), val.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand ltrim(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("LTRIM %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
inline FormattedCommand rpop(const StringView &key) {
|
||||
return format_cmd("RPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rpoplpush(const StringView &source, const StringView &destination) {
|
||||
return format_cmd("RPOPLPUSH %b %b",
|
||||
source.data(), source.size(),
|
||||
destination.data(), destination.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand rpush(const StringView &key, const StringView &val) {
|
||||
return format_cmd("RPUSH %b %b", key.data(), key.size(), val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand rpush_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "RPUSH" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// HASH commands.
|
||||
|
||||
inline FormattedCommand hdel(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HDEL %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hdel_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HDEL" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hexists(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HEXISTS %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hget(const StringView &key, const StringView &field) {
|
||||
return format_cmd("HGET %b %b", key.data(), key.size(), field.data(), field.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hgetall(const StringView &key) {
|
||||
return format_cmd("HGETALL %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hincrby(const StringView &key,
|
||||
const StringView &field,
|
||||
long long increment) {
|
||||
return format_cmd("HINCRBY %b %b %lld",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand hincrbyfloat(const StringView &key,
|
||||
const StringView &field,
|
||||
double increment) {
|
||||
return format_cmd("HINCRBYFLOAT %b %b %f",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
increment);
|
||||
}
|
||||
|
||||
inline FormattedCommand hkeys(const StringView &key) {
|
||||
return format_cmd("HKEYS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand hlen(const StringView &key) {
|
||||
return format_cmd("HLEN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hmget(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HMGET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand hmset(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HMSET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hset(const StringView &key,
|
||||
const StringView &field,
|
||||
const StringView &val) {
|
||||
return format_cmd("HSET %b %b %b",
|
||||
key.data(), key.size(),
|
||||
field.data(), field.size(),
|
||||
val.data(), val.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
auto hset_range(const StringView &key,
|
||||
Input first,
|
||||
Input last)
|
||||
-> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
|
||||
FormattedCommand>::type {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "HSET" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand hvals(const StringView &key) {
|
||||
return format_cmd("HVALS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
// SET commands.
|
||||
|
||||
inline FormattedCommand sadd(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SADD %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand sadd_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "SADD" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand scard(const StringView &key) {
|
||||
return format_cmd("SCARD %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand sismember(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SISMEMBER %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand smembers(const StringView &key) {
|
||||
return format_cmd("SMEMBERS %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand spop(const StringView &key) {
|
||||
return format_cmd("SPOP %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand spop(const StringView &key, long long count) {
|
||||
return format_cmd("SPOP %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand srem(const StringView &key, const StringView &member) {
|
||||
return format_cmd("SREM %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand srem_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "SREM" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
// SORTED SET commands.
|
||||
|
||||
inline FormattedCommand bzpopmax(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BZPOPMAX %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand bzpopmax_range(Input first,
|
||||
Input last,
|
||||
const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BZPOPMAX" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand bzpopmin(const StringView &key, const std::chrono::seconds &timeout) {
|
||||
return format_cmd("BZPOPMIN %b %lld", key.data(), key.size(), timeout.count());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand bzpopmin_range(Input first, Input last, const std::chrono::seconds &timeout) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "BZPOPMIN" << std::make_pair(first, last) << timeout.count();
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand zadd(const StringView &key,
|
||||
const StringView &member,
|
||||
double score,
|
||||
UpdateType type,
|
||||
bool changed) {
|
||||
CmdArgs args;
|
||||
args << "ZADD" << key;
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
if (changed) {
|
||||
args << "CH";
|
||||
}
|
||||
|
||||
args << score << member;
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand zadd_range(const StringView &key,
|
||||
Input first,
|
||||
Input last,
|
||||
UpdateType type,
|
||||
bool changed) {
|
||||
CmdArgs args;
|
||||
args << "ZADD" << key;
|
||||
|
||||
cmd::detail::set_update_type(args, type);
|
||||
|
||||
if (changed) {
|
||||
args << "CH";
|
||||
}
|
||||
|
||||
while (first != last) {
|
||||
// Swap the <member, score> pair to <score, member> pair.
|
||||
args << first->second << first->first;
|
||||
++first;
|
||||
}
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
inline FormattedCommand zcard(const StringView &key) {
|
||||
return format_cmd("ZCARD %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zcount(const StringView &key, const Interval &interval) {
|
||||
return format_cmd("ZCOUNT %b %s %s",
|
||||
key.data(), key.size(),
|
||||
interval.min().c_str(),
|
||||
interval.max().c_str());
|
||||
}
|
||||
|
||||
inline FormattedCommand zincrby(const StringView &key,
|
||||
double increment,
|
||||
const StringView &member) {
|
||||
return format_cmd("ZINCRBY %b %f %b",
|
||||
key.data(), key.size(),
|
||||
increment,
|
||||
member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zlexcount(const StringView &key,
|
||||
const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZLEXCOUNT %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmax(const StringView &key) {
|
||||
return format_cmd("ZPOPMAX %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmax(const StringView &key, long long count) {
|
||||
return format_cmd("ZPOPMAX %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmin(const StringView &key) {
|
||||
return format_cmd("ZPOPMIN %b", key.data(), key.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zpopmin_count(const StringView &key, long long count) {
|
||||
return format_cmd("ZPOPMIN %b %lld", key.data(), key.size(), count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrange(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("ZRANGE %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZRANGEBYLEX %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrangebyscore(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrank(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZRANK %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zrem(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZREM %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
template <typename Input>
|
||||
FormattedCommand zrem_range(const StringView &key, Input first, Input last) {
|
||||
assert(first != last);
|
||||
|
||||
CmdArgs args;
|
||||
args << "ZREM" << key << std::make_pair(first, last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zremrangebylex(const StringView &key, const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREMRANGEBYLEX %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zremrangebyrank(const StringView &key, long long start, long long stop) {
|
||||
return format_cmd("ZREMRANGEBYRANK %b %lld %lld", key.data(), key.size(), start, stop);
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zremrangebyscore(const StringView &key,
|
||||
const Interval &interval) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREMRANGEBYSCORE %b %b %b",
|
||||
key.data(), key.size(),
|
||||
min.data(), min.size(),
|
||||
max.data(), max.size());
|
||||
}
|
||||
|
||||
template <typename Interval>
|
||||
FormattedCommand zrevrangebylex(const StringView &key,
|
||||
const Interval &interval,
|
||||
const LimitOptions &opts) {
|
||||
const auto &min = interval.min();
|
||||
const auto &max = interval.max();
|
||||
|
||||
return format_cmd("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld",
|
||||
key.data(), key.size(),
|
||||
max.data(), max.size(),
|
||||
min.data(), min.size(),
|
||||
opts.offset,
|
||||
opts.count);
|
||||
}
|
||||
|
||||
inline FormattedCommand zrevrank(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZREVRANK %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
inline FormattedCommand zscore(const StringView &key, const StringView &member) {
|
||||
return format_cmd("ZSCORE %b %b", key.data(), key.size(), member.data(), member.size());
|
||||
}
|
||||
|
||||
// SCRIPTING commands.
|
||||
template <typename Keys, typename Args>
|
||||
FormattedCommand eval(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
CmdArgs args;
|
||||
auto keys_num = std::distance(keys_first, keys_last);
|
||||
|
||||
args << "EVAL" << script << keys_num
|
||||
<< std::make_pair(keys_first, keys_last)
|
||||
<< std::make_pair(args_first, args_last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
template <typename Keys, typename Args>
|
||||
FormattedCommand evalsha(const StringView &script,
|
||||
Keys keys_first,
|
||||
Keys keys_last,
|
||||
Args args_first,
|
||||
Args args_last) {
|
||||
CmdArgs args;
|
||||
auto keys_num = std::distance(keys_first, keys_last);
|
||||
|
||||
args << "EVALSHA" << script << keys_num
|
||||
<< std::make_pair(keys_first, keys_last)
|
||||
<< std::make_pair(args_first, args_last);
|
||||
|
||||
return format_cmd(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H
|
||||
@@ -0,0 +1,376 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "command.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
namespace cmd {
|
||||
|
||||
// KEY commands.
|
||||
|
||||
void restore(Connection &connection,
|
||||
const StringView &key,
|
||||
const StringView &val,
|
||||
long long ttl,
|
||||
bool replace) {
|
||||
CmdArgs args;
|
||||
args << "RESTORE" << key << ttl << val;
|
||||
|
||||
if (replace) {
|
||||
args << "REPLACE";
|
||||
}
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
// STRING commands.
|
||||
|
||||
void bitop(Connection &connection,
|
||||
BitOp op,
|
||||
const StringView &destination,
|
||||
const StringView &key) {
|
||||
CmdArgs args;
|
||||
|
||||
detail::set_bitop(args, op);
|
||||
|
||||
args << destination << key;
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
void set(Connection &connection,
|
||||
const StringView &key,
|
||||
const StringView &val,
|
||||
long long ttl,
|
||||
UpdateType type) {
|
||||
CmdArgs args;
|
||||
args << "SET" << key << val;
|
||||
|
||||
if (ttl > 0) {
|
||||
args << "PX" << ttl;
|
||||
}
|
||||
|
||||
detail::set_update_type(args, type);
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
// LIST commands.
|
||||
|
||||
void linsert(Connection &connection,
|
||||
const StringView &key,
|
||||
InsertPosition position,
|
||||
const StringView &pivot,
|
||||
const StringView &val) {
|
||||
std::string pos;
|
||||
switch (position) {
|
||||
case InsertPosition::BEFORE:
|
||||
pos = "BEFORE";
|
||||
break;
|
||||
|
||||
case InsertPosition::AFTER:
|
||||
pos = "AFTER";
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
connection.send("LINSERT %b %s %b %b",
|
||||
key.data(), key.size(),
|
||||
pos.c_str(),
|
||||
pivot.data(), pivot.size(),
|
||||
val.data(), val.size());
|
||||
}
|
||||
|
||||
// GEO commands.
|
||||
|
||||
void geodist(Connection &connection,
|
||||
const StringView &key,
|
||||
const StringView &member1,
|
||||
const StringView &member2,
|
||||
GeoUnit unit) {
|
||||
CmdArgs args;
|
||||
args << "GEODIST" << key << member1 << member2;
|
||||
|
||||
detail::set_geo_unit(args, unit);
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
void georadius_store(Connection &connection,
|
||||
const StringView &key,
|
||||
const std::pair<double, double> &loc,
|
||||
double radius,
|
||||
GeoUnit unit,
|
||||
const StringView &destination,
|
||||
bool store_dist,
|
||||
long long count) {
|
||||
CmdArgs args;
|
||||
args << "GEORADIUS" << key << loc.first << loc.second;
|
||||
|
||||
detail::set_georadius_store_parameters(args,
|
||||
radius,
|
||||
unit,
|
||||
destination,
|
||||
store_dist,
|
||||
count);
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
void georadius(Connection &connection,
|
||||
const StringView &key,
|
||||
const std::pair<double, double> &loc,
|
||||
double radius,
|
||||
GeoUnit unit,
|
||||
long long count,
|
||||
bool asc,
|
||||
bool with_coord,
|
||||
bool with_dist,
|
||||
bool with_hash) {
|
||||
CmdArgs args;
|
||||
args << "GEORADIUS" << key << loc.first << loc.second;
|
||||
|
||||
detail::set_georadius_parameters(args,
|
||||
radius,
|
||||
unit,
|
||||
count,
|
||||
asc,
|
||||
with_coord,
|
||||
with_dist,
|
||||
with_hash);
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
void georadiusbymember(Connection &connection,
|
||||
const StringView &key,
|
||||
const StringView &member,
|
||||
double radius,
|
||||
GeoUnit unit,
|
||||
long long count,
|
||||
bool asc,
|
||||
bool with_coord,
|
||||
bool with_dist,
|
||||
bool with_hash) {
|
||||
CmdArgs args;
|
||||
args << "GEORADIUSBYMEMBER" << key << member;
|
||||
|
||||
detail::set_georadius_parameters(args,
|
||||
radius,
|
||||
unit,
|
||||
count,
|
||||
asc,
|
||||
with_coord,
|
||||
with_dist,
|
||||
with_hash);
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
void georadiusbymember_store(Connection &connection,
|
||||
const StringView &key,
|
||||
const StringView &member,
|
||||
double radius,
|
||||
GeoUnit unit,
|
||||
const StringView &destination,
|
||||
bool store_dist,
|
||||
long long count) {
|
||||
CmdArgs args;
|
||||
args << "GEORADIUSBYMEMBER" << key << member;
|
||||
|
||||
detail::set_georadius_store_parameters(args,
|
||||
radius,
|
||||
unit,
|
||||
destination,
|
||||
store_dist,
|
||||
count);
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
// Stream commands.
|
||||
|
||||
void xtrim(Connection &connection, const StringView &key, long long count, bool approx) {
|
||||
CmdArgs args;
|
||||
args << "XTRIM" << key << "MAXLEN";
|
||||
|
||||
if (approx) {
|
||||
args << "~";
|
||||
}
|
||||
|
||||
args << count;
|
||||
|
||||
connection.send(args);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
void set_bitop(CmdArgs &args, BitOp op) {
|
||||
args << "BITOP";
|
||||
|
||||
switch (op) {
|
||||
case BitOp::AND:
|
||||
args << "AND";
|
||||
break;
|
||||
|
||||
case BitOp::OR:
|
||||
args << "OR";
|
||||
break;
|
||||
|
||||
case BitOp::XOR:
|
||||
args << "XOR";
|
||||
break;
|
||||
|
||||
case BitOp::NOT:
|
||||
args << "NOT";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Unknown bit operations");
|
||||
}
|
||||
}
|
||||
|
||||
void set_update_type(CmdArgs &args, UpdateType type) {
|
||||
switch (type) {
|
||||
case UpdateType::EXIST:
|
||||
args << "XX";
|
||||
break;
|
||||
|
||||
case UpdateType::NOT_EXIST:
|
||||
args << "NX";
|
||||
break;
|
||||
|
||||
case UpdateType::ALWAYS:
|
||||
// Do nothing.
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Unknown update type");
|
||||
}
|
||||
}
|
||||
|
||||
void set_aggregation_type(CmdArgs &args, Aggregation aggr) {
|
||||
args << "AGGREGATE";
|
||||
|
||||
switch (aggr) {
|
||||
case Aggregation::SUM:
|
||||
args << "SUM";
|
||||
break;
|
||||
|
||||
case Aggregation::MIN:
|
||||
args << "MIN";
|
||||
break;
|
||||
|
||||
case Aggregation::MAX:
|
||||
args << "MAX";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Unknown aggregation type");
|
||||
}
|
||||
}
|
||||
|
||||
void set_geo_unit(CmdArgs &args, GeoUnit unit) {
|
||||
switch (unit) {
|
||||
case GeoUnit::M:
|
||||
args << "m";
|
||||
break;
|
||||
|
||||
case GeoUnit::KM:
|
||||
args << "km";
|
||||
break;
|
||||
|
||||
case GeoUnit::MI:
|
||||
args << "mi";
|
||||
break;
|
||||
|
||||
case GeoUnit::FT:
|
||||
args << "ft";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Unknown geo unit type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_georadius_store_parameters(CmdArgs &args,
|
||||
double radius,
|
||||
GeoUnit unit,
|
||||
const StringView &destination,
|
||||
bool store_dist,
|
||||
long long count) {
|
||||
args << radius;
|
||||
|
||||
detail::set_geo_unit(args, unit);
|
||||
|
||||
args << "COUNT" << count;
|
||||
|
||||
if (store_dist) {
|
||||
args << "STOREDIST";
|
||||
} else {
|
||||
args << "STORE";
|
||||
}
|
||||
|
||||
args << destination;
|
||||
}
|
||||
|
||||
void set_georadius_parameters(CmdArgs &args,
|
||||
double radius,
|
||||
GeoUnit unit,
|
||||
long long count,
|
||||
bool asc,
|
||||
bool with_coord,
|
||||
bool with_dist,
|
||||
bool with_hash) {
|
||||
args << radius;
|
||||
|
||||
detail::set_geo_unit(args, unit);
|
||||
|
||||
if (with_coord) {
|
||||
args << "WITHCOORD";
|
||||
}
|
||||
|
||||
if (with_dist) {
|
||||
args << "WITHDIST";
|
||||
}
|
||||
|
||||
if (with_hash) {
|
||||
args << "WITHHASH";
|
||||
}
|
||||
|
||||
args << "COUNT" << count;
|
||||
|
||||
if (asc) {
|
||||
args << "ASC";
|
||||
} else {
|
||||
args << "DESC";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,180 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class CmdArgs {
|
||||
public:
|
||||
template <typename Arg>
|
||||
CmdArgs& append(Arg &&arg);
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
CmdArgs& append(Arg &&arg, Args &&...args);
|
||||
|
||||
// All overloads of operator<< are for internal use only.
|
||||
CmdArgs& operator<<(const StringView &arg);
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type = 0>
|
||||
CmdArgs& operator<<(T &&arg);
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& operator<<(const std::pair<Iter, Iter> &range);
|
||||
|
||||
template <std::size_t N, typename ...Args>
|
||||
auto operator<<(const std::tuple<Args...> &) ->
|
||||
typename std::enable_if<N == sizeof...(Args), CmdArgs&>::type {
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <std::size_t N = 0, typename ...Args>
|
||||
auto operator<<(const std::tuple<Args...> &arg) ->
|
||||
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type;
|
||||
|
||||
const char** argv() {
|
||||
return _argv.data();
|
||||
}
|
||||
|
||||
const std::size_t* argv_len() {
|
||||
return _argv_len.data();
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return _argv.size();
|
||||
}
|
||||
|
||||
private:
|
||||
// Deep copy.
|
||||
CmdArgs& _append(std::string arg);
|
||||
|
||||
// Shallow copy.
|
||||
CmdArgs& _append(const StringView &arg);
|
||||
|
||||
// Shallow copy.
|
||||
CmdArgs& _append(const char *arg);
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type = 0>
|
||||
CmdArgs& _append(T &&arg) {
|
||||
return operator<<(std::forward<T>(arg));
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& _append(std::true_type, const std::pair<Iter, Iter> &range);
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& _append(std::false_type, const std::pair<Iter, Iter> &range);
|
||||
|
||||
std::vector<const char *> _argv;
|
||||
std::vector<std::size_t> _argv_len;
|
||||
|
||||
std::list<std::string> _args;
|
||||
};
|
||||
|
||||
template <typename Arg>
|
||||
inline CmdArgs& CmdArgs::append(Arg &&arg) {
|
||||
return _append(std::forward<Arg>(arg));
|
||||
}
|
||||
|
||||
template <typename Arg, typename ...Args>
|
||||
inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) {
|
||||
_append(std::forward<Arg>(arg));
|
||||
|
||||
return append(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::operator<<(const StringView &arg) {
|
||||
_argv.push_back(arg.data());
|
||||
_argv_len.push_back(arg.size());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
inline CmdArgs& CmdArgs::operator<<(const std::pair<Iter, Iter> &range) {
|
||||
return _append(IsKvPair<typename std::decay<decltype(*std::declval<Iter>())>::type>(), range);
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
|
||||
int>::type>
|
||||
inline CmdArgs& CmdArgs::operator<<(T &&arg) {
|
||||
return _append(std::to_string(std::forward<T>(arg)));
|
||||
}
|
||||
|
||||
template <std::size_t N, typename ...Args>
|
||||
auto CmdArgs::operator<<(const std::tuple<Args...> &arg) ->
|
||||
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type {
|
||||
operator<<(std::get<N>(arg));
|
||||
|
||||
return operator<<<N + 1, Args...>(arg);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(std::string arg) {
|
||||
_args.push_back(std::move(arg));
|
||||
return operator<<(_args.back());
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(const StringView &arg) {
|
||||
return operator<<(arg);
|
||||
}
|
||||
|
||||
inline CmdArgs& CmdArgs::_append(const char *arg) {
|
||||
return operator<<(arg);
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& CmdArgs::_append(std::false_type, const std::pair<Iter, Iter> &range) {
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
while (first != last) {
|
||||
*this << *first;
|
||||
++first;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
CmdArgs& CmdArgs::_append(std::true_type, const std::pair<Iter, Iter> &range) {
|
||||
auto first = range.first;
|
||||
auto last = range.second;
|
||||
while (first != last) {
|
||||
*this << first->first << first->second;
|
||||
++first;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
|
||||
@@ -0,0 +1,201 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "command_options.h"
|
||||
#include "errors.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const std::string NEGATIVE_INFINITY_NUMERIC = "-inf";
|
||||
const std::string POSITIVE_INFINITY_NUMERIC = "+inf";
|
||||
|
||||
const std::string NEGATIVE_INFINITY_STRING = "-";
|
||||
const std::string POSITIVE_INFINITY_STRING = "+";
|
||||
|
||||
std::string unbound(const std::string &bnd);
|
||||
|
||||
std::string bound(const std::string &bnd);
|
||||
|
||||
}
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
const std::string& UnboundedInterval<double>::min() const {
|
||||
return NEGATIVE_INFINITY_NUMERIC;
|
||||
}
|
||||
|
||||
const std::string& UnboundedInterval<double>::max() const {
|
||||
return POSITIVE_INFINITY_NUMERIC;
|
||||
}
|
||||
|
||||
BoundedInterval<double>::BoundedInterval(double min, double max, BoundType type) :
|
||||
_min(std::to_string(min)),
|
||||
_max(std::to_string(max)) {
|
||||
switch (type) {
|
||||
case BoundType::CLOSED:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
case BoundType::OPEN:
|
||||
_min = unbound(_min);
|
||||
_max = unbound(_max);
|
||||
break;
|
||||
|
||||
case BoundType::LEFT_OPEN:
|
||||
_min = unbound(_min);
|
||||
break;
|
||||
|
||||
case BoundType::RIGHT_OPEN:
|
||||
_max = unbound(_max);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Unknow BoundType");
|
||||
}
|
||||
}
|
||||
|
||||
LeftBoundedInterval<double>::LeftBoundedInterval(double min, BoundType type) :
|
||||
_min(std::to_string(min)) {
|
||||
switch (type) {
|
||||
case BoundType::OPEN:
|
||||
_min = unbound(_min);
|
||||
break;
|
||||
|
||||
case BoundType::RIGHT_OPEN:
|
||||
// Do nothing.
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Bound type can only be OPEN or RIGHT_OPEN");
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& LeftBoundedInterval<double>::max() const {
|
||||
return POSITIVE_INFINITY_NUMERIC;
|
||||
}
|
||||
|
||||
RightBoundedInterval<double>::RightBoundedInterval(double max, BoundType type) :
|
||||
_max(std::to_string(max)) {
|
||||
switch (type) {
|
||||
case BoundType::OPEN:
|
||||
_max = unbound(_max);
|
||||
break;
|
||||
|
||||
case BoundType::LEFT_OPEN:
|
||||
// Do nothing.
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Bound type can only be OPEN or LEFT_OPEN");
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& RightBoundedInterval<double>::min() const {
|
||||
return NEGATIVE_INFINITY_NUMERIC;
|
||||
}
|
||||
|
||||
const std::string& UnboundedInterval<std::string>::min() const {
|
||||
return NEGATIVE_INFINITY_STRING;
|
||||
}
|
||||
|
||||
const std::string& UnboundedInterval<std::string>::max() const {
|
||||
return POSITIVE_INFINITY_STRING;
|
||||
}
|
||||
|
||||
BoundedInterval<std::string>::BoundedInterval(const std::string &min,
|
||||
const std::string &max,
|
||||
BoundType type) {
|
||||
switch (type) {
|
||||
case BoundType::CLOSED:
|
||||
_min = bound(min);
|
||||
_max = bound(max);
|
||||
break;
|
||||
|
||||
case BoundType::OPEN:
|
||||
_min = unbound(min);
|
||||
_max = unbound(max);
|
||||
break;
|
||||
|
||||
case BoundType::LEFT_OPEN:
|
||||
_min = unbound(min);
|
||||
_max = bound(max);
|
||||
break;
|
||||
|
||||
case BoundType::RIGHT_OPEN:
|
||||
_min = bound(min);
|
||||
_max = unbound(max);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Unknow BoundType");
|
||||
}
|
||||
}
|
||||
|
||||
LeftBoundedInterval<std::string>::LeftBoundedInterval(const std::string &min, BoundType type) {
|
||||
switch (type) {
|
||||
case BoundType::OPEN:
|
||||
_min = unbound(min);
|
||||
break;
|
||||
|
||||
case BoundType::RIGHT_OPEN:
|
||||
_min = bound(min);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Bound type can only be OPEN or RIGHT_OPEN");
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& LeftBoundedInterval<std::string>::max() const {
|
||||
return POSITIVE_INFINITY_STRING;
|
||||
}
|
||||
|
||||
RightBoundedInterval<std::string>::RightBoundedInterval(const std::string &max, BoundType type) {
|
||||
switch (type) {
|
||||
case BoundType::OPEN:
|
||||
_max = unbound(max);
|
||||
break;
|
||||
|
||||
case BoundType::LEFT_OPEN:
|
||||
_max = bound(max);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Bound type can only be OPEN or LEFT_OPEN");
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& RightBoundedInterval<std::string>::min() const {
|
||||
return NEGATIVE_INFINITY_STRING;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string unbound(const std::string &bnd) {
|
||||
return "(" + bnd;
|
||||
}
|
||||
|
||||
std::string bound(const std::string &bnd) {
|
||||
return "[" + bnd;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
|
||||
#include <string>
|
||||
#include "utils.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum class UpdateType {
|
||||
EXIST,
|
||||
NOT_EXIST,
|
||||
ALWAYS
|
||||
};
|
||||
|
||||
enum class InsertPosition {
|
||||
BEFORE,
|
||||
AFTER
|
||||
};
|
||||
|
||||
enum class BoundType {
|
||||
CLOSED,
|
||||
OPEN,
|
||||
LEFT_OPEN,
|
||||
RIGHT_OPEN
|
||||
};
|
||||
|
||||
// (-inf, +inf)
|
||||
template <typename T>
|
||||
class UnboundedInterval;
|
||||
|
||||
// [min, max], (min, max), (min, max], [min, max)
|
||||
template <typename T>
|
||||
class BoundedInterval;
|
||||
|
||||
// [min, +inf), (min, +inf)
|
||||
template <typename T>
|
||||
class LeftBoundedInterval;
|
||||
|
||||
// (-inf, max], (-inf, max)
|
||||
template <typename T>
|
||||
class RightBoundedInterval;
|
||||
|
||||
template <>
|
||||
class UnboundedInterval<double> {
|
||||
public:
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
class BoundedInterval<double> {
|
||||
public:
|
||||
BoundedInterval(double min, double max, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class LeftBoundedInterval<double> {
|
||||
public:
|
||||
LeftBoundedInterval(double min, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const;
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
};
|
||||
|
||||
template <>
|
||||
class RightBoundedInterval<double> {
|
||||
public:
|
||||
RightBoundedInterval(double max, BoundType type);
|
||||
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class UnboundedInterval<std::string> {
|
||||
public:
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
class BoundedInterval<std::string> {
|
||||
public:
|
||||
BoundedInterval(const std::string &min, const std::string &max, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
template <>
|
||||
class LeftBoundedInterval<std::string> {
|
||||
public:
|
||||
LeftBoundedInterval(const std::string &min, BoundType type);
|
||||
|
||||
const std::string& min() const {
|
||||
return _min;
|
||||
}
|
||||
|
||||
const std::string& max() const;
|
||||
|
||||
private:
|
||||
std::string _min;
|
||||
};
|
||||
|
||||
template <>
|
||||
class RightBoundedInterval<std::string> {
|
||||
public:
|
||||
RightBoundedInterval(const std::string &max, BoundType type);
|
||||
|
||||
const std::string& min() const;
|
||||
|
||||
const std::string& max() const {
|
||||
return _max;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _max;
|
||||
};
|
||||
|
||||
struct LimitOptions {
|
||||
long long offset = 0;
|
||||
long long count = -1;
|
||||
};
|
||||
|
||||
enum class Aggregation {
|
||||
SUM,
|
||||
MIN,
|
||||
MAX
|
||||
};
|
||||
|
||||
enum class BitOp {
|
||||
AND,
|
||||
OR,
|
||||
XOR,
|
||||
NOT
|
||||
};
|
||||
|
||||
enum class GeoUnit {
|
||||
M,
|
||||
KM,
|
||||
MI,
|
||||
FT
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct WithCoord : TupleWithType<std::pair<double, double>, T> {};
|
||||
|
||||
template <typename T>
|
||||
struct WithDist : TupleWithType<double, T> {};
|
||||
|
||||
template <typename T>
|
||||
struct WithHash : TupleWithType<long long, T> {};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
|
||||
@@ -0,0 +1,494 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "connection.h"
|
||||
#include <cassert>
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
#include "reply.h"
|
||||
#include "command.h"
|
||||
#include "command_args.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <winsock2.h> // for `timeval` with MSVC compiler
|
||||
|
||||
#endif
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
ConnectionOptions::ConnectionOptions(const std::string &uri) :
|
||||
ConnectionOptions(_parse_uri(uri)) {}
|
||||
|
||||
ConnectionOptions ConnectionOptions::_parse_uri(const std::string &uri) const {
|
||||
std::string type;
|
||||
std::string auth;
|
||||
std::string path;
|
||||
std::tie(type, auth, path) = _split_uri(uri);
|
||||
|
||||
ConnectionOptions opts;
|
||||
|
||||
_set_auth_opts(auth, opts);
|
||||
|
||||
auto db = 0;
|
||||
std::string parameter_string;
|
||||
std::tie(path, db, parameter_string) = _split_path(path);
|
||||
|
||||
_parse_parameters(parameter_string, opts);
|
||||
|
||||
opts.db = db;
|
||||
|
||||
if (type == "tcp") {
|
||||
_set_tcp_opts(path, opts);
|
||||
} else if (type == "unix") {
|
||||
_set_unix_opts(path, opts);
|
||||
} else {
|
||||
throw Error("invalid URI: invalid type");
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
void ConnectionOptions::_parse_parameters(const std::string ¶meter_string,
|
||||
ConnectionOptions &opts) const {
|
||||
auto parameters = _split(parameter_string, "&");
|
||||
if (parameters.empty()) {
|
||||
// No parameters
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto ¶meter : parameters) {
|
||||
auto kv_pair = _split(parameter, "=");
|
||||
if (kv_pair.size() != 2) {
|
||||
throw Error("invalid option: not a key-value pair: " + parameter);
|
||||
}
|
||||
|
||||
const auto &key = kv_pair[0];
|
||||
const auto &val = kv_pair[1];
|
||||
_set_option(key, val, opts);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionOptions::_set_option(const std::string &key,
|
||||
const std::string &val,
|
||||
ConnectionOptions &opts) const {
|
||||
if (key == "keep_alive") {
|
||||
opts.keep_alive = _parse_bool_option(val);
|
||||
} else if (key == "connect_timeout") {
|
||||
opts.connect_timeout = _parse_timeout_option(val);
|
||||
} else if (key == "socket_timeout") {
|
||||
opts.socket_timeout = _parse_timeout_option(val);
|
||||
} else {
|
||||
throw Error("unknown uri parameter");
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionOptions::_parse_bool_option(const std::string &str) const {
|
||||
if (str == "true") {
|
||||
return true;
|
||||
} else if (str == "false") {
|
||||
return false;
|
||||
} else {
|
||||
throw Error("invalid uri parameter of bool type: " + str);
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::milliseconds ConnectionOptions::_parse_timeout_option(const std::string &str) const {
|
||||
std::size_t timeout = 0;
|
||||
std::string unit;
|
||||
try {
|
||||
std::size_t pos = 0;
|
||||
timeout = std::stoul(str, &pos);
|
||||
unit = str.substr(pos);
|
||||
} catch (const std::exception &e) {
|
||||
throw Error("invalid uri parameter of timeout type: " + str);
|
||||
}
|
||||
|
||||
if (unit == "ms") {
|
||||
return std::chrono::milliseconds(timeout);
|
||||
} else if (unit == "s") {
|
||||
return std::chrono::seconds(timeout);
|
||||
} else if (unit == "m") {
|
||||
return std::chrono::minutes(timeout);
|
||||
} else {
|
||||
throw Error("unknown timeout unit: " + unit);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ConnectionOptions::_split(const std::string &str,
|
||||
const std::string &delimiter) const {
|
||||
if (str.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> fields;
|
||||
|
||||
if (delimiter.empty()) {
|
||||
std::transform(str.begin(), str.end(), std::back_inserter(fields),
|
||||
[](char c) { return std::string(1, c); });
|
||||
return fields;
|
||||
}
|
||||
|
||||
std::string::size_type pos = 0;
|
||||
std::string::size_type idx = 0;
|
||||
while (true) {
|
||||
pos = str.find(delimiter, idx);
|
||||
if (pos == std::string::npos) {
|
||||
fields.push_back(str.substr(idx));
|
||||
break;
|
||||
}
|
||||
|
||||
fields.push_back(str.substr(idx, pos - idx));
|
||||
idx = pos + delimiter.size();
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
auto ConnectionOptions::_split_uri(const std::string &uri) const
|
||||
-> std::tuple<std::string, std::string, std::string> {
|
||||
auto pos = uri.find("://");
|
||||
if (pos == std::string::npos) {
|
||||
throw Error("invalid URI: no scheme");
|
||||
}
|
||||
|
||||
auto type = uri.substr(0, pos);
|
||||
|
||||
auto start = pos + 3;
|
||||
pos = uri.find("@", start);
|
||||
if (pos == std::string::npos) {
|
||||
// No auth info.
|
||||
return std::make_tuple(type, std::string{}, uri.substr(start));
|
||||
}
|
||||
|
||||
auto auth = uri.substr(start, pos - start);
|
||||
|
||||
return std::make_tuple(type, auth, uri.substr(pos + 1));
|
||||
}
|
||||
|
||||
auto ConnectionOptions::_split_path(const std::string &path) const
|
||||
-> std::tuple<std::string, int, std::string> {
|
||||
auto parameter_pos = path.rfind("?");
|
||||
std::string parameter_string;
|
||||
if (parameter_pos != std::string::npos) {
|
||||
parameter_string = path.substr(parameter_pos + 1);
|
||||
}
|
||||
|
||||
auto pos = path.rfind("/");
|
||||
if (pos != std::string::npos) {
|
||||
// Might specified a db number.
|
||||
try {
|
||||
auto db = std::stoi(path.substr(pos + 1));
|
||||
|
||||
return std::make_tuple(path.substr(0, pos), db, parameter_string);
|
||||
} catch (const std::exception &) {
|
||||
// Not a db number, and it might be a path to unix domain socket.
|
||||
}
|
||||
}
|
||||
|
||||
// No db number specified, and use default one, i.e. 0.
|
||||
return std::make_tuple(path.substr(0, parameter_pos), 0, parameter_string);
|
||||
}
|
||||
|
||||
void ConnectionOptions::_set_auth_opts(const std::string &auth, ConnectionOptions &opts) const {
|
||||
if (auth.empty()) {
|
||||
// No auth info.
|
||||
return;
|
||||
}
|
||||
|
||||
auto pos = auth.find(":");
|
||||
if (pos == std::string::npos) {
|
||||
// No user name.
|
||||
opts.password = auth;
|
||||
} else {
|
||||
opts.user = auth.substr(0, pos);
|
||||
opts.password = auth.substr(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionOptions::_set_tcp_opts(const std::string &path, ConnectionOptions &opts) const {
|
||||
opts.type = ConnectionType::TCP;
|
||||
|
||||
auto pos = path.find(":");
|
||||
if (pos != std::string::npos) {
|
||||
// Port number specified.
|
||||
try {
|
||||
opts.port = std::stoi(path.substr(pos + 1));
|
||||
} catch (const std::exception &) {
|
||||
throw Error("invalid URI: invalid port");
|
||||
}
|
||||
} // else use default port, i.e. 6379.
|
||||
|
||||
opts.host = path.substr(0, pos);
|
||||
}
|
||||
|
||||
void ConnectionOptions::_set_unix_opts(const std::string &path, ConnectionOptions &opts) const {
|
||||
opts.type = ConnectionType::UNIX;
|
||||
opts.path = path;
|
||||
}
|
||||
|
||||
class Connection::Connector {
|
||||
public:
|
||||
explicit Connector(const ConnectionOptions &opts);
|
||||
|
||||
ContextUPtr connect() const;
|
||||
|
||||
private:
|
||||
ContextUPtr _connect() const;
|
||||
|
||||
redisContext* _connect_tcp() const;
|
||||
|
||||
redisContext* _connect_unix() const;
|
||||
|
||||
void _set_socket_timeout(redisContext &ctx) const;
|
||||
|
||||
void _enable_keep_alive(redisContext &ctx) const;
|
||||
|
||||
timeval _to_timeval(const std::chrono::milliseconds &dur) const;
|
||||
|
||||
const ConnectionOptions &_opts;
|
||||
};
|
||||
|
||||
Connection::Connector::Connector(const ConnectionOptions &opts) : _opts(opts) {}
|
||||
|
||||
Connection::ContextUPtr Connection::Connector::connect() const {
|
||||
auto ctx = _connect();
|
||||
|
||||
assert(ctx);
|
||||
|
||||
if (ctx->err != REDIS_OK) {
|
||||
throw_error(*ctx, "Failed to connect to Redis");
|
||||
}
|
||||
|
||||
_set_socket_timeout(*ctx);
|
||||
|
||||
_enable_keep_alive(*ctx);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
Connection::ContextUPtr Connection::Connector::_connect() const {
|
||||
redisContext *context = nullptr;
|
||||
switch (_opts.type) {
|
||||
case ConnectionType::TCP:
|
||||
context = _connect_tcp();
|
||||
break;
|
||||
|
||||
case ConnectionType::UNIX:
|
||||
context = _connect_unix();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Never goes here.
|
||||
throw Error("Unknown connection type");
|
||||
}
|
||||
|
||||
if (context == nullptr) {
|
||||
throw Error("Failed to allocate memory for connection.");
|
||||
}
|
||||
|
||||
return ContextUPtr(context);
|
||||
}
|
||||
|
||||
redisContext* Connection::Connector::_connect_tcp() const {
|
||||
if (_opts.connect_timeout > std::chrono::milliseconds(0)) {
|
||||
return redisConnectWithTimeout(_opts.host.c_str(),
|
||||
_opts.port,
|
||||
_to_timeval(_opts.connect_timeout));
|
||||
} else {
|
||||
return redisConnect(_opts.host.c_str(), _opts.port);
|
||||
}
|
||||
}
|
||||
|
||||
redisContext* Connection::Connector::_connect_unix() const {
|
||||
if (_opts.connect_timeout > std::chrono::milliseconds(0)) {
|
||||
return redisConnectUnixWithTimeout(
|
||||
_opts.path.c_str(),
|
||||
_to_timeval(_opts.connect_timeout));
|
||||
} else {
|
||||
return redisConnectUnix(_opts.path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Connector::_set_socket_timeout(redisContext &ctx) const {
|
||||
if (_opts.socket_timeout <= std::chrono::milliseconds(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (redisSetTimeout(&ctx, _to_timeval(_opts.socket_timeout)) != REDIS_OK) {
|
||||
throw_error(ctx, "Failed to set socket timeout");
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Connector::_enable_keep_alive(redisContext &ctx) const {
|
||||
if (!_opts.keep_alive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (redisEnableKeepAlive(&ctx) != REDIS_OK) {
|
||||
throw_error(ctx, "Failed to enable keep alive option");
|
||||
}
|
||||
}
|
||||
|
||||
timeval Connection::Connector::_to_timeval(const std::chrono::milliseconds &dur) const {
|
||||
auto sec = std::chrono::duration_cast<std::chrono::seconds>(dur);
|
||||
auto msec = std::chrono::duration_cast<std::chrono::microseconds>(dur - sec);
|
||||
|
||||
timeval t;
|
||||
t.tv_sec = sec.count();
|
||||
t.tv_usec = msec.count();
|
||||
return t;
|
||||
}
|
||||
|
||||
void swap(Connection &lhs, Connection &rhs) noexcept {
|
||||
std::swap(lhs._ctx, rhs._ctx);
|
||||
std::swap(lhs._create_time, rhs._create_time);
|
||||
std::swap(lhs._opts, rhs._opts);
|
||||
}
|
||||
|
||||
Connection::Connection(const ConnectionOptions &opts) :
|
||||
_ctx(Connector(opts).connect()),
|
||||
_create_time(std::chrono::steady_clock::now()),
|
||||
_last_active(std::chrono::steady_clock::now()),
|
||||
_opts(opts) {
|
||||
assert(_ctx && !broken());
|
||||
|
||||
const auto &tls_opts = opts.tls;
|
||||
// If not compiled with TLS, TLS is always disabled.
|
||||
if (tls::enabled(tls_opts)) {
|
||||
_tls_ctx = tls::secure_connection(*_ctx, tls_opts);
|
||||
}
|
||||
|
||||
_set_options();
|
||||
}
|
||||
|
||||
void Connection::reconnect() {
|
||||
Connection connection(_opts);
|
||||
|
||||
swap(*this, connection);
|
||||
}
|
||||
|
||||
void Connection::send(int argc, const char **argv, const std::size_t *argv_len) {
|
||||
auto ctx = _context();
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
if (redisAppendCommandArgv(ctx,
|
||||
argc,
|
||||
argv,
|
||||
argv_len) != REDIS_OK) {
|
||||
throw_error(*ctx, "Failed to send command");
|
||||
}
|
||||
|
||||
assert(!broken());
|
||||
}
|
||||
|
||||
void Connection::send(CmdArgs &args) {
|
||||
auto ctx = _context();
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
if (redisAppendCommandArgv(ctx,
|
||||
args.size(),
|
||||
args.argv(),
|
||||
args.argv_len()) != REDIS_OK) {
|
||||
throw_error(*ctx, "Failed to send command");
|
||||
}
|
||||
|
||||
assert(!broken());
|
||||
}
|
||||
|
||||
ReplyUPtr Connection::recv(bool handle_error_reply) {
|
||||
auto *ctx = _context();
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
void *r = nullptr;
|
||||
if (redisGetReply(ctx, &r) != REDIS_OK) {
|
||||
throw_error(*ctx, "Failed to get reply");
|
||||
}
|
||||
|
||||
assert(!broken() && r != nullptr);
|
||||
|
||||
auto reply = ReplyUPtr(static_cast<redisReply*>(r));
|
||||
|
||||
if (handle_error_reply && reply::is_error(*reply)) {
|
||||
throw_error(*reply);
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
void Connection::_set_options() {
|
||||
_auth();
|
||||
|
||||
_select_db();
|
||||
|
||||
if (_opts.readonly) {
|
||||
_enable_readonly();
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::_enable_readonly() {
|
||||
send("READONLY");
|
||||
|
||||
auto reply = recv();
|
||||
|
||||
assert(reply);
|
||||
|
||||
reply::parse<void>(*reply);
|
||||
}
|
||||
|
||||
void Connection::_auth() {
|
||||
const std::string DEFAULT_USER = "default";
|
||||
|
||||
if (_opts.user == DEFAULT_USER && _opts.password.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_opts.user == DEFAULT_USER) {
|
||||
cmd::auth(*this, _opts.password);
|
||||
} else {
|
||||
// Redis 6.0 or latter
|
||||
cmd::auth(*this, _opts.user, _opts.password);
|
||||
}
|
||||
|
||||
auto reply = recv();
|
||||
|
||||
assert(reply);
|
||||
|
||||
reply::parse<void>(*reply);
|
||||
}
|
||||
|
||||
void Connection::_select_db() {
|
||||
if (_opts.db == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd::select(*this, _opts.db);
|
||||
|
||||
auto reply = recv();
|
||||
|
||||
assert(reply);
|
||||
|
||||
reply::parse<void>(*reply);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <hiredis/hiredis.h>
|
||||
#include "errors.h"
|
||||
#include "reply.h"
|
||||
#include "utils.h"
|
||||
#include "tls.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum class ConnectionType {
|
||||
TCP = 0,
|
||||
UNIX
|
||||
};
|
||||
|
||||
struct ConnectionOptions {
|
||||
public:
|
||||
ConnectionOptions() = default;
|
||||
|
||||
explicit ConnectionOptions(const std::string &uri);
|
||||
|
||||
ConnectionOptions(const ConnectionOptions &) = default;
|
||||
ConnectionOptions& operator=(const ConnectionOptions &) = default;
|
||||
|
||||
ConnectionOptions(ConnectionOptions &&) = default;
|
||||
ConnectionOptions& operator=(ConnectionOptions &&) = default;
|
||||
|
||||
~ConnectionOptions() = default;
|
||||
|
||||
ConnectionType type = ConnectionType::TCP;
|
||||
|
||||
std::string host;
|
||||
|
||||
int port = 6379;
|
||||
|
||||
std::string path;
|
||||
|
||||
std::string user = "default";
|
||||
|
||||
std::string password;
|
||||
|
||||
int db = 0;
|
||||
|
||||
bool keep_alive = false;
|
||||
|
||||
std::chrono::milliseconds connect_timeout{0};
|
||||
|
||||
std::chrono::milliseconds socket_timeout{0};
|
||||
|
||||
tls::TlsOptions tls;
|
||||
|
||||
// `readonly` is only used for reading from a slave node in Redis Cluster mode.
|
||||
// Client code should never manually set/get it. This member might be removed in the future.
|
||||
bool readonly = false;
|
||||
|
||||
private:
|
||||
ConnectionOptions _parse_uri(const std::string &uri) const;
|
||||
|
||||
auto _split_uri(const std::string &uri) const
|
||||
-> std::tuple<std::string, std::string, std::string>;
|
||||
|
||||
auto _split_path(const std::string &path) const
|
||||
-> std::tuple<std::string, int, std::string>;
|
||||
|
||||
void _parse_parameters(const std::string ¶meter_string,
|
||||
ConnectionOptions &opts) const;
|
||||
|
||||
void _set_option(const std::string &key, const std::string &val, ConnectionOptions &opts) const;
|
||||
|
||||
bool _parse_bool_option(const std::string &str) const;
|
||||
|
||||
std::chrono::milliseconds _parse_timeout_option(const std::string &str) const;
|
||||
|
||||
std::vector<std::string> _split(const std::string &str, const std::string &delimiter) const;
|
||||
|
||||
void _set_auth_opts(const std::string &auth, ConnectionOptions &opts) const;
|
||||
|
||||
void _set_tcp_opts(const std::string &path, ConnectionOptions &opts) const;
|
||||
|
||||
void _set_unix_opts(const std::string &path, ConnectionOptions &opts) const;
|
||||
};
|
||||
|
||||
class CmdArgs;
|
||||
|
||||
class Connection {
|
||||
public:
|
||||
explicit Connection(const ConnectionOptions &opts);
|
||||
|
||||
Connection(const Connection &) = delete;
|
||||
Connection& operator=(const Connection &) = delete;
|
||||
|
||||
Connection(Connection &&) = default;
|
||||
Connection& operator=(Connection &&) = default;
|
||||
|
||||
~Connection() = default;
|
||||
|
||||
// Check if the connection is broken. Client needs to do this check
|
||||
// before sending some command to the connection. If it's broken,
|
||||
// client needs to reconnect it.
|
||||
bool broken() const noexcept {
|
||||
return _ctx->err != REDIS_OK;
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
_ctx->err = 0;
|
||||
}
|
||||
|
||||
void invalidate() noexcept {
|
||||
_ctx->err = REDIS_ERR;
|
||||
}
|
||||
|
||||
void reconnect();
|
||||
|
||||
auto create_time() const
|
||||
-> std::chrono::time_point<std::chrono::steady_clock> {
|
||||
return _create_time;
|
||||
}
|
||||
|
||||
auto last_active() const
|
||||
-> std::chrono::time_point<std::chrono::steady_clock> {
|
||||
return _last_active;
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
void send(const char *format, Args &&...args);
|
||||
|
||||
void send(int argc, const char **argv, const std::size_t *argv_len);
|
||||
|
||||
void send(CmdArgs &args);
|
||||
|
||||
ReplyUPtr recv(bool handle_error_reply = true);
|
||||
|
||||
const ConnectionOptions& options() const {
|
||||
return _opts;
|
||||
}
|
||||
|
||||
friend void swap(Connection &lhs, Connection &rhs) noexcept;
|
||||
|
||||
private:
|
||||
class Connector;
|
||||
|
||||
struct ContextDeleter {
|
||||
void operator()(redisContext *context) const {
|
||||
if (context != nullptr) {
|
||||
redisFree(context);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
using ContextUPtr = std::unique_ptr<redisContext, ContextDeleter>;
|
||||
|
||||
void _set_options();
|
||||
|
||||
void _auth();
|
||||
|
||||
void _select_db();
|
||||
|
||||
void _enable_readonly();
|
||||
|
||||
redisContext* _context();
|
||||
|
||||
ContextUPtr _ctx;
|
||||
|
||||
// The time that the connection is created.
|
||||
std::chrono::time_point<std::chrono::steady_clock> _create_time{};
|
||||
|
||||
// The time that the connection is created or the time that
|
||||
// the connection is recently used, i.e. `_context()` is called.
|
||||
std::chrono::time_point<std::chrono::steady_clock> _last_active{};
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
// TODO: define _tls_ctx before _ctx
|
||||
tls::TlsContextUPtr _tls_ctx;
|
||||
};
|
||||
|
||||
using ConnectionSPtr = std::shared_ptr<Connection>;
|
||||
|
||||
enum class Role {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// Inline implementaions.
|
||||
|
||||
template <typename ...Args>
|
||||
inline void Connection::send(const char *format, Args &&...args) {
|
||||
auto ctx = _context();
|
||||
|
||||
assert(ctx != nullptr);
|
||||
|
||||
if (redisAppendCommand(ctx,
|
||||
format,
|
||||
std::forward<Args>(args)...) != REDIS_OK) {
|
||||
throw_error(*ctx, "Failed to send command");
|
||||
}
|
||||
|
||||
assert(!broken());
|
||||
}
|
||||
|
||||
inline redisContext* Connection::_context() {
|
||||
_last_active = std::chrono::steady_clock::now();
|
||||
|
||||
return _ctx.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H
|
||||
@@ -0,0 +1,276 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "connection_pool.h"
|
||||
#include <cassert>
|
||||
#include "errors.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
ConnectionPool::ConnectionPool(const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts) :
|
||||
_opts(connection_opts),
|
||||
_pool_opts(pool_opts) {
|
||||
if (_pool_opts.size == 0) {
|
||||
throw Error("CANNOT create an empty pool");
|
||||
}
|
||||
|
||||
// Lazily create connections.
|
||||
}
|
||||
|
||||
ConnectionPool::ConnectionPool(SimpleSentinel sentinel,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts) :
|
||||
_opts(connection_opts),
|
||||
_pool_opts(pool_opts),
|
||||
_sentinel(std::move(sentinel)) {
|
||||
// In this case, the connection must be of TCP type.
|
||||
if (_opts.type != ConnectionType::TCP) {
|
||||
throw Error("Sentinel only supports TCP connection");
|
||||
}
|
||||
|
||||
if (_opts.connect_timeout == std::chrono::milliseconds(0)
|
||||
|| _opts.socket_timeout == std::chrono::milliseconds(0)) {
|
||||
throw Error("With sentinel, connection timeout and socket timeout cannot be 0");
|
||||
}
|
||||
|
||||
// Cleanup connection options.
|
||||
_update_connection_opts("", -1);
|
||||
|
||||
assert(_sentinel);
|
||||
}
|
||||
|
||||
ConnectionPool::ConnectionPool(ConnectionPool &&that) {
|
||||
std::lock_guard<std::mutex> lock(that._mutex);
|
||||
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
ConnectionPool& ConnectionPool::operator=(ConnectionPool &&that) {
|
||||
if (this != &that) {
|
||||
std::lock(_mutex, that._mutex);
|
||||
std::lock_guard<std::mutex> lock_this(_mutex, std::adopt_lock);
|
||||
std::lock_guard<std::mutex> lock_that(that._mutex, std::adopt_lock);
|
||||
|
||||
_move(std::move(that));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Connection ConnectionPool::fetch() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
if (_pool.empty()) {
|
||||
if (_used_connections == _pool_opts.size) {
|
||||
_wait_for_connection(lock);
|
||||
} else {
|
||||
// Lazily create a new connection.
|
||||
auto connection = _create();
|
||||
|
||||
++_used_connections;
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
// _pool is NOT empty.
|
||||
auto connection = _fetch();
|
||||
|
||||
auto connection_lifetime = _pool_opts.connection_lifetime;
|
||||
auto connection_idle_time = _pool_opts.connection_idle_time;
|
||||
|
||||
if (_sentinel) {
|
||||
auto opts = _opts;
|
||||
auto role_changed = _role_changed(connection.options());
|
||||
auto sentinel = _sentinel;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (role_changed || _need_reconnect(connection, connection_lifetime, connection_idle_time)) {
|
||||
try {
|
||||
connection = _create(sentinel, opts, false);
|
||||
} catch (const Error &e) {
|
||||
// Failed to reconnect, return it to the pool, and retry latter.
|
||||
release(std::move(connection));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (_need_reconnect(connection, connection_lifetime, connection_idle_time)) {
|
||||
try {
|
||||
connection.reconnect();
|
||||
} catch (const Error &e) {
|
||||
// Failed to reconnect, return it to the pool, and retry latter.
|
||||
release(std::move(connection));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
ConnectionOptions ConnectionPool::connection_options() {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
return _opts;
|
||||
}
|
||||
|
||||
void ConnectionPool::release(Connection connection) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
_pool.push_back(std::move(connection));
|
||||
}
|
||||
|
||||
_cv.notify_one();
|
||||
}
|
||||
|
||||
Connection ConnectionPool::create() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
auto opts = _opts;
|
||||
|
||||
if (_sentinel) {
|
||||
auto sentinel = _sentinel;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
return _create(sentinel, opts, false);
|
||||
} else {
|
||||
lock.unlock();
|
||||
|
||||
return Connection(opts);
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionPool ConnectionPool::clone() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
auto opts = _opts;
|
||||
auto pool_opts = _pool_opts;
|
||||
|
||||
if (_sentinel) {
|
||||
auto sentinel = _sentinel;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
return ConnectionPool(sentinel, pool_opts, opts);
|
||||
} else {
|
||||
lock.unlock();
|
||||
|
||||
return ConnectionPool(pool_opts, opts);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionPool::_move(ConnectionPool &&that) {
|
||||
_opts = std::move(that._opts);
|
||||
_pool_opts = std::move(that._pool_opts);
|
||||
_pool = std::move(that._pool);
|
||||
_used_connections = that._used_connections;
|
||||
_sentinel = std::move(that._sentinel);
|
||||
}
|
||||
|
||||
Connection ConnectionPool::_create() {
|
||||
if (_sentinel) {
|
||||
// Get Redis host and port info from sentinel.
|
||||
return _create(_sentinel, _opts, true);
|
||||
}
|
||||
|
||||
return Connection(_opts);
|
||||
}
|
||||
|
||||
Connection ConnectionPool::_create(SimpleSentinel &sentinel,
|
||||
const ConnectionOptions &opts,
|
||||
bool locked) {
|
||||
try {
|
||||
auto connection = sentinel.create(opts);
|
||||
|
||||
std::unique_lock<std::mutex> lock(_mutex, std::defer_lock);
|
||||
if (!locked) {
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
const auto &connection_opts = connection.options();
|
||||
if (_role_changed(connection_opts)) {
|
||||
// Master/Slave has been changed, reconnect all connections.
|
||||
_update_connection_opts(connection_opts.host, connection_opts.port);
|
||||
}
|
||||
|
||||
return connection;
|
||||
} catch (const StopIterError &e) {
|
||||
throw Error("Failed to create connection with sentinel");
|
||||
}
|
||||
}
|
||||
|
||||
Connection ConnectionPool::_fetch() {
|
||||
assert(!_pool.empty());
|
||||
|
||||
auto connection = std::move(_pool.front());
|
||||
_pool.pop_front();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
void ConnectionPool::_wait_for_connection(std::unique_lock<std::mutex> &lock) {
|
||||
auto timeout = _pool_opts.wait_timeout;
|
||||
if (timeout > std::chrono::milliseconds(0)) {
|
||||
// Wait until _pool is no longer empty or timeout.
|
||||
if (!_cv.wait_for(lock,
|
||||
timeout,
|
||||
[this] { return !(this->_pool).empty(); })) {
|
||||
throw Error("Failed to fetch a connection in "
|
||||
+ std::to_string(timeout.count()) + " milliseconds");
|
||||
}
|
||||
} else {
|
||||
// Wait forever.
|
||||
_cv.wait(lock, [this] { return !(this->_pool).empty(); });
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionPool::_need_reconnect(const Connection &connection,
|
||||
const std::chrono::milliseconds &connection_lifetime,
|
||||
const std::chrono::milliseconds &connection_idle_time) const {
|
||||
if (connection.broken()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (connection_lifetime > std::chrono::milliseconds(0)) {
|
||||
if (now - connection.create_time() > connection_lifetime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (connection_idle_time > std::chrono::milliseconds(0)) {
|
||||
if (now - connection.last_active() > connection_idle_time) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include "connection.h"
|
||||
#include "sentinel.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
struct ConnectionPoolOptions {
|
||||
// Max number of connections, including both in-use and idle ones.
|
||||
std::size_t size = 1;
|
||||
|
||||
// Max time to wait for a connection. 0ms means client waits forever.
|
||||
std::chrono::milliseconds wait_timeout{0};
|
||||
|
||||
// Max lifetime of a connection. 0ms means we never expire the connection.
|
||||
std::chrono::milliseconds connection_lifetime{0};
|
||||
|
||||
// Max idle time of a connection. 0ms means we never expire the connection.
|
||||
std::chrono::milliseconds connection_idle_time{0};
|
||||
};
|
||||
|
||||
class ConnectionPool {
|
||||
public:
|
||||
ConnectionPool(const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
ConnectionPool(SimpleSentinel sentinel,
|
||||
const ConnectionPoolOptions &pool_opts,
|
||||
const ConnectionOptions &connection_opts);
|
||||
|
||||
ConnectionPool() = default;
|
||||
|
||||
ConnectionPool(ConnectionPool &&that);
|
||||
ConnectionPool& operator=(ConnectionPool &&that);
|
||||
|
||||
ConnectionPool(const ConnectionPool &) = delete;
|
||||
ConnectionPool& operator=(const ConnectionPool &) = delete;
|
||||
|
||||
~ConnectionPool() = default;
|
||||
|
||||
// Fetch a connection from pool.
|
||||
Connection fetch();
|
||||
|
||||
ConnectionOptions connection_options();
|
||||
|
||||
void release(Connection connection);
|
||||
|
||||
// Create a new connection.
|
||||
Connection create();
|
||||
|
||||
ConnectionPool clone();
|
||||
|
||||
private:
|
||||
void _move(ConnectionPool &&that);
|
||||
|
||||
// NOT thread-safe
|
||||
Connection _create();
|
||||
|
||||
Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked);
|
||||
|
||||
Connection _fetch();
|
||||
|
||||
void _wait_for_connection(std::unique_lock<std::mutex> &lock);
|
||||
|
||||
bool _need_reconnect(const Connection &connection,
|
||||
const std::chrono::milliseconds &connection_lifetime,
|
||||
const std::chrono::milliseconds &connection_idle_time) const;
|
||||
|
||||
void _update_connection_opts(const std::string &host, int port) {
|
||||
_opts.host = host;
|
||||
_opts.port = port;
|
||||
}
|
||||
|
||||
bool _role_changed(const ConnectionOptions &opts) const {
|
||||
return opts.port != _opts.port || opts.host != _opts.host;
|
||||
}
|
||||
|
||||
ConnectionOptions _opts;
|
||||
|
||||
ConnectionPoolOptions _pool_opts;
|
||||
|
||||
std::deque<Connection> _pool;
|
||||
|
||||
std::size_t _used_connections = 0;
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
std::condition_variable _cv;
|
||||
|
||||
SimpleSentinel _sentinel;
|
||||
};
|
||||
|
||||
using ConnectionPoolSPtr = std::shared_ptr<ConnectionPool>;
|
||||
|
||||
class SafeConnection {
|
||||
public:
|
||||
explicit SafeConnection(ConnectionPool &pool) : _pool(pool), _connection(_pool.fetch()) {
|
||||
assert(!_connection.broken());
|
||||
}
|
||||
|
||||
SafeConnection(const SafeConnection &) = delete;
|
||||
SafeConnection& operator=(const SafeConnection &) = delete;
|
||||
|
||||
SafeConnection(SafeConnection &&) = delete;
|
||||
SafeConnection& operator=(SafeConnection &&) = delete;
|
||||
|
||||
~SafeConnection() {
|
||||
_pool.release(std::move(_connection));
|
||||
}
|
||||
|
||||
Connection& connection() {
|
||||
return _connection;
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionPool &_pool;
|
||||
Connection _connection;
|
||||
};
|
||||
|
||||
// NOTE: This class is similar to `SafeConnection`.
|
||||
// The difference is that `SafeConnection` tries to avoid copying a std::shared_ptr.
|
||||
class GuardedConnection {
|
||||
public:
|
||||
explicit GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool),
|
||||
_connection(_pool->fetch()) {
|
||||
assert(!_connection.broken());
|
||||
}
|
||||
|
||||
GuardedConnection(const GuardedConnection &) = delete;
|
||||
GuardedConnection& operator=(const GuardedConnection &) = delete;
|
||||
|
||||
GuardedConnection(GuardedConnection &&) = default;
|
||||
GuardedConnection& operator=(GuardedConnection &&) = default;
|
||||
|
||||
~GuardedConnection() {
|
||||
// If `GuardedConnection` has been moved, `_pool` will be nullptr.
|
||||
if (_pool) {
|
||||
_pool->release(std::move(_connection));
|
||||
}
|
||||
}
|
||||
|
||||
Connection& connection() {
|
||||
return _connection;
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionPoolSPtr _pool;
|
||||
Connection _connection;
|
||||
};
|
||||
|
||||
using GuardedConnectionSPtr = std::shared_ptr<GuardedConnection>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||
* Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style)
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the University of California, Berkeley nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* CRC16 implementation according to CCITT standards.
|
||||
*
|
||||
* Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
|
||||
* following parameters:
|
||||
*
|
||||
* Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
|
||||
* Width : 16 bit
|
||||
* Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
|
||||
* Initialization : 0000
|
||||
* Reflect Input byte : False
|
||||
* Reflect Output CRC : False
|
||||
* Xor constant to output CRC : 0000
|
||||
* Output for "123456789" : 31C3
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
static const uint16_t crc16tab[256]= {
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
|
||||
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
|
||||
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
|
||||
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
|
||||
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
|
||||
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
|
||||
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
|
||||
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
|
||||
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
|
||||
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
|
||||
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
|
||||
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
|
||||
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
|
||||
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
|
||||
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
|
||||
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
|
||||
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
|
||||
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
|
||||
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
|
||||
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
|
||||
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
|
||||
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
|
||||
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
|
||||
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
|
||||
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
|
||||
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
|
||||
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
|
||||
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
|
||||
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
|
||||
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
|
||||
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
|
||||
};
|
||||
|
||||
uint16_t crc16(const char *buf, int len) {
|
||||
int counter;
|
||||
uint16_t crc = 0;
|
||||
for (counter = 0; counter < len; counter++)
|
||||
crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
|
||||
return crc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
// By now, not all compilers support std::string_view and std::optional,
|
||||
// so we make our own implementation.
|
||||
|
||||
class StringView {
|
||||
public:
|
||||
constexpr StringView() noexcept = default;
|
||||
|
||||
constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {}
|
||||
|
||||
StringView(const char *data) : _data(data), _size(std::strlen(data)) {}
|
||||
|
||||
StringView(const std::string &str) : _data(str.data()), _size(str.size()) {}
|
||||
|
||||
constexpr StringView(const StringView &) noexcept = default;
|
||||
|
||||
StringView& operator=(const StringView &) noexcept = default;
|
||||
|
||||
constexpr const char* data() const noexcept {
|
||||
return _data;
|
||||
}
|
||||
|
||||
constexpr std::size_t size() const noexcept {
|
||||
return _size;
|
||||
}
|
||||
|
||||
private:
|
||||
const char *_data = nullptr;
|
||||
std::size_t _size = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Optional {
|
||||
public:
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1910)
|
||||
Optional() : _value() {} // MSVC 2015 bug
|
||||
#else
|
||||
Optional() = default;
|
||||
#endif
|
||||
|
||||
Optional(const Optional &) = default;
|
||||
Optional& operator=(const Optional &) = default;
|
||||
|
||||
Optional(Optional &&) = default;
|
||||
Optional& operator=(Optional &&) = default;
|
||||
|
||||
~Optional() = default;
|
||||
|
||||
template <typename ...Args>
|
||||
explicit Optional(Args &&...args) : _value(true, T(std::forward<Args>(args)...)) {}
|
||||
|
||||
explicit operator bool() const {
|
||||
return _value.first;
|
||||
}
|
||||
|
||||
T& value() {
|
||||
return _value.second;
|
||||
}
|
||||
|
||||
const T& value() const {
|
||||
return _value.second;
|
||||
}
|
||||
|
||||
T* operator->() {
|
||||
return &(_value.second);
|
||||
}
|
||||
|
||||
const T* operator->() const {
|
||||
return &(_value.second);
|
||||
}
|
||||
|
||||
T& operator*() {
|
||||
return _value.second;
|
||||
}
|
||||
|
||||
const T& operator*() const {
|
||||
return _value.second;
|
||||
}
|
||||
|
||||
private:
|
||||
std::pair<bool, T> _value;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
@@ -0,0 +1,46 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#define REDIS_PLUS_PLUS_HAS_OPTIONAL
|
||||
|
||||
#define REDIS_PLUS_PLUS_HAS_VARIANT
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
using StringView = std::string_view;
|
||||
|
||||
template <typename T>
|
||||
using Optional = std::optional<T>;
|
||||
|
||||
template <typename ...Args>
|
||||
using Variant = std::variant<Args...>;
|
||||
|
||||
using Monostate = std::monostate;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_CXX_UTILS_H
|
||||
@@ -0,0 +1,142 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "errors.h"
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
#include "shards.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace sw::redis;
|
||||
|
||||
std::pair<ReplyErrorType, std::string> parse_error(const std::string &msg);
|
||||
|
||||
std::unordered_map<std::string, ReplyErrorType> error_map = {
|
||||
{"MOVED", ReplyErrorType::MOVED},
|
||||
{"ASK", ReplyErrorType::ASK}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
void throw_error(const redisContext &context, const std::string &err_info) {
|
||||
auto err_code = context.err;
|
||||
const auto *err_str = context.errstr;
|
||||
if (err_str == nullptr) {
|
||||
throw Error(err_info + ": null error message: " + std::to_string(err_code));
|
||||
}
|
||||
|
||||
auto err_msg = err_info + ": " + err_str;
|
||||
|
||||
switch (err_code) {
|
||||
case REDIS_ERR_IO:
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ETIMEDOUT) {
|
||||
throw TimeoutError(err_msg);
|
||||
} else {
|
||||
throw IoError(err_msg);
|
||||
}
|
||||
break;
|
||||
|
||||
case REDIS_ERR_EOF:
|
||||
throw ClosedError(err_msg);
|
||||
break;
|
||||
|
||||
case REDIS_ERR_PROTOCOL:
|
||||
throw ProtoError(err_msg);
|
||||
break;
|
||||
|
||||
case REDIS_ERR_OOM:
|
||||
throw OomError(err_msg);
|
||||
break;
|
||||
|
||||
case REDIS_ERR_OTHER:
|
||||
throw Error(err_msg);
|
||||
break;
|
||||
|
||||
#ifdef REDIS_ERR_TIMEOUT
|
||||
case REDIS_ERR_TIMEOUT:
|
||||
throw TimeoutError(err_msg);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
throw Error("unknown error code: " + err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
void throw_error(const redisReply &reply) {
|
||||
assert(reply.type == REDIS_REPLY_ERROR);
|
||||
|
||||
if (reply.str == nullptr) {
|
||||
throw Error("Null error reply");
|
||||
}
|
||||
|
||||
auto err_str = std::string(reply.str, reply.len);
|
||||
|
||||
auto err_type = ReplyErrorType::ERR;
|
||||
std::string err_msg;
|
||||
std::tie(err_type, err_msg) = parse_error(err_str);
|
||||
|
||||
switch (err_type) {
|
||||
case ReplyErrorType::MOVED:
|
||||
throw MovedError(err_msg);
|
||||
break;
|
||||
|
||||
case ReplyErrorType::ASK:
|
||||
throw AskError(err_msg);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ReplyError(err_str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace sw::redis;
|
||||
|
||||
std::pair<ReplyErrorType, std::string> parse_error(const std::string &err) {
|
||||
// The error contains an Error Prefix, and an optional error message.
|
||||
auto idx = err.find_first_of(" \n");
|
||||
|
||||
if (idx == std::string::npos) {
|
||||
throw ProtoError("No Error Prefix: " + err);
|
||||
}
|
||||
|
||||
auto err_prefix = err.substr(0, idx);
|
||||
auto err_type = ReplyErrorType::ERR;
|
||||
|
||||
auto iter = error_map.find(err_prefix);
|
||||
if (iter != error_map.end()) {
|
||||
// Specific error.
|
||||
err_type = iter->second;
|
||||
} // else Generic error.
|
||||
|
||||
return {err_type, err.substr(idx + 1)};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2017 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <hiredis/hiredis.h>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
enum ReplyErrorType {
|
||||
ERR,
|
||||
MOVED,
|
||||
ASK
|
||||
};
|
||||
|
||||
class Error : public std::exception {
|
||||
public:
|
||||
explicit Error(const std::string &msg) : _msg(msg) {}
|
||||
|
||||
Error(const Error &) = default;
|
||||
Error& operator=(const Error &) = default;
|
||||
|
||||
Error(Error &&) = default;
|
||||
Error& operator=(Error &&) = default;
|
||||
|
||||
virtual ~Error() override = default;
|
||||
|
||||
virtual const char* what() const noexcept override {
|
||||
return _msg.data();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _msg;
|
||||
};
|
||||
|
||||
class IoError : public Error {
|
||||
public:
|
||||
explicit IoError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
IoError(const IoError &) = default;
|
||||
IoError& operator=(const IoError &) = default;
|
||||
|
||||
IoError(IoError &&) = default;
|
||||
IoError& operator=(IoError &&) = default;
|
||||
|
||||
virtual ~IoError() override = default;
|
||||
};
|
||||
|
||||
class TimeoutError : public IoError {
|
||||
public:
|
||||
explicit TimeoutError(const std::string &msg) : IoError(msg) {}
|
||||
|
||||
TimeoutError(const TimeoutError &) = default;
|
||||
TimeoutError& operator=(const TimeoutError &) = default;
|
||||
|
||||
TimeoutError(TimeoutError &&) = default;
|
||||
TimeoutError& operator=(TimeoutError &&) = default;
|
||||
|
||||
virtual ~TimeoutError() override = default;
|
||||
};
|
||||
|
||||
class ClosedError : public Error {
|
||||
public:
|
||||
explicit ClosedError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ClosedError(const ClosedError &) = default;
|
||||
ClosedError& operator=(const ClosedError &) = default;
|
||||
|
||||
ClosedError(ClosedError &&) = default;
|
||||
ClosedError& operator=(ClosedError &&) = default;
|
||||
|
||||
virtual ~ClosedError() override = default;
|
||||
};
|
||||
|
||||
class ProtoError : public Error {
|
||||
public:
|
||||
explicit ProtoError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ProtoError(const ProtoError &) = default;
|
||||
ProtoError& operator=(const ProtoError &) = default;
|
||||
|
||||
ProtoError(ProtoError &&) = default;
|
||||
ProtoError& operator=(ProtoError &&) = default;
|
||||
|
||||
virtual ~ProtoError() override = default;
|
||||
};
|
||||
|
||||
class OomError : public Error {
|
||||
public:
|
||||
explicit OomError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
OomError(const OomError &) = default;
|
||||
OomError& operator=(const OomError &) = default;
|
||||
|
||||
OomError(OomError &&) = default;
|
||||
OomError& operator=(OomError &&) = default;
|
||||
|
||||
virtual ~OomError() override = default;
|
||||
};
|
||||
|
||||
class ReplyError : public Error {
|
||||
public:
|
||||
explicit ReplyError(const std::string &msg) : Error(msg) {}
|
||||
|
||||
ReplyError(const ReplyError &) = default;
|
||||
ReplyError& operator=(const ReplyError &) = default;
|
||||
|
||||
ReplyError(ReplyError &&) = default;
|
||||
ReplyError& operator=(ReplyError &&) = default;
|
||||
|
||||
virtual ~ReplyError() override = default;
|
||||
};
|
||||
|
||||
class WatchError : public Error {
|
||||
public:
|
||||
explicit WatchError() : Error("Watched key has been modified") {}
|
||||
|
||||
WatchError(const WatchError &) = default;
|
||||
WatchError& operator=(const WatchError &) = default;
|
||||
|
||||
WatchError(WatchError &&) = default;
|
||||
WatchError& operator=(WatchError &&) = default;
|
||||
|
||||
virtual ~WatchError() override = default;
|
||||
};
|
||||
|
||||
|
||||
// MovedError and AskError are defined in shards.h
|
||||
class MovedError;
|
||||
|
||||
class AskError;
|
||||
|
||||
void throw_error(const redisContext &context, const std::string &err_info);
|
||||
|
||||
void throw_error(const redisReply &reply);
|
||||
|
||||
template <typename Input>
|
||||
inline void range_check(const char *cmd, Input first, Input last) {
|
||||
if (first == last) {
|
||||
throw Error(std::string(cmd) + ": no key specified");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H
|
||||
@@ -0,0 +1,273 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#include "event_loop.h"
|
||||
#include <cassert>
|
||||
#include <hiredis/adapters/libuv.h>
|
||||
#include "async_connection.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
EventLoop::EventLoop() {
|
||||
_loop = _create_event_loop();
|
||||
|
||||
_event_async = _create_uv_async(_event_callback);
|
||||
_stop_async = _create_uv_async(_stop_callback);
|
||||
|
||||
_loop_thread = std::thread([this]() { uv_run(this->_loop.get(), UV_RUN_DEFAULT); });
|
||||
}
|
||||
|
||||
EventLoop::~EventLoop() {
|
||||
_stop();
|
||||
|
||||
if (_loop_thread.joinable()) {
|
||||
_loop_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoop::unwatch(AsyncConnectionSPtr connection, std::exception_ptr err) {
|
||||
assert(connection);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
_disconnect_events.emplace(std::move(connection), err);
|
||||
}
|
||||
|
||||
_notify();
|
||||
}
|
||||
|
||||
void EventLoop::add(AsyncConnectionSPtr event) {
|
||||
assert(event);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
_command_events.insert(std::move(event));
|
||||
}
|
||||
|
||||
_notify();
|
||||
}
|
||||
|
||||
void EventLoop::watch(redisAsyncContext &ctx) {
|
||||
if (redisLibuvAttach(&ctx, _loop.get()) != REDIS_OK) {
|
||||
throw Error("failed to attach to event loop");
|
||||
}
|
||||
|
||||
redisAsyncSetConnectCallback(&ctx, EventLoop::_connect_callback);
|
||||
redisAsyncSetDisconnectCallback(&ctx, EventLoop::_disconnect_callback);
|
||||
}
|
||||
|
||||
void EventLoop::_connect_callback(const redisAsyncContext *ctx, int status) {
|
||||
assert(ctx != nullptr);
|
||||
|
||||
auto *context = static_cast<AsyncContext *>(ctx->data);
|
||||
assert(context != nullptr);
|
||||
|
||||
auto &connection = context->connection;
|
||||
std::exception_ptr err;
|
||||
if (status != REDIS_OK) {
|
||||
try {
|
||||
throw_error(ctx->c, "failed to connect to server");
|
||||
} catch (const Error &e) {
|
||||
err = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
connection->connect_callback(err);
|
||||
}
|
||||
|
||||
void EventLoop::_disconnect_callback(const redisAsyncContext *ctx, int status) {
|
||||
assert(ctx != nullptr);
|
||||
|
||||
auto *context = static_cast<AsyncContext *>(ctx->data);
|
||||
assert(context != nullptr);
|
||||
|
||||
if (!context->run_disconnect_callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &connection = context->connection;
|
||||
std::exception_ptr err;
|
||||
if (status != REDIS_OK) {
|
||||
try {
|
||||
throw_error(ctx->c, "failed to disconnect from server");
|
||||
} catch (const Error &e) {
|
||||
err = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: if status == REDIS_OK, should we call the callback?
|
||||
connection->disconnect_callback(err);
|
||||
}
|
||||
|
||||
void EventLoop::_event_callback(uv_async_t *handle) {
|
||||
assert(handle != nullptr);
|
||||
|
||||
auto *event_loop = static_cast<EventLoop*>(handle->data);
|
||||
assert(event_loop != nullptr);
|
||||
|
||||
std::unordered_set<AsyncConnectionSPtr> command_events;
|
||||
std::unordered_map<AsyncConnectionSPtr, std::exception_ptr> disconnect_events;
|
||||
std::tie(command_events, disconnect_events) = event_loop->_get_events();
|
||||
|
||||
for (auto &connection : command_events) {
|
||||
assert(connection);
|
||||
|
||||
connection->event_callback();
|
||||
}
|
||||
|
||||
for (auto &ele : disconnect_events) {
|
||||
auto &connection = ele.first;
|
||||
auto &err = ele.second;
|
||||
|
||||
assert(connection);
|
||||
|
||||
if (!err) {
|
||||
// Ensure all pending events have been sent before disconnecting.
|
||||
connection->event_callback();
|
||||
}
|
||||
|
||||
// If `event_callback` fails, connection will be release by event loop,
|
||||
// and this `disconnect` call will do nothing.
|
||||
connection->disconnect(err);
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoop::_stop_callback(uv_async_t *handle) {
|
||||
assert(handle != nullptr);
|
||||
|
||||
auto *event_loop = static_cast<EventLoop*>(handle->data);
|
||||
assert(event_loop != nullptr);
|
||||
|
||||
std::unordered_set<AsyncConnectionSPtr> command_events;
|
||||
std::unordered_map<AsyncConnectionSPtr, std::exception_ptr> disconnect_events;
|
||||
std::tie(command_events, disconnect_events) = event_loop->_get_events();
|
||||
|
||||
event_loop->_clean_up(command_events, disconnect_events);
|
||||
|
||||
uv_stop(event_loop->_loop.get());
|
||||
}
|
||||
|
||||
void EventLoop::_clean_up(std::unordered_set<AsyncConnectionSPtr> &command_events,
|
||||
std::unordered_map<AsyncConnectionSPtr, std::exception_ptr> &disconnect_events) {
|
||||
auto err = std::make_exception_ptr(Error("event loop is closing"));
|
||||
for (auto &connection : command_events) {
|
||||
assert(connection);
|
||||
|
||||
connection->disconnect(err);
|
||||
}
|
||||
|
||||
for (auto &ele : disconnect_events) {
|
||||
auto &connection = ele.first;
|
||||
auto e = ele.second;
|
||||
if (!e) {
|
||||
e = err;
|
||||
}
|
||||
|
||||
assert(connection);
|
||||
|
||||
connection->disconnect(e);
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoop::LoopDeleter::operator()(uv_loop_t *loop) const {
|
||||
if (loop == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uv_walk(loop,
|
||||
[](uv_handle_t *handle, void *) {
|
||||
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);
|
||||
}
|
||||
},
|
||||
nullptr);
|
||||
|
||||
// Ensure uv_walk's callback to be called.
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
|
||||
uv_loop_close(loop);
|
||||
|
||||
delete loop;
|
||||
}
|
||||
|
||||
void EventLoop::_notify() {
|
||||
assert(_event_async);
|
||||
|
||||
uv_async_send(_event_async.get());
|
||||
}
|
||||
|
||||
void EventLoop::_stop() {
|
||||
assert(_stop_async);
|
||||
|
||||
uv_async_send(_stop_async.get());
|
||||
}
|
||||
|
||||
auto EventLoop::_get_events()
|
||||
-> std::pair<std::unordered_set<AsyncConnectionSPtr>,
|
||||
std::unordered_map<AsyncConnectionSPtr, std::exception_ptr>> {
|
||||
std::unordered_set<AsyncConnectionSPtr> command_events;
|
||||
std::unordered_map<AsyncConnectionSPtr, std::exception_ptr> disconnect_events;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
command_events.swap(_command_events);
|
||||
disconnect_events.swap(_disconnect_events);
|
||||
}
|
||||
|
||||
return std::make_pair(std::move(command_events), std::move(disconnect_events));
|
||||
}
|
||||
|
||||
EventLoop::UvAsyncUPtr EventLoop::_create_uv_async(AsyncCallback callback) {
|
||||
auto uv_async = std::unique_ptr<uv_async_t>(new uv_async_t);
|
||||
auto err = uv_async_init(_loop.get(), uv_async.get(), callback);
|
||||
if (err != 0) {
|
||||
throw Error("failed to initialize async: " + _err_msg(err));
|
||||
}
|
||||
|
||||
uv_async->data = this;
|
||||
|
||||
return uv_async;
|
||||
}
|
||||
|
||||
EventLoop::LoopUPtr EventLoop::_create_event_loop() const {
|
||||
auto *loop = new uv_loop_t;
|
||||
auto err = uv_loop_init(loop);
|
||||
if (err != 0) {
|
||||
delete loop;
|
||||
throw Error("failed to initialize event loop: " + _err_msg(err));
|
||||
}
|
||||
|
||||
return LoopUPtr(loop);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_EVENT_LOOP_H
|
||||
#define SEWENEW_REDISPLUSPLUS_EVENT_LOOP_H
|
||||
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <uv.h>
|
||||
#include "connection.h"
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
class AsyncConnection;
|
||||
class AsyncEvent;
|
||||
|
||||
class EventLoop {
|
||||
public:
|
||||
EventLoop();
|
||||
|
||||
EventLoop(const EventLoop &) = delete;
|
||||
EventLoop& operator=(const EventLoop &) = delete;
|
||||
|
||||
EventLoop(EventLoop &&that);
|
||||
|
||||
EventLoop& operator=(EventLoop &&that);
|
||||
|
||||
~EventLoop();
|
||||
|
||||
void unwatch(std::shared_ptr<AsyncConnection> connection, std::exception_ptr err = nullptr);
|
||||
|
||||
void add(std::shared_ptr<AsyncConnection> event);
|
||||
|
||||
// Not thread safe. Only call it in callback functions.
|
||||
void watch(redisAsyncContext &ctx);
|
||||
|
||||
private:
|
||||
static void _connect_callback(const redisAsyncContext *ctx, int status);
|
||||
|
||||
static void _disconnect_callback(const redisAsyncContext *ctx, int status);
|
||||
|
||||
static void _event_callback(uv_async_t *handle);
|
||||
|
||||
static void _stop_callback(uv_async_t *handle);
|
||||
|
||||
struct LoopDeleter {
|
||||
void operator()(uv_loop_t *loop) const;
|
||||
};
|
||||
|
||||
using LoopUPtr = std::unique_ptr<uv_loop_t, LoopDeleter>;
|
||||
|
||||
std::string _err_msg(int err) const {
|
||||
return uv_strerror(err);
|
||||
}
|
||||
|
||||
LoopUPtr _create_event_loop() const;
|
||||
|
||||
using UvAsyncUPtr = std::unique_ptr<uv_async_t>;
|
||||
|
||||
using AsyncCallback = void (*)(uv_async_t*);
|
||||
|
||||
UvAsyncUPtr _create_uv_async(AsyncCallback callback);
|
||||
|
||||
void _stop();
|
||||
|
||||
void _notify();
|
||||
|
||||
void _clean_up(std::unordered_set<std::shared_ptr<AsyncConnection>> &command_events,
|
||||
std::unordered_map<std::shared_ptr<AsyncConnection>, std::exception_ptr> &disconnect_events);
|
||||
|
||||
auto _get_events()
|
||||
-> std::pair<std::unordered_set<std::shared_ptr<AsyncConnection>>,
|
||||
std::unordered_map<std::shared_ptr<AsyncConnection>, std::exception_ptr>>;
|
||||
|
||||
// We must define _event_async and _stop_async before _loop,
|
||||
// because these memory can only be release after _loop's deleter
|
||||
// has been called, i.e. the deleter will close these handles.
|
||||
UvAsyncUPtr _event_async;
|
||||
|
||||
UvAsyncUPtr _stop_async;
|
||||
|
||||
std::thread _loop_thread;
|
||||
|
||||
std::mutex _mtx;
|
||||
|
||||
std::unordered_map<std::shared_ptr<AsyncConnection>, std::exception_ptr> _disconnect_events;
|
||||
|
||||
std::unordered_set<std::shared_ptr<AsyncConnection>> _command_events;
|
||||
|
||||
// _loop must be defined at last, since its destructor needs other data members.
|
||||
LoopUPtr _loop;
|
||||
};
|
||||
|
||||
using EventLoopSPtr = std::shared_ptr<EventLoop>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_EVENT_LOOP_H
|
||||
@@ -0,0 +1,39 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_UTILS_H
|
||||
|
||||
#define BOOST_THREAD_PROVIDES_FUTURE
|
||||
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
|
||||
|
||||
#include <boost/thread/future.hpp>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
template <typename T>
|
||||
using Future = boost::future<T>;
|
||||
|
||||
template <typename T>
|
||||
using Promise = boost::promise<T>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_UTILS_H
|
||||
@@ -0,0 +1,36 @@
|
||||
/**************************************************************************
|
||||
Copyright (c) 2021 sewenew
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*************************************************************************/
|
||||
|
||||
#ifndef SEWENEW_REDISPLUSPLUS_ASYNC_UTILS_H
|
||||
#define SEWENEW_REDISPLUSPLUS_ASYNC_UTILS_H
|
||||
|
||||
#include <future>
|
||||
|
||||
namespace sw {
|
||||
|
||||
namespace redis {
|
||||
|
||||
template <typename T>
|
||||
using Future = std::future<T>;
|
||||
|
||||
template <typename T>
|
||||
using Promise = std::promise<T>;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // end SEWENEW_REDISPLUSPLUS_ASYNC_UTILS_H
|
||||
Loaded 100 of 1791 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user