redis-cpp is a header-only library in C++17 for Redis (and C++11 backport)

Overview

redis-cpp - lightweight C++ client library for Redis

redis-cpp is a C++17 library for executing Redis commands with support for pipelines and the publish / subscribe pattern. Moreover, you can extend the library with your own stream implementation to communicate with Redis. You can also use it like a RESP serializer (pure core). You need only know a couple of functions to start working with Redis.

// Connect to server
auto stream = rediscpp::make_stream("localhost", "6379");
// Execute command
std::cout << rediscpp::execute(*stream, "ping").as<std::string>() << std::endl;

And you may dive deeper if you feel the need.

NOTE
If you need a C++11 version you could switch to c++11 branch and use that one.

Version

1.0.0

Features

  • easy way to access Redis
  • pipelines
  • publish / subscribe
  • pure core in C++ for the RESP
  • extensible transport
  • header-only library if it's necessary
  • minimal dependencies
  • various levels of usage

License

Distributed under the MIT License

Compiler and OS

This has compiled and tested within gcc 9.3 and clang 10.0 on Ubuntu 20.04.
You might try other compiler or OS.

NOTE
All code is a cross-platform.

Dependencies

  • Boost (only for using with built-in implementation of transport).

Build and install

Build library

git clone https://github.com/tdv/redis-cpp.git  
cd redis-cpp
mkdir build  
cd build  
cmake ..  
make  
make install  

You can use CMAKE_INSTALL_PREFIX to select the installation directory
Moreover, you can use cmake options to configure the library for header-only or pure core.
Instead of cmake options, you can define REDISCPP_HEADER_ONLY and use the library as header-only without any cmake file.

NOTE
redis-cpp has two build options

  • Pure core only
  • Header-only

Use cmake -D with REDISCPP_HEADER_ONLY or REDISCPP_PURE_CORE. You can enable both options at the same time.
You can use your own transport with the 'pure core' option.

If you need to use the header-only library, you can copy the folder redis-cpp from include/redis-cpp in your project and define the macro REDISCPP_HEADER_ONLY before including the redis-cpp headers following the example code below:

#define REDISCPP_HEADER_ONLY
#include <redis-cpp/stream.h>
#include <redis-cpp/execute.h>

// Include something else

Build examples

cd examples/{example_project}
mkdir build  
cd build  
cmake ..  
make  

Examples

NOTE
Look at the redis-docker folder to get all you need to start testing redis-cpp. There are files to build and run a Redis server in Docker.

Ping

Source code
Description
The "Ping" example demonstrates how to execute a Redis command.

// STD
#include <cstdlib>
#include <iostream>

#include <redis-cpp/stream.h>
#include <redis-cpp/execute.h>

