First Commit
This commit is contained in:
26
ext/redis-plus-plus-1.3.3/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
26
ext/redis-plus-plus-1.3.3/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -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.
|
||||
20
ext/redis-plus-plus-1.3.3/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
ext/redis-plus-plus-1.3.3/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -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.
|
||||
27
ext/redis-plus-plus-1.3.3/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
27
ext/redis-plus-plus-1.3.3/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -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.
|
||||
32
ext/redis-plus-plus-1.3.3/.gitignore
vendored
Normal file
32
ext/redis-plus-plus-1.3.3/.gitignore
vendored
Normal file
@@ -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
|
||||
32
ext/redis-plus-plus-1.3.3/.travis.yml
Normal file
32
ext/redis-plus-plus-1.3.3/.travis.yml
Normal file
@@ -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
|
||||
307
ext/redis-plus-plus-1.3.3/CMakeLists.txt
Normal file
307
ext/redis-plus-plus-1.3.3/CMakeLists.txt
Normal file
@@ -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)
|
||||
201
ext/redis-plus-plus-1.3.3/LICENSE
Normal file
201
ext/redis-plus-plus-1.3.3/LICENSE
Normal file
@@ -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.
|
||||
2604
ext/redis-plus-plus-1.3.3/README.md
Normal file
2604
ext/redis-plus-plus-1.3.3/README.md
Normal file
File diff suppressed because it is too large
Load Diff
12
ext/redis-plus-plus-1.3.3/cmake/redis++-config.cmake.in
Normal file
12
ext/redis-plus-plus-1.3.3/cmake/redis++-config.cmake.in
Normal file
@@ -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++)
|
||||
12
ext/redis-plus-plus-1.3.3/cmake/redis++.pc.in
Normal file
12
ext/redis-plus-plus-1.3.3/cmake/redis++.pc.in
Normal file
@@ -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++
|
||||
@@ -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
@@ -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,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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
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++
|
||||
@@ -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()
|
||||
@@ -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++)
|
||||
@@ -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)
|
||||
@@ -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,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
@@ -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,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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
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++
|
||||
@@ -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()
|
||||
@@ -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++)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
448
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_connection.cpp
Normal file
448
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_connection.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
498
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_connection.h
Normal file
498
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_connection.h
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
181
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_connection_pool.h
Normal file
181
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_connection_pool.h
Normal file
@@ -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
|
||||
23
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis++.h
Normal file
23
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis++.h
Normal file
@@ -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
|
||||
52
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis.cpp
Normal file
52
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
789
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis.h
Normal file
789
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis.h
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
819
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis_cluster.h
Normal file
819
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_redis_cluster.h
Normal file
@@ -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
|
||||
110
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_sentinel.cpp
Normal file
110
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_sentinel.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
89
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_sentinel.h
Normal file
89
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_sentinel.h
Normal file
@@ -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
|
||||
374
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_shards_pool.cpp
Normal file
374
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_shards_pool.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
127
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_shards_pool.h
Normal file
127
ext/redis-plus-plus-1.3.3/src/sw/redis++/async_shards_pool.h
Normal file
@@ -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
|
||||
775
ext/redis-plus-plus-1.3.3/src/sw/redis++/cmd_formatter.h
Normal file
775
ext/redis-plus-plus-1.3.3/src/sw/redis++/cmd_formatter.h
Normal file
@@ -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
|
||||
376
ext/redis-plus-plus-1.3.3/src/sw/redis++/command.cpp
Normal file
376
ext/redis-plus-plus-1.3.3/src/sw/redis++/command.cpp
Normal file
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
2271
ext/redis-plus-plus-1.3.3/src/sw/redis++/command.h
Normal file
2271
ext/redis-plus-plus-1.3.3/src/sw/redis++/command.h
Normal file
File diff suppressed because it is too large
Load Diff
180
ext/redis-plus-plus-1.3.3/src/sw/redis++/command_args.h
Normal file
180
ext/redis-plus-plus-1.3.3/src/sw/redis++/command_args.h
Normal file
@@ -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
|
||||
201
ext/redis-plus-plus-1.3.3/src/sw/redis++/command_options.cpp
Normal file
201
ext/redis-plus-plus-1.3.3/src/sw/redis++/command_options.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
211
ext/redis-plus-plus-1.3.3/src/sw/redis++/command_options.h
Normal file
211
ext/redis-plus-plus-1.3.3/src/sw/redis++/command_options.h
Normal file
@@ -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
|
||||
494
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection.cpp
Normal file
494
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
237
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection.h
Normal file
237
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection.h
Normal file
@@ -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
|
||||
276
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection_pool.cpp
Normal file
276
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection_pool.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
182
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection_pool.h
Normal file
182
ext/redis-plus-plus-1.3.3/src/sw/redis++/connection_pool.h
Normal file
@@ -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
|
||||
96
ext/redis-plus-plus-1.3.3/src/sw/redis++/crc16.cpp
Normal file
96
ext/redis-plus-plus-1.3.3/src/sw/redis++/crc16.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
113
ext/redis-plus-plus-1.3.3/src/sw/redis++/cxx11/cxx_utils.h
Normal file
113
ext/redis-plus-plus-1.3.3/src/sw/redis++/cxx11/cxx_utils.h
Normal file
@@ -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
|
||||
46
ext/redis-plus-plus-1.3.3/src/sw/redis++/cxx17/cxx_utils.h
Normal file
46
ext/redis-plus-plus-1.3.3/src/sw/redis++/cxx17/cxx_utils.h
Normal file
@@ -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
|
||||
142
ext/redis-plus-plus-1.3.3/src/sw/redis++/errors.cpp
Normal file
142
ext/redis-plus-plus-1.3.3/src/sw/redis++/errors.cpp
Normal file
@@ -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)};
|
||||
}
|
||||
|
||||
}
|
||||
166
ext/redis-plus-plus-1.3.3/src/sw/redis++/errors.h
Normal file
166
ext/redis-plus-plus-1.3.3/src/sw/redis++/errors.h
Normal file
@@ -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
|
||||
273
ext/redis-plus-plus-1.3.3/src/sw/redis++/event_loop.cpp
Normal file
273
ext/redis-plus-plus-1.3.3/src/sw/redis++/event_loop.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
119
ext/redis-plus-plus-1.3.3/src/sw/redis++/event_loop.h
Normal file
119
ext/redis-plus-plus-1.3.3/src/sw/redis++/event_loop.h
Normal file
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user