int main()
{
    try
    {
        auto stream = rediscpp::make_stream("localhost", "6379");
        auto response = rediscpp::execute(*stream, "ping");
        std::cout << response.as<std::string>() << std::endl;
    }
    catch (std::exception const &e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Set and Get data

Source code
Description
The example demonstrates how to set and get a value.

// STD
#include <cstdlib>
#include <iostream>

#include <redis-cpp/stream.h>
#include <redis-cpp/execute.h>

int main()
{
    try
    {
        auto stream = rediscpp::make_stream("localhost", "6379");

        auto const key = "my_key";

        auto response = rediscpp::execute(*stream, "set",
                key, "Some value for 'my_key'", "ex", "60");

        std::cout << "Set key '" << key << "': " << response.as<std::string>() << std::endl;

        response = rediscpp::execute(*stream, "get", key);
        std::cout << "Get key '" << key << "': " << response.as<std::string>() << std::endl;
    }
    catch (std::exception const &e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Pipeline

Source code
Description
It's a more complicated example which demonstrates how to use a pipeline within Redis to achieve better performance.

// STD
#include <cstdlib>
#include <iostream>

#include <redis-cpp/stream.h>
#include <redis-cpp/execute.h>

int main()
{
    try
    {
        auto stream = rediscpp::make_stream("localhost", "6379");

        int const N = 10;
        auto const key_pref = "my_key_";

        // Executing command 'SET' N times without getting any response
        for (int i = 0 ; i < N ; ++i)
        {
            auto const item = std::to_string(i);
            rediscpp::execute_no_flush(*stream,
                "set", key_pref + item, item , "ex", "60");
        }

        // Flush all
        std::flush(*stream);

        // Getting response for each sent 'SET' request
        for (int i = 0 ; i < N ; ++i)
        {
            rediscpp::value value{*stream};
            std::cout << "Set " << key_pref << i << ": "
                      << value.as<std::string_view>() << std::endl;
        }

        // Executing command 'GET' N times without getting any response
        for (int i = 0 ; i < N ; ++i)
        {
            rediscpp::execute_no_flush(*stream, "get",
                key_pref + std::to_string(i));
        }

        // Flush all
        std::flush(*stream);

        // Getting response for each sent 'GET' request
        for (int i = 0 ; i < N ; ++i)
        {
            rediscpp::value value{*stream};
            std::cout << "Get " << key_pref << i << ": "
                      << value.as<std::string_view>() << std::endl;
        }
    }
    catch (std::exception const &e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Resp

Source code
Description
The "Resp" example demonstrates a basic RESP serialization within redis-cpp without communication with Redis server. It's meant to show you how to use RESP serialization with redis-cpp library.

// STD
#include <cstdlib>
#include <iostream>
#include <sstream>

#include <redis-cpp/execute.h>

namespace resps = rediscpp::resp::serialization;
namespace respds = rediscpp::resp::deserialization;

auto make_sample_data()
{
    std::ostringstream stream;

    put(stream, resps::array{
            resps::simple_string{"This is a simple string."},
            resps::error_message{"This is an error message."},
            resps::bulk_string{"This is a bulk string."},
            resps::integer{100500},
            resps::array{
                resps::simple_string("This is a simple string in a nested array."),
                resps::bulk_string("This is a bulk string in a nested array.")
            }
        });

    return stream.str();
}

void print_value(respds::array::item_type const &value, std::ostream &stream)
{
    std::visit(rediscpp::resp::detail::overloaded{
            [&stream] (respds::simple_string const &val)
            { stream << "Simple string: " << val.get() << std::endl; },
            [&stream] (respds::error_message const &val)
            { stream << "Error message: " << val.get() << std::endl; },
            [&stream] (respds::bulk_string const &val)
            { stream << "Bulk string: " << val.get() << std::endl; },
            [&stream] (respds::integer const &val)
            { stream << "Integer: " << val.get() << std::endl; },
            [&stream] (respds::array const &val)
            {
                stream << "----- Array -----" << std::endl;
                for (auto const &i : val.get())
                    print_value(i, stream);
                stream << "-----------------" << std::endl;
            },
            [&stream] (auto const &)
            { stream << "Unexpected value type." << std::endl; }
        }, value);
}

void print_sample_data(std::istream &istream, std::ostream &ostream)
{
    rediscpp::value value{istream};
    print_value(value.get(), ostream);
}

int main()
{
    try
    {
        auto const data = make_sample_data();
        std::cout << "------------ Serialization ------------" << std::endl;
        std::cout << data << std::endl;

        std::cout << "------------ Deserialization ------------" << std::endl;
        std::istringstream stream{data};
        print_sample_data(stream, std::cout);
        std::cout << std::endl;
    }
    catch (std::exception const &e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Publish / Subscribe

Source code
Description
This is a more complicated example within redis-cpp which demonstrates how to publish messages and create a subscription to a queue. In the example a publisher and subscriber located in one process simultaniously, each one has its own stream to communicate with Redis. Usually, in real projects the publisher and subscriber are not located in one process.

// STD
#include <cstdlib>
#include <iostream>
#include <thread>

// BOOST
#include <boost/thread.hpp>

#include <redis-cpp/stream.h>
#include <redis-cpp/execute.h>

int main()
{
    try
    {
        auto const N = 100;
        auto const queue_name = "test_queue";

        bool volatile stopped = false;

        // A message printer. The message from a queue.
        auto print_message = [] (auto const &value)
        {
            using namespace rediscpp::resp::deserialization;
            std::visit(rediscpp::resp::detail::overloaded{
                   [] (bulk_string const &val)
                   { std::cout << val.get() << std::endl; },
                   [] (auto const &)
                   { std::cout << "Unexpected value type." << std::endl; }
               }, value);
        };

        // The subscriber is run in its own thread.
        // It's some artificial example, when publisher
        // and subscriber are working in one process.
        // It's only for demonstration library abilities.
        boost::thread subscriber{
            [&stopped, &queue_name, &print_message]
            {
                // Its own stream for a subscriber
                auto stream = rediscpp::make_stream("localhost", "6379");
                auto response = rediscpp::execute(*stream, "subscribe", queue_name);
                // An almost endless loop for getting messages from the queues.
                while (!stopped)
                {
                    // Reading / waiting for a message.
                    rediscpp::value value{*stream};
                    // Message extraction.
                    std::visit(rediscpp::resp::detail::overloaded{
                            // We're wondered only an array in response.
                            // Otherwise, there is an error.
                            [&print_message] (rediscpp::resp::deserialization::array const &arr)
                            {
                                std::cout << "-------- Message --------" << std::endl;
                                for (auto const &i : arr.get())
                                    print_message(i);
                                std::cout << "-------------------------" << std::endl;
                            },
                            // Oops. An error in a response.
                            [] (rediscpp::resp::deserialization::error_message const &err)
                            { std::cerr << "Error: " << err.get() << std::endl; },
                            // An unexpected response.
                            [] (auto const &)
                            { std::cout << "Unexpected value type." << std::endl; }
                        }, value.get());
                }
            }
        };

        // An artificial delay. It's not necessary in real code.
        std::this_thread::sleep_for(std::chrono::milliseconds{200});

        // Its own stream for a publisher.
        auto stream = rediscpp::make_stream("localhost", "6379");

        // The publishing N messages.
        for (int i = 0 ; i < N ; ++i)
        {
            auto response = rediscpp::execute(*stream,
                    "publish", queue_name, std::to_string(i));
            std::cout << "Delivered to " << response.as<std::int64_t>()
                      << " subscribers." << std::endl;
        }

        // An artificial delay. It's not necessary in real code.
        // It's due to the artificiality of the example,
        // where everything is in one process.
        std::this_thread::sleep_for(std::chrono::milliseconds{200});

        stopped = true;
        std::this_thread::sleep_for(std::chrono::milliseconds{200});
        // Why not?... Please, avoid it in real code.
        // It's justified only in examples.
        subscriber.interrupt();
    }
    catch (std::exception const &e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Conclusion

Take a look at a code above one more time. I hope you can find something useful for your own projects with Redis. I'd thought about adding one more level to wrap all Redis commands and refused this idea. A lot of useless work with a small outcome, because, in many cases we need to run only a handful of commands. Maybe it'll be a good idea in the future. Now you can use redis-cpp like lightweight library to execute Redis commands and get results with minimal effort.

Enjoy your own projects with Redis!

Issues
  • Null Bulk String are converted to empty strings

    Null Bulk String are converted to empty strings

    If I get a non-existing key with the command

    rediscpp::value val = rediscpp::execute(*stream, "get", "non existing key");
    

    The server returns a Null Bulk String, but there is no way to differentiate it from an empty string. The variable is_null_ is set to true, but empty() returns false.

    Thanks

    opened by jean343 10
  • Boost ASIO resolver.resolve is deprecated

    Boost ASIO resolver.resolve is deprecated

    Hi,

    thank you for your great work! I really like this header only library for redis!

    But i get the following compilation error:

    In file included from /cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/3rdparty/redis-cpp/include/redis-cpp/stream.h:31,
                     from /cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/mmo-proxy-server.cpp:15:
    /cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/3rdparty/redis-cpp/include/redis-cpp/detail/stream.hpp: In constructor 'rediscpp::detail::stream::stream(std::string_view, std::string_view)':
    /cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/3rdparty/redis-cpp/include/redis-cpp/detail/stream.hpp:78:53: error: no matching function for call to 'boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::resolve(std::string_view&, std::string_view&)'
       78 |         auto endpoints = resolver.resolve(host, port);
          |                                                     ^
    In file included from /usr/include/boost/asio.hpp:80,
                     from /cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/3rdparty/redis-cpp/include/redis-cpp/detail/stream.hpp:15,
                     from /cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/3rdparty/redis-cpp/include/redis-cpp/stream.h:31,
                     from /cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/mmo-proxy-server.cpp:15:
    /usr/include/boost/asio/ip/basic_resolver.hpp:214:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const query&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::query = boost::asio::ip::basic_resolver_query<boost::asio::ip::tcp>]'
      214 |   results_type resolve(const query& q)
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:214:16: note:   candidate expects 1 argument, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:235:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const query&, boost::system::error_code&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::query = boost::asio::ip::basic_resolver_query<boost::asio::ip::tcp>]'
      235 |   results_type resolve(const query& q, boost::system::error_code& ec)
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:235:37: note:   no known conversion for argument 1 from 'std::string_view' {aka 'std::basic_string_view<char>'} to 'const query&' {aka 'const boost::asio::ip::basic_resolver_query<boost::asio::ip::tcp>&'}
      235 |   results_type resolve(const query& q, boost::system::error_code& ec)
          |                        ~~~~~~~~~~~~~^
    /usr/include/boost/asio/ip/basic_resolver.hpp:274:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      274 |   results_type resolve(BOOST_ASIO_STRING_VIEW_PARAM host,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:274:53: note:   no known conversion for argument 1 from 'std::string_view' {aka 'std::basic_string_view<char>'} to 'std::experimental::fundamentals_v1::string_view' {aka 'std::experimental::fundamentals_v1::basic_string_view<char>'}
      274 |   results_type resolve(BOOST_ASIO_STRING_VIEW_PARAM host,
          |                                                     ^
    /usr/include/boost/asio/ip/basic_resolver.hpp:313:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view, boost::system::error_code&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      313 |   results_type resolve(BOOST_ASIO_STRING_VIEW_PARAM host,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:313:16: note:   candidate expects 3 arguments, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:356:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view, boost::asio::ip::resolver_base::flags) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      356 |   results_type resolve(BOOST_ASIO_STRING_VIEW_PARAM host,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:356:16: note:   candidate expects 3 arguments, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:405:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view, boost::asio::ip::resolver_base::flags, boost::system::error_code&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      405 |   results_type resolve(BOOST_ASIO_STRING_VIEW_PARAM host,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:405:16: note:   candidate expects 4 arguments, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:450:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const protocol_type&, std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::protocol_type = boost::asio::ip::tcp; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      450 |   results_type resolve(const protocol_type& protocol,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:450:16: note:   candidate expects 3 arguments, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:492:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const protocol_type&, std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view, boost::system::error_code&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::protocol_type = boost::asio::ip::tcp; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      492 |   results_type resolve(const protocol_type& protocol,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:492:16: note:   candidate expects 4 arguments, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:539:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const protocol_type&, std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view, boost::asio::ip::resolver_base::flags) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::protocol_type = boost::asio::ip::tcp; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      539 |   results_type resolve(const protocol_type& protocol,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:539:16: note:   candidate expects 4 arguments, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:593:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const protocol_type&, std::experimental::fundamentals_v1::string_view, std::experimental::fundamentals_v1::string_view, boost::asio::ip::resolver_base::flags, boost::system::error_code&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::protocol_type = boost::asio::ip::tcp; std::experimental::fundamentals_v1::string_view = std::experimental::fundamentals_v1::basic_string_view<char>]'
      593 |   results_type resolve(const protocol_type& protocol,
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:593:16: note:   candidate expects 5 arguments, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:932:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const endpoint_type&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::endpoint_type = boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>]'
      932 |   results_type resolve(const endpoint_type& e)
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:932:16: note:   candidate expects 1 argument, 2 provided
    /usr/include/boost/asio/ip/basic_resolver.hpp:955:16: note: candidate: 'boost::asio::ip::basic_resolver<InternetProtocol>::results_type boost::asio::ip::basic_resolver<InternetProtocol>::resolve(const endpoint_type&, boost::system::error_code&) [with InternetProtocol = boost::asio::ip::tcp; boost::asio::ip::basic_resolver<InternetProtocol>::results_type = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>; boost::asio::ip::basic_resolver<InternetProtocol>::endpoint_type = boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>]'
      955 |   results_type resolve(const endpoint_type& e, boost::system::error_code& ec)
          |                ^~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:955:45: note:   no known conversion for argument 1 from 'std::string_view' {aka 'std::basic_string_view<char>'} to 'const endpoint_type&' {aka 'const boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>&'}
      955 |   results_type resolve(const endpoint_type& e, boost::system::error_code& ec)
          |                        ~~~~~~~~~~~~~~~~~~~~~^
    make[3]: *** [CMakeFiles/ProxyServer.dir/build.make:63: CMakeFiles/ProxyServer.dir/mmo-proxy-server.cpp.o] Error 1
    make[3]: Leaving directory '/cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/cmake-build-debug'
    make[2]: *** [CMakeFiles/Makefile2:76: CMakeFiles/ProxyServer.dir/all] Error 2
    make[2]: Leaving directory '/cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/cmake-build-debug'
    make[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/ProxyServer.dir/rule] Error 2
    make[1]: Leaving directory '/cygdrive/c/Users/Justin/IdeaProjects/MMOPrototyp/mmo-proxy-server/cmake-build-debug'
    make: *** [Makefile:118: ProxyServer] Error 2
    

    As screenshot: grafik

    The problem is, that the resolve function is already deprecated. grafik

    See also: https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/ip__tcp/resolver.html

    Can you please fix this?

    opened by JuKu 7
  • Build errors under Ubuntu 18.04 with GCC 9

    Build errors under Ubuntu 18.04 with GCC 9

    When I build under Ubuntu 18.04, I get:

    FAILED: src/source.p/source.cpp.o 
    c++ -Isrc/source.p -Isrc -I../src -I../include/loguru -Iinclude -I../include -Isrc/test_utils -I../src/test_utils -I../include/test -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wnon-virtual-dtor -Wextra -Wpedantic -std=c++17 -g -pthread -MD -MQ src/source.p/source.cpp.o -MF src/source.p/source.cpp.o.d -o src/source.p/source.cpp.o -c ../src/source.cpp
    In file included from ../include/redis-cpp/stream.h:33,
                     from ../src/source.cpp:37:
    ../include/redis-cpp/detail/stream.hpp:109:18: error: 'io_context' in namespace 'boost::asio' does not name a type
      109 |     boost::asio::io_context io_context_;
          |                  ^~~~~~~~~~
    ../include/redis-cpp/detail/stream.hpp:110:42: error: 'io_context_' was not declared in this scope
      110 |     boost::asio::ip::tcp::socket socket_{io_context_};
          |                                          ^~~~~~~~~~~
    ../include/redis-cpp/detail/stream.hpp:110:53: error: could not convert '{<expression error>}' from '<brace-enclosed initializer list>' to 'boost::asio::ip::tcp::socket' {aka 'boost::asio::basic_stream_socket<boost::asio::ip::tcp>'}
      110 |     boost::asio::ip::tcp::socket socket_{io_context_};
          |                                                     ^
          |                                                     |
          |                                                     <brace-enclosed initializer list>
    ../include/redis-cpp/detail/stream.hpp: In lambda function:
    ../include/redis-cpp/detail/stream.hpp:82:57: error: 'io_context_' was not declared in this scope
       82 |                 boost::asio::ip::tcp::resolver resolver{io_context_};
          |                                                         ^~~~~~~~~~~
    ../include/redis-cpp/detail/stream.hpp:82:68: error: no matching function for call to 'boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::basic_resolver(<brace-enclosed initializer list>)'
       82 |                 boost::asio::ip::tcp::resolver resolver{io_context_};
          |                                                                    ^
    In file included from /usr/include/boost/asio.hpp:63,
                     from ../include/redis-cpp/detail/stream.hpp:17,
                     from ../include/redis-cpp/stream.h:33,
                     from ../src/source.cpp:37:
    /usr/include/boost/asio/ip/basic_resolver.hpp:67:12: note: catestdate: 'boost::asio::ip::basic_resolver<InternetProtocol, ResolverService>::basic_resolver(boost::asio::io_service&) [with InternetProtocol = boost::asio::ip::tcp; ResolverService = boost::asio::ip::resolver_service<boost::asio::ip::tcp>]'
       67 |   explicit basic_resolver(boost::asio::io_service& io_service)
          |            ^~~~~~~~~~~~~~
    /usr/include/boost/asio/ip/basic_resolver.hpp:67:12: note:   conversion of argument 1 would be ill-formed:
    In file included from ../include/redis-cpp/stream.h:33,
                     from ../src/source.cpp:37:
    ../include/redis-cpp/detail/stream.hpp:83:83: error: no matching function for call to 'boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::resolve(std::remove_reference<const std::basic_string_view<char>&>::type, std::remove_reference<const std::basic_string_view<char>&>::type)'
       83 |                 auto endpoints = resolver.resolve(std::move(host), std::move(port));
          |                                                                                   ^
    

    This is using the following packages installed:

    apt-get update -qq && \
      apt-get install --assume-yes --no-install-recommends \
      ca-certificates \
      build-essential \
      wget \
      ninja-build \
      pkg-config \
      cmake \
      ninja-build \
      libboost-all-dev \
      libcurl4-openssl-dev \
      libpcap-dev \
      libavahi-client-dev \
      python3-pip \
      software-properties-common
    
    add-apt-repository ppa:ubuntu-toolchain-r/test && \
      apt-get update -qq && \
      apt install --assume-yes gcc-9 g++-9 && \
      update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9
    

    So I have g++ (Ubuntu 9.4.0-1ubuntu1~18.04) 9.4.0 and libboost-all-dev version 1.65.1.0ubuntu1.

    opened by slhck 3
  • Undefined Reference to rediscpp::make_stream

    Undefined Reference to rediscpp::make_stream

    I compiled redis-cpp with following code `

    1. cd /opt &&\
    2. git clone https://github.com/tdv/redis-cpp.git &&\
    3. cd redis-cpp &&\
    4. mkdir build &&\
    5. cd build &&\
    6. cmake -DREDISCPP_HEADER_ONLY=OFF -DREDISCPP_PURE_CORE=ON .. &&\
    7. make &&\
    8. make install`

    On this line

    auto redis_client = rediscpp::make_stream(this->redis_host, this->redis_port);

    I getting below error

    undefined reference torediscpp::make_stream(std::basic_string_view<char, std::char_traits >, std::basic_string_view<char, std::char_traits >)' collect2: error: ld returned 1 exit status`

    My question is that, How to find redis-cpp in cmake and link with my code?? Any help would be appreciated.

    @tdv

    opened by mhasnainamjad 3
  • how to get pattern/channel info in pub/sub.

    how to get pattern/channel info in pub/sub.

    Hi, I am using redis-python for publishing the data which is publishing the data in raw format such as - {'type': 'pmessage', 'pattern': 'channel-*', 'channel': 'channel', 'data': 'data'}.

    When i receive it with redis-cpp subscribe, i get all the information but only values (not keys) also as separate/discrete messages like- pmessage channel-* channel data

    if i underline the specific code segmant - [&print_message] (rediscpp::resp::deserialization::array const &arr) { for (auto const &i : arr.get()){ print_message(i); } Here i can see the whole array is processed line-by-line and string data is extracted for each line....

    My question is can i get the whole array as in json/string format so that i can use that in my way? Thanks.

    opened by rahulsharma11 2
  • Fatal error C1189: #error:

    Fatal error C1189: #error: "RedisCpp. Requires C++ 17 or higher."

    When building the C++11 branch I got this error. I'm using Visual Studio 2019.

    CMakeList has the flag set (STD_CXX "c++11") yet I don't know why this happens. I have tried setting the project settings to use ISO C++11 but nothing changes.

    Any hint?

    opened by Domigome 2
  • multiple definition in header-only library

    multiple definition in header-only library

    I'm attempting to use the header only import of the c++11 branch (basically as described in https://github.com/tdv/redis-cpp/issues/4#issuecomment-667507438). I've included the macro definition #define REDISCPP_HEADER_ONLY, but when I compile it I get the following error:

    multiple definition of `rediscpp::resp::deserialization::get_mark(std::istream&)';
    

    I even tried using #ifndef but the issue persisted. I'm not sure what causes this but, if I change: https://github.com/tdv/redis-cpp/blob/a7302a31ce5b19e6f58ff03957c73acc882d78cf/include/redis-cpp/resp/deserialization.h#L36 to:

    [[nodiscard]]
    inline char get_mark(std::istream &stream)
    {
    

    It resolves the issue. I'm not sure if this is the correct way to fix this though. Can anyone confirm this issue / resolution?

    opened by rochadt 2
  • cannot pass object of non-trivial type 'const rediscpp::resp::deserialization::error_message' through variadic function

    cannot pass object of non-trivial type 'const rediscpp::resp::deserialization::error_message' through variadic function

    I have a simple example:

    #include <iostream>
    
    #define REDISCPP_HEADER_ONLY
    #include <redis-cpp/stream.h>
    #include <redis-cpp/execute.h>
    
    int main(int argc, char *argv[])
    {
      try
      {
        auto stream = rediscpp::make_stream(
            "localhost",
            "6379"
          );
    
        auto const key = "my_key";
    
        auto response = rediscpp::execute(*stream, "set",
                                          key, "Some value for 'my_key'", "ex", "60");
    
        std::cout << "Set key '" << key << "': " << response.as<std::string>() << std::endl;
    
        response = rediscpp::execute(*stream, "get", key);
        std::cout << "Get key '" << key << "': " << response.as<std::string>() << std::endl;
    
        response = rediscpp::execute(*stream, "del", key);
        std::cout << "Deleted key '" << key << "': " << response.as<std::string>() << std::endl;
      }
      catch (std::exception const &e)
      {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
      }
      return EXIT_SUCCESS;
    }
    

    When compiling this under macOS 10.15.7, I get:

    ➜ meson compile -C builddir
    ninja: Entering directory `builddir'
    [1/2] Compiling C++ object redis_test.p/src_redis_test.cpp.o
    FAILED: redis_test.p/src_redis_test.cpp.o 
    c++ -Iredis_test.p -I. -I.. -I../include/redis-cpp -I../include -Xclang -fcolor-diagnostics -pipe -Wall -Winvalid-pch -Wnon-virtual-dtor -Wextra -Wpedantic -std=c++17 -g -MD -MQ redis_test.p/src_redis_test.cpp.o -MF redis_test.p/src_redis_test.cpp.o.d -o redis_test.p/src_redis_test.cpp.o -c ../src/redis_test.cpp
    In file included from ../src/redis_test.cpp:5:
    In file included from ../include/redis-cpp/execute.h:21:
    ../include/redis-cpp/value.h:203:33: error: cannot pass object of non-trivial type 'const rediscpp::resp::deserialization::error_message' through variadic function; call will abort at runtime [-Wnon-pod-varargs]
                        if (is_null(val))
                                    ^
    ../include/redis-cpp/value.h:116:16: note: in instantiation of function template specialization 'rediscpp::value::get_value<std::__1::basic_string_view<char, std::__1::char_traits<char> >, rediscpp::resp::deserialization::error_message>' requested here
            return get_value<std::string_view, resp::deserialization::error_message>();
                   ^
    ../include/redis-cpp/value.h:203:33: error: cannot pass object of non-trivial type 'const rediscpp::resp::deserialization::simple_string' through variadic function; call will abort at runtime [-Wnon-pod-varargs]
                        if (is_null(val))
                                    ^
    ../include/redis-cpp/value.h:122:16: note: in instantiation of function template specialization 'rediscpp::value::get_value<std::__1::basic_string_view<char, std::__1::char_traits<char> >, rediscpp::resp::deserialization::simple_string>' requested here
            return get_value<std::string_view, resp::deserialization::simple_string>();
                   ^
    2 errors generated.
    ninja: build stopped: subcommand failed.
    

    More info on the compiler:

    ➜ c++ --version
    Apple clang version 12.0.0 (clang-1200.0.32.29)
    Target: x86_64-apple-darwin19.6.0
    Thread model: posix
    InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
    

    Any idea what the problem could be?

    opened by slhck 2
  • Array return issues

    Array return issues

    Great project, very good implementation thanks for this project. There is no other way to contact you as this is not an issue with the project but my lack of understanding a portion of the code.

    I’m executing commands in MVS 2019 on windows but connecting to redis within an Ubuntu VM.
    I am primarily working with the Redis TimeSeries module (TSDB). I have successfully ingested data into TSDB but I am having problems syntactically with the return values. Your set and get examples for redis database within the project summarize Bulk/string replies. However, can you please provide an example of an array type reply for commands such as HMGET? Below I will give an example of the TS.GET command which is for the redis time series module but both HMGET and TS.GET return values are array replies.

    I’ve been studying the value.h, execute.h, deserialization.h files and looked at your resp serialization and deserialization examples to get an understanding of the code. I’ve looked at value.h line 83

    [[nodiscard]] bool is_array() const noexcept { return marker_ == resp::detail::marker::array; } But there is no corresponding return get_value for an array type?

    This is what I have done so far:

    #define REDISCPP_HEADER_ONLY #include <redis-cpp/execute.h> #include <redis-cpp/stream.h>

    int main() { try {

    // Create Redis Connection
    auto stream = rediscpp::make_stream("localhost", "6379");
    
    // Variables to Create & Add Sample to Redis TimeSeries DB (TSDB)
    auto const pmc       = "TS.ADD";    // Redis cmd to create and add TSDB
    auto const key       = "MyData";    // Key
    auto const times     = "*";         // Redis Arg for auto generated time stamp
    auto const sampv     = "1";         // Redis Arg to assign sample number
    auto const cmd2      = "RETENTION"; // Redis Arg to assign Retention period of sample
    auto const retperiod = "0";         // Retention period set to 0 (Meaning, sample will persist indefinately within TSDB)
    auto const cmd3      = "LABELS";    // Redis Arg to assign Labels within TSDB
    auto const label1    = "Speed";     // Label 1 = Speed
    auto const value1    = "60 mph";    // Value assigned to Label 1
    auto const label2    = "Time";
    auto const value2    = "2 seconds";
    
    // Execute Command to Create & Add Sample to Redis TimeSeries DB (TSDB)
    auto response = rediscpp::execute(*stream, pmc, key, times, sampv, cmd2, retperiod, 
                                       cmd3, label1, value1, label2, value2);
    
    //  No problems here.  Command executes successfully and we have now added a
    //  sample to TSDB. If you have Redis TSDB installed TS.INFO MyData should
    //  show the following:
    
    /*  1) totalSamples
        2) (integer) 1
        3) memoryUsage
        4) (integer) 4244
        5) firstTimestamp
        6) (integer) 1592739199434
        7) lastTimestamp
        8) (integer) 1592739199434
        9) retentionTime
        10) (integer) 0
        11) chunkCount
        12) (integer) 1
        13) maxSamplesPerChunk
        14) (integer) 256
        15) labels
        16) 1)  1) "Speed"
                2) "60 mph"
            2)  1) "Time"
                2) "2 seconds"
        17) sourceKey
        18) (nil)
        19) rules
        20) (empty list or set)*/
    
    // Problem starts here when we want the return value in the form of an array
    // within the client. Redis documentation for TS.GET command - The returned
    // array will contain: - The last sample timestamp followed by the last
    // sample value, when the time-series contains data. - An empty array, when
    // the time-series is empty.
    
    // TS.GET command Variable
    auto const redisoutput = "TS.GET";
    
    response = rediscpp::execute(*stream, redisoutput, key);
    std::cout << "TS.GET '" << key << "': " << 
    response.as<std::string>() << std::endl; //  ---> TS.GET 'MyData': Error: bad cast  
    

    } catch (std::exception const &e) { std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }

    Any command that returns an array will have the error above : "Error: bad cast". Can you provide an example of an array return with a command like HMGET or TS.GET (if you use TSDB)?

    Thanks.

    opened by NMS0 2
  • More connection parameters

    More connection parameters

    Hi!

    I've seen that the connection function does not have password into it

    i.e.:

    auto stream = rediscpp::make_stream("localhost", "6379");
    

    Is there any way to define the password of the connection?

    opened by SanPen 1
  • Could NOT find Boost (missing: thread system iostreams) (found suitable   version

    Could NOT find Boost (missing: thread system iostreams) (found suitable version "1.74.0", minimum required is "1.67.0")

    Hi there,

    Trying to build con Windows 10 64 bits with Boost 1.74.0 I get this error from Cmake.

    Selecting Windows SDK version 10.0.19041.0 to target Windows 10.0.19042.
    The C compiler identification is MSVC 19.16.27044.0
    The CXX compiler identification is MSVC 19.16.27044.0
    Detecting C compiler ABI info
    Detecting C compiler ABI info - done
    Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x64/cl.exe - skipped
    Detecting C compile features
    Detecting C compile features - done
    Detecting CXX compiler ABI info
    Detecting CXX compiler ABI info - done
    Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x64/cl.exe - skipped
    Detecting CXX compile features
    Detecting CXX compile features - done
    Looking for pthread.h
    Looking for pthread.h - not found
    Found Threads: TRUE  
    CMake Error at C:/Program Files/CMake/share/cmake-3.21/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
      Could NOT find Boost (missing: thread system iostreams) (found suitable
      version "1.74.0", minimum required is "1.67.0")
    Call Stack (most recent call first):
      C:/Program Files/CMake/share/cmake-3.21/Modules/FindPackageHandleStandardArgs.cmake:594 (_FPHSA_FAILURE_MESSAGE)
      C:/Program Files/CMake/share/cmake-3.21/Modules/FindBoost.cmake:2345 (find_package_handle_standard_args)
      CMakeLists.txt:40 (find_package)
    
    
    Configuring incomplete, errors occurred!
    See also "C:/Users/USER/Downloads/cpp-redis-header/redis-cpp/buildclean/CMakeFiles/CMakeOutput.log".
    See also "C:/Users/USER/Downloads/cpp-redis-header/redis-cpp/buildclean/CMakeFiles/CMakeError.log".
    

    I have also try to manually set the path with:

    set(BOOST_ROOT "C:/local/boost_1_74_0") set(Boost_INCLUDE_DIR "C:/local/boost_1_74_0") set(Boost_LIBRARY_DIR "C:/local/boost_1_74_0/libs")

    But still same error.

    Also tried with set(Boost_USE_STATIC_LIBS ON) and same error persist.

    No luck, hope someone can give me a lead.

    opened by Domigome 1
Owner
If you like to directly contact me you could do that via Gmail having written an email to tkachenkodmitryv, I'll really appreciate your feedback and proposals
null
Modern, asynchronous, and wicked fast C++11 client for Redis

redox Modern, asynchronous, and wicked fast C++11 client for Redis [] (https://travis-ci.org/hmartiro/redox) Redox is a C++ interface to the Redis key

Hayk Martiros 374 Jun 14, 2022
Minimalistic C client for Redis >= 1.2

This Readme reflects the latest changed in the master branch. See v1.0.0 for the Readme and documentation for the latest release (API/ABI history). HI

Redis 5.3k Jun 29, 2022
C++11 Lightweight Redis client: async, thread-safe, no dependency, pipelining, multi-platform

C++11 Lightweight Redis client: async, thread-safe, no dependency, pipelining, multi-platform

Simon Ninon 967 Jun 22, 2022
Redis client written in C++

redis-plus-plus Overview Features Branches Installation Install hiredis Install redis-plus-plus Run Tests (Optional) Use redis-plus-plus In Your Proje

null 855 Jun 22, 2022
cpp_redis is a C++11 Asynchronous Multi-Platform Lightweight Redis Client

C++11 Lightweight Redis client: async, thread-safe, no dependency, pipelining, multi-platform

CPP Redis 501 Jun 22, 2022
Aredis - a clean redis C++ client

aredis a clean redis C++ client redis_conn rc; if (rc.connect("127.0.0.1"/*host*/, 6379/*port*/, nullptr/*password*/, 1/*db*/)) { redis_command cmd;

Evan 26 Dec 10, 2021
A C++ Redis client

redis3m A C++ Redis client, born to bring my experience using Redis and C++ on a opensource library. Main goals Provide a simple and efficient wrapper

Luca Marturana 184 Apr 21, 2022
This is a "pure" CPP implementation Database

This is a "pure" CPP implementation Database

PoHengLin 1 Jan 22, 2022
A lightweight header-only C++11 library for quick and easy SQL querying with QtSql classes.

EasyQtSql EasyQtSql is a lightweight header-only C++11 library for quick and easy SQL querying with QtSql classes. Features: Header only C++11 library

null 44 Jun 28, 2022
❤️ SQLite ORM light header only library for modern C++

SQLite ORM SQLite ORM light header only library for modern C++ Status Branch Travis Appveyor master dev Advantages No raw string queries Intuitive syn

Yevgeniy Zakharov 1.6k Jun 27, 2022
A friendly and lightweight C++ database library for MySQL, PostgreSQL, SQLite and ODBC.

QTL QTL is a C ++ library for accessing SQL databases and currently supports MySQL, SQLite, PostgreSQL and ODBC. QTL is a lightweight library that con

null 155 Jun 26, 2022
dqlite is a C library that implements an embeddable and replicated SQL database engine with high-availability and automatic failover

dqlite dqlite is a C library that implements an embeddable and replicated SQL database engine with high-availability and automatic failover. The acron

Canonical 3k Jun 27, 2022
Velox is a new C++ vectorized database acceleration library aimed to optimizing query engines and data processing systems.

Velox is a C++ database acceleration library which provides reusable, extensible, and high-performance data processing components

Facebook Incubator 893 Jun 27, 2022
Trilogy is a client library for MySQL-compatible database servers, designed for performance, flexibility, and ease of embedding.

Trilogy is a client library for MySQL-compatible database servers, designed for performance, flexibility, and ease of embedding.

GitHub 248 Jun 11, 2022
Simple constant key/value storage library, for read-heavy systems with infrequent large bulk inserts.

Sparkey is a simple constant key/value storage library. It is mostly suited for read heavy systems with infrequent large bulk inserts. It includes bot

Spotify 965 Jun 19, 2022
A type safe SQL template library for C++

sqlpp11 A type safe embedded domain specific language for SQL queries and results in C++ Documentation is found in the wiki So what is this about? SQL

Roland Bock 2k Jul 2, 2022
Kreon is a key-value store library optimized for flash-based storage

Kreon is a key-value store library optimized for flash-based storage, where CPU overhead and I/O amplification are more significant bottlenecks compared to I/O randomness.

Computer Architecture and VLSI Systems (CARV) Laboratory 20 Jun 10, 2022
SOCI - The C++ Database Access Library

Originally, SOCI was developed by Maciej Sobczak at CERN as abstraction layer for Oracle, a Simple Oracle Call Interface. Later, several database backends have been developed for SOCI, thus the long name has lost its practicality. Currently, if you like, SOCI may stand for Simple Open (Database) Call Interface or something similar.

SOCI 1.1k Jun 25, 2022
The fastest database-library on Android OS.

Android SQLite3 NDK 封装 Demo下载 (操作:按钮新增 按钮查询 点按编辑 长按删除) 写在前面 sqlite3 开源、集成简单(现在的版本只有2个文件 sqlite3.h sqlite3.c) 这个库抽离自 Telegram 的开源代码、作者:DrKLO 我个人感觉 Tele

水银灯、 2 Dec 27, 2021