Repository: sewenew/redis-protobuf Branch: master Commit: c3329b508f53 Files: 77 Total size: 285.5 KB Directory structure: gitextract_4asdw0d9/ ├── .gitignore ├── CMakeLists.txt ├── Chinese.md ├── LICENSE ├── README.md ├── docker/ │ ├── Dockerfile │ ├── example.proto │ └── google/ │ └── protobuf/ │ ├── any.proto │ ├── duration.proto │ ├── empty.proto │ ├── struct.proto │ ├── timestamp.proto │ └── wrappers.proto ├── src/ │ └── sw/ │ └── redis-protobuf/ │ ├── append_command.cpp │ ├── append_command.h │ ├── clear_command.cpp │ ├── clear_command.h │ ├── commands.cpp │ ├── commands.h │ ├── del_command.cpp │ ├── del_command.h │ ├── errors.h │ ├── field_ref.h │ ├── get_command.cpp │ ├── get_command.h │ ├── import_command.cpp │ ├── import_command.h │ ├── last_import_command.cpp │ ├── last_import_command.h │ ├── len_command.cpp │ ├── len_command.h │ ├── merge_command.cpp │ ├── merge_command.h │ ├── module_api.cpp │ ├── module_api.h │ ├── module_entry.cpp │ ├── module_entry.h │ ├── options.cpp │ ├── options.h │ ├── path.cpp │ ├── path.h │ ├── proto_factory.cpp │ ├── proto_factory.h │ ├── redis_protobuf.cpp │ ├── redis_protobuf.h │ ├── redismodule.cpp │ ├── redismodule.h │ ├── schema_command.cpp │ ├── schema_command.h │ ├── set_command.cpp │ ├── set_command.h │ ├── type_command.cpp │ ├── type_command.h │ ├── utils.cpp │ └── utils.h └── test/ └── src/ └── sw/ └── redis-protobuf/ ├── append_test.cpp ├── append_test.h ├── clear_test.cpp ├── clear_test.h ├── del_test.cpp ├── del_test.h ├── import_test.cpp ├── import_test.h ├── len_test.cpp ├── len_test.h ├── merge_test.cpp ├── merge_test.h ├── proto_test.cpp ├── proto_test.h ├── schema_test.cpp ├── schema_test.h ├── set_get_test.cpp ├── set_get_test.h ├── test_main.cpp ├── type_test.cpp ├── type_test.h └── utils.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # 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 ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1) set(REDIS_PROTOBUF_VERSION "0.1.0") message(STATUS "redis-protobuf version: ${REDIS_PROTOBUF_VERSION}") project(redis-protobuf LANGUAGES CXX VERSION ${REDIS_PROTOBUF_VERSION}) set(REDIS_PROTOBUF_DEFAULT_BUILD_TYPE "Release") if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE ${REDIS_PROTOBUF_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-protobuf build type: ${CMAKE_BUILD_TYPE}") set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wextra -Werror -fPIC -Wno-unused-parameter") set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis-protobuf) file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp" "${PROJECT_SOURCE_DIR}/*.cc") set(SHARED_LIB shared) add_library(${SHARED_LIB} SHARED ${PROJECT_SOURCE_FILES}) # protobuf dependency find_path(PROTOBUF_HEADER google) target_include_directories(${SHARED_LIB} PUBLIC ${PROTOBUF_HEADER}) find_library(PROTOBUF_LIB libprotobuf.a) if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") target_link_libraries(${SHARED_LIB} -Wl,-force_load ${PROTOBUF_LIB}) target_link_libraries(${SHARED_LIB} z) else() target_link_libraries(${SHARED_LIB} -Wl,--whole-archive ${PROTOBUF_LIB} -Wl,--no-whole-archive) endif() set_target_properties(${SHARED_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) set_target_properties(${SHARED_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1) include(GNUInstallDirs) # Install shared lib. install(TARGETS ${SHARED_LIB} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) ================================================ FILE: Chinese.md ================================================ 欢迎加入redis-protobuf微信交流群 ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # redis-protobuf [中文交流群](http://github.com/sewenew/redis-protobuf/blob/master/Chinese.md) - [Overview](#overview) - [Motivation](#motivation) - [Installation](#installation) - [Run redis-protobuf With Docker](#run-redis-protobuf-with-docker) - [Install redis-protobuf With Source Code](#install-redis-protobuf-with-source-code) - [Load redis-protobuf](#load-redis-protobuf) - [Getting Started](#getting-started) - [redis-cli](#redis-cli) - [C++ Client](#c-client) - [Python Client](#python-client) - [Commands](#commands) - [Path](#path) - [PB.SET](#pbset) - [PB.GET](#pbget) - [PB.DEL](#pbdel) - [PB.APPEND](#pbappend) - [PB.LEN](#pblen) - [PB.CLEAR](#pbclear) - [PB.MERGE](#pbmerge) - [PB.TYPE](#pbtype) - [PB.SCHEMA](#pbschema) - [Author](#author) ## Overview This is a [Redis Module](https://redis.io/topics/modules-intro) for reading and writing protobuf messages (only support Protobuf version 3, i.e. `syntax="proto3";`). **NOTE**: In order to use this module, you should read the [Protobuf doc](https://developers.google.com/protocol-buffers) to learn how to define a protobuf message. **NOTE**: I'm not a native speaker. So if the documentation is unclear, please feel free to open an issue or pull request. I'll response ASAP. ### Motivation Redis supports multiple data structures, such as string, list, and hash. In order to keep things simple, Redis doesn't support nested data structures, e.g. you cannot have a list whose element is a list. However, sometimes nested data structures are useful, and [RedisJSON](https://github.com/RedisJSON/RedisJSON) is an option. With *RedisJSON*, you can save JSON object into Redis, and read and write JSON fields with that module. Since JSON can be nested, you can define your own nested data structure. In fact, *redis-protobuf* is inspired by *RedisJSON*. Both JSON and Protobuf are methods for serializing structured data. You can find many articles comparing these methods. Basically, Protobuf is faster and smaller than JSON, while JSON is more human friendly, since the former is binary while the latter is text. Also, you can convert a Protobuf to JSON and vice versa. In order to make the nested data structure fast and memory efficient, I wrote this *redis-protobuf* module. With this module, you can define a Protobuf message, and *redis-protobuf* will use reflection to read and write message fields. ## Installation ### Run redis-protobuf With Docker Run the following command to start *redis-protobuf* with Docker. ``` docker run -p 6379:6379 sewenew/redis-protobuf:latest ``` In this case, Docker runs Redis with a *redis.conf* file located at */usr/lib/redis/conf/redis.conf*. Also, you should put your *.proto* files in */usr/lib/redis/proto* directory. However, by default, the Docker image ships with an [example.proto](https://github.com/sewenew/redis-protobuf/blob/master/docker/example.proto) file, so that you can run the following examples without creating extra *.proto* files. After running the Docker image, you can go to the [Getting Started section](#getting-started) to see how to run *redis-protobuf* commands. ### Install redis-protobuf With Source Code You can also install redis-protobuf with source code. #### Install Protobuf First of all, you need to install Protobuf-C++. However, [the offical release](https://github.com/protocolbuffers/protobuf) doesn't expose reflection API for fields of map type (see [this issue](https://github.com/protocolbuffers/protobuf/issues/1322) for detail). So I modified the code to expose some internal interfaces. I published the modified code with *redis-protobuf* release, and you can download it from [here](https://github.com/sewenew/redis-protobuf/releases/download/0.0.1/protobuf-3.8.0-map-reflection.tar.gz). Also, you can check the *CHANGES-BY-SEWENEW.md* file for detail on what I've modifed. Install modified Protobuf with the following commands: ``` curl -L -k https://github.com/sewenew/redis-protobuf/releases/download/0.0.1/protobuf-3.8.0-map-reflection.tar.gz -o protobuf-3.8.0-map-reflection.tar.gz tar xfz protobuf-3.8.0-map-reflection.tar.gz cd protobuf-3.8.0-map-reflection ./configure "CFLAGS=-fPIC" "CXXFLAGS=-fPIC" make -j 4 make install ``` If you want to install Protobuf at a non-default location, you can specify the `--prefix=/path/to/install/location` option. ``` ./configure "CFLAGS=-fPIC" "CXXFLAGS=-fPIC" --prefix=/usr ``` **NOTE**: You must specify the `"CFLAGS=-fPIC" "CXXFLAGS=-fPIC"` options when compiling Protobuf. #### Install redis-protobuf *redis-protobuf* is built with [CMAKE](https://cmake.org). ``` git clone https://github.com/sewenew/redis-protobuf.git cd redis-protobuf mkdir compile cd compile cmake .. make ``` If Protobuf is installed at non-default location, you should use `CMAKE_PREFIX_PATH` to specify the installation path of Protobuf. ``` cmake -DCMAKE_PREFIX_PATH=/path/to/Protobuf .. ``` When `make` is done, you should find *libredis-protobuf.so* (or *libredis-protobuf.dylib* on MacOS) under the *redis-protobuf/compile* directory. ### Load redis-protobuf Redis Module is supported since Redis 4.0, so you must install Redis 4.0 or above. In order to load *redis-protobuf*, you need to modify the *redis.conf* file to add the `loadmodule` directive: ``` loadmodule /path/to/libredis-protobuf.so --dir proto-directory ``` *proto-directory* is the directory where your *.proto* files located. You must ensure that the directory exists and put your *.proto* files in this directory, so that *redis-protobuf* can load these *.proto* files dynamically. Also, if you want to use the standard *.proto* files, such as *google/protobuf/timestamp.proto*, *google/protobuf/struct.proto*, you also need to copy [these](https://github.com/sewenew/redis-protobuf/tree/master/docker/google/protobuf) standard proto files to *proto-directory*. Check [this](https://github.com/sewenew/redis-protobuf/issues/13) and [this](https://github.com/sewenew/redis-protobuf/issues/14) issues for detail. Now, you can start your Redis instance: ``` redis-server /path/to/redis.conf ``` If Redis loads the module successfully, you can get the following message from the log: ``` Module 'PB' loaded from /path/to/libredis-protobuf.so ``` **NOTE**: If any of the given *.proto* file is invalid, Redis fails to load the module. #### Possible Errors If Redis fails to load *redis-protobuf*, and print the following error message: ``` undefined symbol: deflateInit2_ ``` You can try [this solution](https://github.com/sewenew/redis-protobuf/issues/16). #### redis-protobuf Options ## Getting Started After [loading the module](#load-redis-protobuf), you can use any Redis client to send *redis-protobuf* [commands](#Commands). We'll use the following *.proto* file as example, unless otherwise stated. In order to test examples in this doc, you need to put the following *.proto* file in the *proto-directory*. **NOTE**: The [Docker image](https://cloud.docker.com/repository/docker/sewenew/redis-protobuf) also ships with this *.proto* file. So you can run the following examples with Docker too. ``` syntax = "proto3"; message SubMsg { string s = 1; int32 i = 2; } message Msg { int32 i = 1; SubMsg sub = 2; repeated int32 arr = 3; } ``` As we mentioned before, *redis-protobuf* only supports Protobuf version 3. So you must put `syntax = "proto3";` at the beginning of your *.proto* file. ### redis-cli The following examples use the offical Redis client, i.e. *redis-cli*, to send *redis-protobuf* commands. List module info: ``` 127.0.0.1:6379> MODULE LIST 1) 1) "name" 2) "PB" 3) "ver" 4) (integer) 1 ``` Set message: ``` 127.0.0.1:6379> PB.SET key Msg '{"i" : 1, "sub" : {"s" : "string", "i" : 2}, "arr" : [1, 2, 3]}' (integer) 1 ``` **NOTE**: As we mentioned before, Protobuf is not human friendly. So *redis-protobuf* also supports setting JSON string as value, and the module will convert the JSON string to Protobuf message automatically. Check the [C++ client section](c-client) to see an example of setting binary string as value. Get message: ``` 127.0.0.1:6379> PB.GET key --FORMAT JSON Msg "{\"i\":1,\"sub\":{\"s\":\"string\",\"i\":2},\"arr\":[1,2,3]}" ``` Set fields: ``` 127.0.0.1:6379> PB.SET key Msg /i 10 (integer) 1 127.0.0.1:6379> PB.SET key Msg /sub/s redis-protobuf (integer) 1 127.0.0.1:6379> PB.SET key Msg /arr/0 2 (integer) 1 ``` Get fields: ``` 127.0.0.1:6379> PB.GET key Msg /i (integer) 10 127.0.0.1:6379> PB.GET key Msg /sub/s "redis-protobuf" 127.0.0.1:6379> PB.GET key Msg /arr/0 (integer) 2 127.0.0.1:6379> PB.GET key --FORMAT JSON Msg /sub "{\"s\":\"redis-protobuf\",\"i\":2}" ``` Delete message: ``` 127.0.0.1:6379> PB.DEL key Msg (integer) 1 ``` ### C++ Client If you are using C++, you can use [redis-plus-plus](https://github.com/sewenew/redis-plus-plus) to send *redis-protobuf* commands: ```C++ try { auto redis = Redis("tcp://127.0.0.1"); // Set Protobuf message. Msg msg; msg.set_i(1); auto *sub = msg.mutable_sub(); sub->set_s("string"); sub->set_i(2); msg.add_arr(1); msg.add_arr(2); msg.add_arr(3); // Serialize Protobuf message. std::string s; if (!msg.SerializeToString(&s)) { throw Error("failed to serialize protobuf message"); } // Set value with the serialized message. assert(redis.command("PB.SET", "key", "Msg", s) == 1); // Get the message in binary format. s = redis.command("PB.GET", "key", "--FORMAT", "BINARY", "Msg"); // Create a new message with the binary string. Msg msg_new; if (!msg_new.ParseFromString(s)) { throw Error("failed to parse string to protobuf message"); } // Set and get a message field. assert(redis.command("PB.SET", "key", "Msg", "/i", 10) == 1); assert(redis.command("PB.GET", "key", "Msg", "/i") == 10); // Set and get a nested message field. assert(redis.command("PB.SET", "key", "Msg", "/sub/s", "redis-protobuf") == 1); assert(redis.command("PB.GET", "key", "Msg", "/sub/s") == "redis-protobuf"); // Delete the message. assert(redis.command("PB.DEL", "key", "Msg") == 1); } catch (const Error &e) { // Error handling } ``` ### Python Client If you are using Python, you can use [redis-py](https://github.com/andymccurdy/redis-py) to send *redis-protobuf* commands: ``` >>> import redis >>> r = redis.StrictRedis(host='localhost', port=6379, db=0) >>> r.execute_command('PB.SET', 'key', 'Msg', '{"i" : 1, "sub" : {"s" : "string", "i" : 2}, "arr" : [1, 2, 3]}') 1 >>> r.execute_command('PB.GET', 'key', '--FORMAT', 'BINARY', 'Msg') b'\x08\x01\x12\n\n\x06string\x10\x02\x1a\x03\x01\x02\x03' >>> r.execute_command('PB.GET', 'key', '--FORMAT', 'JSON', 'Msg') b'{"i":1,"sub":{"s":"string","i":2},"arr":[1,2,3]}' >>> r.execute_command('PB.SET', 'key', 'Msg', '/i', 2) 1 >>> r.execute_command('PB.GET', 'key', 'Msg', '/i') 2 >>> r.execute_command('PB.SET', 'key', 'Msg', '/sub/s', 'redis-protobuf') 1 >>> r.execute_command('PB.GET', 'key', 'Msg', '/sub/s') b'redis-protobuf' >>> r.execute_command('PB.SET', 'key', 'Msg', '/arr/0', 100) 1 >>> r.execute_command('PB.GET', 'key', 'Msg', '/arr/0') 100 ``` ## Commands ### Type and Path Most commands have *type* and *path* as arguments, which specifies the message type and field. We use *JSON Pointer*: [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) to specify the *path*. I'll use the following *.proto* file as an example to show you how to specify *type* and *path*. **NOTE**: *path* IS CASE SENSITIVE. ``` syntax = "proto3"; package redis.pb; message SubMsg { string s = 1; } message Msg { int32 i = 1; SubMsg sub = 2; repeated string str_arr = 3; repeated SubMsg msg_arr = 4; map m = 5; }; ``` #### Type *Type* is the type name of a protobuf message. If you specify package name in the *.proto* definition, e.g. `package redis.pb`, you must also specify the pacakge name as a part of the type name, separated with a dot, i.e. '.'. For example, we can use the following to specify the type the message: ``` redis.pb.Msg ``` However, if you don't specify package name in the *.proto* definition, you can use the message type directly without any prefix. ### Path With JSON Pointer, you can use `/` to address the field (and nested field) of a message. ``` /i /sub /sub/s ``` If the field is an array, you can use array index (seperated by '/'), i.e. `/i`, to specify the ith element. If the element is of message type, you can use '/' to specify the field of the element: ``` /str_arr/2 /msg_arr/1/s ``` The index is 0-based, and if the index is out-of-range, *redis-protobuf* will reply with an error. If the field is a map, you can use a key (seperated by '/'), i.e. `/key`, to specify the corresponding value. If the value of message type, again, you can use a '/' to specify the field of the value: ``` /m/key /m/key/s ``` ### PB.SET #### Syntax ``` PB.SET key [--NX | --XX] [--EX seconds | --PX milliseconds] type [path] value ``` - If the *path* is omitted, set the whole message with the given *value*. - Otherwise, set the corresponding field with the given *value*. **NOTE**: Since Protobuf fields are optional, when setting the whole message, you can only set parts of fields of this message. The following example only sets `/i` field, and leave other fields unset. ``` 127.0.0.1:6379> PB.SET key Msg '{"i" : 1}' (integer) 1 ``` Protobuf message has pre-defined schema, so the *value* should match the type of the corresponding field. - If the field is of integer type, i.e. `int32`, `int64`, `uint32`, `uint64`, `sint32`, `sint64`, `fixed32`, `fixed64`, `sfixed32`, `sfixed64`, the *value* string should be converted to the corresponding integer. - If the field is of floating-point type, i.e. `float`, `double`, the *value* string should be converted to the corresponding floating-point number. - If the field is of boolean type, i.e. `bool`, the *value* string should be *true* or *false*, or an integer (`0` is `false`, none `0` is `true`). - If the field is of string type, i.e. `string`, `byte`, the *value* string can be any binary string. - If the field is of enum type, i.e. `enum`, the *value* string should be converted to an integer. - If the field is of message type, the *value* string should be a binary string that serialized from the corresponding Protobuf message, or a JSON string that can be converted to the corresponding Protobuf message. #### Options - **--NX**: Only set the key if it doesn't exist. - **--XX**: Only set the key if it already exists. - **--EX seconds**: Set the key with the specified expiration in seconds. - **--PX milliseconds**: Set the key with the specified expiration in milliseconds. #### Return Value Integer reply: 1 if set successfully. 0, otherwise, e.g. option *--NX* has been set, while the key already exists. #### Error Return an error reply in the following cases: - The field specified by *path*, doesn't exist. - *type* doesn't match the type of the message saved in *key*, i.e. try to overwrite a *key*, in which the message is of a different type. See the examples part for an example. - *value* doesn't match the type of the field. #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.SET key Msg '{"i" : 1, "sub" : {"s" : "string", "i" : 2}, "arr" : [1, 2, 3]}' (integer) 1 127.0.0.1:6379> PB.SET key Msg /i 10 (integer) 1 127.0.0.1:6379> PB.SET key Msg /sub/s redis-protobuf (integer) 1 127.0.0.1:6379> PB.SET key Msg /arr/0 2 (integer) 1 127.0.0.1:6379> PB.SET key SubMsg '{"s" : "hello"}' (error) ERR type mismatch ``` ### PB.GET #### Syntax ``` PB.GET key [--FORMAT BINARY|JSON] type [path] ``` - If *path* is omitted, return the whole message in *key*. - Otherwise, return the value of that field. #### Options - **--FORMAT**: If the field at *path* is of message type, this option specifies the format of the return value. If the field is of other types, this option is ignored. - **BINARY**: return the value as a binary string by serializing the Protobuf message. - **JSON**: return the value as a JSON string by converting the Protobuf message to JSON. #### Return Value Reply type is depends on the type of the field at *path*. - Integer reply: if the field is of integer or enum type. - Bulk string reply: if the field is of string or message type. - Simple string reply: if the field is of boolean or floating-point type. - Array reply: if the field is repeated or map type. - Nil reply: if *key* doesn't exist. #### Error Return an error reply in the following cases: - If the field specified by *path*, doesn't exist. - If the specified type doesn't match the type of the message saved in *key*. #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.GET key --FORMAT BINARY Msg "\b\n\x12\x12\n\x0eredis-protobuf\x10\x02\x1a\x03\x02\x02\x03" 127.0.0.1:6379> PB.GET key --FORMAT JSON Msg "{\"i\":10,\"sub\":{\"s\":\"redis-protobuf\",\"i\":2},\"arr\":[2,2,3]}" 127.0.0.1:6379> PB.GET key Msg /i (integer) 10 127.0.0.1:6379> PB.GET key --FORMAT JSON Msg /sub "{\"s\":\"redis-protobuf\",\"i\":2}" 127.0.0.1:6379> PB.GET key Msg /arr/0 (integer) 2 127.0.0.1:6379> PB.GET key Msg /arr 1) (integer) 2 2) (integer) 2 3) (integer) 3 ``` ### PB.DEL #### Syntax ``` PB.DEL key type [path] ``` - If *path* specifies an array element, e.g. `/arr/0`, delete the corresponding element from the array. - If *path* specifies a map value, e.g. `/m/key`, delete the corresponding key-value pair from the map. - If *path* is omitted, delete the key. #### Return Value Integer reply: 1 if *key* exists, 0 othewise. #### Error Return an error reply in the following cases: - The field specified by *path*, doesn't exist - The field is not an array element or a map value. - The specifies *type* doesn't match the type of the message saved in *key*. #### Time Complexity - Delete array element: O(N), and N is the length of the array. - Delete map element: O(1) - Delete message: O(1) #### Examples ``` 127.0.0.1:6379> PB.DEL key Msg /arr/0 (integer) 1 127.0.0.1:6379> PB.DEL key Msg (integer) 1 ``` ### PB.APPEND #### Syntax ``` PB.APPEND key type path element [element, element...] ``` - If the field at *path* is a string, append *value* string to the field. - If the field at *path* is an array, append the *value* as an element to the array. If *key* doesn't exist, create an empty message, and do the append operation to the new message. #### Return Value Integer reply: The length of the string or the size of the array after the append operation. #### Error Return an error reply in the following cases: - The field specified by *path*, doesn't exist. - The field at *path* is not a string or array. #### Time Complexity Amortized O(1) #### Examples ``` 127.0.0.1:6379> pb.append key Msg /sub/s WithTail (integer) 14 127.0.0.1:6379> pb.append key Msg /arr 4 (integer) 4 ``` ### PB.LEN #### Syntax ``` PB.LEN key type [path] ``` - If the field at *path* is a string, return the length of the string. - If the field at *path* is an array, return the size of the array. - If the field at *path* is a map, return the size of the map. - If the field at *path* is a message, return the length of the serialized binary string of the message. - If *path* is omitted, return the length of the serialized binary string of whole message. #### Return Value Integer reply: 0 if *key* doesn't exist. Otherwise, the length of the string/array/map/message. #### Error Return an error reply in the following cases: - *path* doesn't exist. - The field at *path* is not a string/array/map/message. #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.LEN key Msg (integer) 28 127.0.0.1:6379> PB.LEN key Msg /sub/s (integer) 14 127.0.0.1:6379> PB.LEN key Msg /arr (integer) 4 ``` ### PB.CLEAR #### Syntax ``` PB.CLEAR key type [path] ``` - If *path* specifies a message type, clear the message in *key*. - If *path* specifies a field, clear the field. - If *path* is omitted, clear the whole message, NOT delete! Please check the Protubuf doc for the definition of **clear**. #### Return Value Integer reply: 1 if the *key* exists, 0 otherwise. #### Error Return an error reply in the following cases: - The specified *type* doesn't match the type of the message saved in *key*. - *path* doesn't exist. #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.CLEAR key Msg (integer) 1 127.0.0.1:6379> PB.CLEAR key Msg /arr (integer) 1 127.0.0.1:6379> PB.CLEAR non-exist-key Msg (integer) 0 ``` ### PB.MERGE #### Syntax ``` PB.MERGE key type [path] value ``` - If *path* specifies a field, merge the *value* into the field. - If *key* doesn't exist, this command behaves as *PB.SET*. - If *path* is omitted, parse *value* to a message, and merge it into the message in *key*. Please check the Protubuf doc for the definition of **merge**. #### Return Value Integer reply: 1 if the *key* exists, 0 otherwise. #### Error Return an error reply in the following cases: - The specified *type*, doesn't match the type of the message saved in *key*. - *path* doesn't exist. #### Time Complexity O(1) #### Examples ``` ``` ### PB.TYPE #### Syntax ``` PB.TYPE key ``` Get the message type of message in *key*. #### Return Value - Simple string reply: The Protobuf message type, if *key* exists. - Nil reply: If *key* doesn't exist. #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.TYPE key Msg ``` ### PB.SCHEMA #### Syntax ``` PB.SCHEMA type ``` Get the schema of the given Protobuf message *type*. #### Return Value - Bulk string reply: The schema of the given *type*. - Nil reply: If the *type* doesn't exist. #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.SCHEMA Msg "message Msg {\n int32 i = 1;\n SubMsg sub = 2;\n repeated int32 arr = 3;\n}\n" ``` ### PB.IMPORT #### Syntax ``` PB.IMPORT filename content ``` Import a protobuf file asynchronously. If the file has been imported successfully, the file will be persisted in `proto-directory`. **NOTE**: - Since this command runs asynchronously, you need to use [PB.LASTIMPORT](#pblastimport) to check the importing result. - If a file has already been imported, you cannot re-import it with this command, i.e. you cannot update an already imported proto file. Instead, you need to update the file on disk, and restart Redis server. #### Return Value - Simple string reply: "OK" #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.IMPORT test.proto 'syntax="proto3"; message M { int32 i = 1; }' OK ``` ### PB.LASTIMPORT #### Syntax ``` PB.LASTIMPORT ``` Get the importing result of all imported proto files since last call to this command. Since `PB.IMPORT` runs asynchronously, the importing result will be recorded. When `PB.LASTIMPORT` is called, all these records will be returned to client, and these records will be cleared on the server side. So if you call `PB.LASTIMPORT` twice, without calling `PB.IMPORT` between these two calls of `PB.LASTIMPORT`, the second calls will return empty array reply. #### Return Value - Array reply: Status of last imported protobuf files. For each file, if it's imported successfully, the status is "OK". Otherwise, the status is an error message. #### Time Complexity O(1) #### Examples ``` 127.0.0.1:6379> PB.LASTIMPORT 1) 1) "msg1.proto" 2) "ERR failed to load msg1.proto\nerror:... 2) 1) "msg2.proto" 2) "OK" ``` ## Author *redis-protobuf* is written by [sewenew](https://github.com/sewenew), who is also active on [StackOverflow](https://stackoverflow.com/users/5384363/for-stack). ================================================ FILE: docker/Dockerfile ================================================ From redis:latest ENV LIBDIR /usr/lib/redis/modules ENV DEPS "make g++ curl cmake unzip" # Install dependencies RUN set -ex;\ deps="$DEPS";\ apt-get update;\ apt-get install -y --no-install-recommends $deps; # Install protobuf RUN set -ex;\ mkdir -p /usr/src;\ cd /usr/src;\ curl -L -k https://github.com/sewenew/redis-protobuf/releases/download/0.0.1/protobuf-3.8.0-map-reflection.tar.gz -o protobuf-3.8.0-map-reflection.tar.gz;\ tar xfz protobuf-3.8.0-map-reflection.tar.gz;\ cd protobuf-3.8.0-map-reflection;\ ./configure "CFLAGS=-fPIC" "CXXFLAGS=-fPIC" --prefix=/usr;\ make -j 4;\ make install; # Build redis-protobuf RUN set -ex;\ cd /usr/src;\ curl -L -k 'https://github.com/sewenew/redis-protobuf/archive/master.zip' -o redis-protobuf.zip;\ unzip redis-protobuf.zip;\ cd redis-protobuf-master;\ mkdir compile;\ cd compile;\ cmake -DCMAKE_BUILD_TYPE=Release ..;\ make; # Load redis-protobuf ENV REDISDIR /usr/lib/redis RUN set -ex;\ mkdir -p "$REDISDIR/proto/google/protobuf" "$REDISDIR/proto/google/protobuf/util" "$REDISDIR/proto/google/protobuf/compiler" "$REDISDIR/modules" "$REDISDIR/conf";\ cp /usr/src/redis-protobuf-master/docker/example.proto "$REDISDIR/proto";\ cp /usr/src/protobuf-3.8.0-map-reflection/src/google/protobuf/*.proto "$REDISDIR/proto/google/protobuf";\ cp /usr/src/redis-protobuf-master/compile/libredis-protobuf.so "$REDISDIR/modules";\ echo 'loadmodule /usr/lib/redis/modules/libredis-protobuf.so --dir /usr/lib/redis/proto' > "$REDISDIR/conf/redis.conf"; # Cleanup RUN set -ex;\ deps="$DEPS";\ apt-get purge -y --auto-remove $deps;\ rm -rf /usr/src/*; CMD ["redis-server", "/usr/lib/redis/conf/redis.conf"] ================================================ FILE: docker/example.proto ================================================ syntax = "proto3"; //package sw.redis.pb; message SubMsg { string s = 1; int32 i = 2; } message Msg { int32 i = 1; SubMsg sub = 2; repeated int32 arr = 3; map m = 4; } ================================================ FILE: docker/google/protobuf/any.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 Google Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT // OWNER OR 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. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "github.com/golang/protobuf/ptypes/any"; option java_package = "com.google.protobuf"; option java_outer_classname = "AnyProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; // `Any` contains an arbitrary serialized protocol buffer message along with a // URL that describes the type of the serialized message. // // Protobuf library provides support to pack/unpack Any values in the form // of utility functions or additional generated methods of the Any type. // // Example 1: Pack and unpack a message in C++. // // Foo foo = ...; // Any any; // any.PackFrom(foo); // ... // if (any.UnpackTo(&foo)) { // ... // } // // Example 2: Pack and unpack a message in Java. // // Foo foo = ...; // Any any = Any.pack(foo); // ... // if (any.is(Foo.class)) { // foo = any.unpack(Foo.class); // } // // Example 3: Pack and unpack a message in Python. // // foo = Foo(...) // any = Any() // any.Pack(foo) // ... // if any.Is(Foo.DESCRIPTOR): // any.Unpack(foo) // ... // // Example 4: Pack and unpack a message in Go // // foo := &pb.Foo{...} // any, err := ptypes.MarshalAny(foo) // ... // foo := &pb.Foo{} // if err := ptypes.UnmarshalAny(any, foo); err != nil { // ... // } // // The pack methods provided by protobuf library will by default use // 'type.googleapis.com/full.type.name' as the type URL and the unpack // methods only use the fully qualified type name after the last '/' // in the type URL, for example "foo.bar.com/x/y.z" will yield type // name "y.z". // // // JSON // ==== // The JSON representation of an `Any` value uses the regular // representation of the deserialized, embedded message, with an // additional field `@type` which contains the type URL. Example: // // package google.profile; // message Person { // string first_name = 1; // string last_name = 2; // } // // { // "@type": "type.googleapis.com/google.profile.Person", // "firstName": , // "lastName": // } // // If the embedded message type is well-known and has a custom JSON // representation, that representation will be embedded adding a field // `value` which holds the custom JSON in addition to the `@type` // field. Example (for message [google.protobuf.Duration][]): // // { // "@type": "type.googleapis.com/google.protobuf.Duration", // "value": "1.212s" // } // message Any { // A URL/resource name that uniquely identifies the type of the serialized // protocol buffer message. This string must contain at least // one "/" character. The last segment of the URL's path must represent // the fully qualified name of the type (as in // `path/google.protobuf.Duration`). The name should be in a canonical form // (e.g., leading "." is not accepted). // // In practice, teams usually precompile into the binary all types that they // expect it to use in the context of Any. However, for URLs which use the // scheme `http`, `https`, or no scheme, one can optionally set up a type // server that maps type URLs to message definitions as follows: // // * If no scheme is provided, `https` is assumed. // * An HTTP GET on the URL must yield a [google.protobuf.Type][] // value in binary format, or produce an error. // * Applications are allowed to cache lookup results based on the // URL, or have them precompiled into a binary to avoid any // lookup. Therefore, binary compatibility needs to be preserved // on changes to types. (Use versioned type names to manage // breaking changes.) // // Note: this functionality is not currently available in the official // protobuf release, and it is not used for type URLs beginning with // type.googleapis.com. // // Schemes other than `http`, `https` (or the empty scheme) might be // used with implementation specific semantics. // string type_url = 1; // Must be a valid serialized protocol buffer of the above specified type. bytes value = 2; } ================================================ FILE: docker/google/protobuf/duration.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 Google Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT // OWNER OR 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. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option cc_enable_arenas = true; option go_package = "github.com/golang/protobuf/ptypes/duration"; option java_package = "com.google.protobuf"; option java_outer_classname = "DurationProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; // A Duration represents a signed, fixed-length span of time represented // as a count of seconds and fractions of seconds at nanosecond // resolution. It is independent of any calendar and concepts like "day" // or "month". It is related to Timestamp in that the difference between // two Timestamp values is a Duration and it can be added or subtracted // from a Timestamp. Range is approximately +-10,000 years. // // # Examples // // Example 1: Compute Duration from two Timestamps in pseudo code. // // Timestamp start = ...; // Timestamp end = ...; // Duration duration = ...; // // duration.seconds = end.seconds - start.seconds; // duration.nanos = end.nanos - start.nanos; // // if (duration.seconds < 0 && duration.nanos > 0) { // duration.seconds += 1; // duration.nanos -= 1000000000; // } else if (durations.seconds > 0 && duration.nanos < 0) { // duration.seconds -= 1; // duration.nanos += 1000000000; // } // // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. // // Timestamp start = ...; // Duration duration = ...; // Timestamp end = ...; // // end.seconds = start.seconds + duration.seconds; // end.nanos = start.nanos + duration.nanos; // // if (end.nanos < 0) { // end.seconds -= 1; // end.nanos += 1000000000; // } else if (end.nanos >= 1000000000) { // end.seconds += 1; // end.nanos -= 1000000000; // } // // Example 3: Compute Duration from datetime.timedelta in Python. // // td = datetime.timedelta(days=3, minutes=10) // duration = Duration() // duration.FromTimedelta(td) // // # JSON Mapping // // In JSON format, the Duration type is encoded as a string rather than an // object, where the string ends in the suffix "s" (indicating seconds) and // is preceded by the number of seconds, with nanoseconds expressed as // fractional seconds. For example, 3 seconds with 0 nanoseconds should be // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 // microsecond should be expressed in JSON format as "3.000001s". // // message Duration { // Signed seconds of the span of time. Must be from -315,576,000,000 // to +315,576,000,000 inclusive. Note: these bounds are computed from: // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years int64 seconds = 1; // Signed fractions of a second at nanosecond resolution of the span // of time. Durations less than one second are represented with a 0 // `seconds` field and a positive or negative `nanos` field. For durations // of one second or more, a non-zero value for the `nanos` field must be // of the same sign as the `seconds` field. Must be from -999,999,999 // to +999,999,999 inclusive. int32 nanos = 2; } ================================================ FILE: docker/google/protobuf/empty.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 Google Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT // OWNER OR 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. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "github.com/golang/protobuf/ptypes/empty"; option java_package = "com.google.protobuf"; option java_outer_classname = "EmptyProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option cc_enable_arenas = true; // A generic empty message that you can re-use to avoid defining duplicated // empty messages in your APIs. A typical example is to use it as the request // or the response type of an API method. For instance: // // service Foo { // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); // } // // The JSON representation for `Empty` is empty JSON object `{}`. message Empty {} ================================================ FILE: docker/google/protobuf/struct.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 Google Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT // OWNER OR 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. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option cc_enable_arenas = true; option go_package = "github.com/golang/protobuf/ptypes/struct;structpb"; option java_package = "com.google.protobuf"; option java_outer_classname = "StructProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; // `Struct` represents a structured data value, consisting of fields // which map to dynamically typed values. In some languages, `Struct` // might be supported by a native representation. For example, in // scripting languages like JS a struct is represented as an // object. The details of that representation are described together // with the proto support for the language. // // The JSON representation for `Struct` is JSON object. message Struct { // Unordered map of dynamically typed values. map fields = 1; } // `Value` represents a dynamically typed value which can be either // null, a number, a string, a boolean, a recursive struct value, or a // list of values. A producer of value is expected to set one of that // variants, absence of any variant indicates an error. // // The JSON representation for `Value` is JSON value. message Value { // The kind of value. oneof kind { // Represents a null value. NullValue null_value = 1; // Represents a double value. double number_value = 2; // Represents a string value. string string_value = 3; // Represents a boolean value. bool bool_value = 4; // Represents a structured value. Struct struct_value = 5; // Represents a repeated `Value`. ListValue list_value = 6; } } // `NullValue` is a singleton enumeration to represent the null value for the // `Value` type union. // // The JSON representation for `NullValue` is JSON `null`. enum NullValue { // Null value. NULL_VALUE = 0; } // `ListValue` is a wrapper around a repeated field of values. // // The JSON representation for `ListValue` is JSON array. message ListValue { // Repeated field of dynamically typed values. repeated Value values = 1; } ================================================ FILE: docker/google/protobuf/timestamp.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 Google Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT // OWNER OR 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. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option cc_enable_arenas = true; option go_package = "github.com/golang/protobuf/ptypes/timestamp"; option java_package = "com.google.protobuf"; option java_outer_classname = "TimestampProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; // A Timestamp represents a point in time independent of any time zone or local // calendar, encoded as a count of seconds and fractions of seconds at // nanosecond resolution. The count is relative to an epoch at UTC midnight on // January 1, 1970, in the proleptic Gregorian calendar which extends the // Gregorian calendar backwards to year one. // // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap // second table is needed for interpretation, using a [24-hour linear // smear](https://developers.google.com/time/smear). // // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By // restricting to that range, we ensure that we can convert to and from [RFC // 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. // // # Examples // // Example 1: Compute Timestamp from POSIX `time()`. // // Timestamp timestamp; // timestamp.set_seconds(time(NULL)); // timestamp.set_nanos(0); // // Example 2: Compute Timestamp from POSIX `gettimeofday()`. // // struct timeval tv; // gettimeofday(&tv, NULL); // // Timestamp timestamp; // timestamp.set_seconds(tv.tv_sec); // timestamp.set_nanos(tv.tv_usec * 1000); // // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. // // FILETIME ft; // GetSystemTimeAsFileTime(&ft); // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; // // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. // Timestamp timestamp; // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); // // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. // // long millis = System.currentTimeMillis(); // // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) // .setNanos((int) ((millis % 1000) * 1000000)).build(); // // // Example 5: Compute Timestamp from current time in Python. // // timestamp = Timestamp() // timestamp.GetCurrentTime() // // # JSON Mapping // // In JSON format, the Timestamp type is encoded as a string in the // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" // where {year} is always expressed using four digits while {month}, {day}, // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone // is required. A proto3 JSON serializer should always use UTC (as indicated by // "Z") when printing the Timestamp type and a proto3 JSON parser should be // able to accept both UTC and other timezones (as indicated by an offset). // // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past // 01:30 UTC on January 15, 2017. // // In JavaScript, one can convert a Date object to this format using the // standard // [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) // method. In Python, a standard `datetime.datetime` object can be converted // to this format using // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use // the Joda Time's [`ISODateTimeFormat.dateTime()`]( // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D // ) to obtain a formatter capable of generating timestamps in this format. // // message Timestamp { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. int64 seconds = 1; // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. int32 nanos = 2; } ================================================ FILE: docker/google/protobuf/wrappers.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 Google Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT // OWNER OR 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. // Wrappers for primitive (non-message) types. These types are useful // for embedding primitives in the `google.protobuf.Any` type and for places // where we need to distinguish between the absence of a primitive // typed field and its default value. // // These wrappers have no meaningful use within repeated fields as they lack // the ability to detect presence on individual elements. // These wrappers have no meaningful use within a map or a oneof since // individual entries of a map or fields of a oneof can already detect presence. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option cc_enable_arenas = true; option go_package = "github.com/golang/protobuf/ptypes/wrappers"; option java_package = "com.google.protobuf"; option java_outer_classname = "WrappersProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; // Wrapper message for `double`. // // The JSON representation for `DoubleValue` is JSON number. message DoubleValue { // The double value. double value = 1; } // Wrapper message for `float`. // // The JSON representation for `FloatValue` is JSON number. message FloatValue { // The float value. float value = 1; } // Wrapper message for `int64`. // // The JSON representation for `Int64Value` is JSON string. message Int64Value { // The int64 value. int64 value = 1; } // Wrapper message for `uint64`. // // The JSON representation for `UInt64Value` is JSON string. message UInt64Value { // The uint64 value. uint64 value = 1; } // Wrapper message for `int32`. // // The JSON representation for `Int32Value` is JSON number. message Int32Value { // The int32 value. int32 value = 1; } // Wrapper message for `uint32`. // // The JSON representation for `UInt32Value` is JSON number. message UInt32Value { // The uint32 value. uint32 value = 1; } // Wrapper message for `bool`. // // The JSON representation for `BoolValue` is JSON `true` and `false`. message BoolValue { // The bool value. bool value = 1; } // Wrapper message for `string`. // // The JSON representation for `StringValue` is JSON string. message StringValue { // The string value. string value = 1; } // Wrapper message for `bytes`. // // The JSON representation for `BytesValue` is JSON string. message BytesValue { // The bytes value. bytes value = 1; } ================================================ FILE: src/sw/redis-protobuf/append_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "append_command.h" #include "errors.h" #include "redis_protobuf.h" namespace sw { namespace redis { namespace pb { int AppendCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); const auto &path = args.path; if (path.empty()) { throw Error("can only call append on array"); } auto key = api::open_key(ctx, args.key_name, api::KeyMode::WRITEONLY); assert(key); auto &m = RedisProtobuf::instance(); long long len = 0; if (!api::key_exists(key.get(), m.type())) { auto msg = m.proto_factory()->create(path.type()); assert(msg != nullptr); if (msg->GetTypeName() != path.type()) { throw Error("type mismatch"); } MutableFieldRef field(msg.get(), path); len = _append(field, args.elements); if (RedisModule_ModuleTypeSetValue(key.get(), m.type(), msg.get()) != REDISMODULE_OK) { throw Error("failed to set message"); } msg.release(); } else { auto *msg = api::get_msg_by_key(key.get()); assert(msg != nullptr); MutableFieldRef field(msg, path); // TODO: create a new message, and append to that message, then swap to this message. len = _append(field, args.elements); } RedisModule_ReplyWithLongLong(ctx, len); RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } AppendCommand::Args AppendCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc < 5) { throw WrongArityError(); } Args args; args.key_name = argv[1]; args.path = Path(argv[2], argv[3]); args.elements.reserve(argc - 4); for (auto idx = 4; idx != argc; ++idx) { args.elements.emplace_back(argv[idx]); } return args; } long long AppendCommand::_append(MutableFieldRef &field, const std::vector &elements) const { if (field.is_array() && !field.is_array_element()) { for (const auto &ele : elements) { _append_arr(field, ele); } return field.size(); } else if (field.type() == gp::FieldDescriptor::CPPTYPE_STRING) { return _append_str(field, elements); } else { throw Error("not an array or string"); } } void AppendCommand::_append_arr(MutableFieldRef &field, const StringView &val) const { assert(field.is_array() && !field.is_array_element()); switch (field.type()) { case gp::FieldDescriptor::CPPTYPE_INT32: field.add_int32(util::sv_to_int32(val)); break; case gp::FieldDescriptor::CPPTYPE_INT64: field.add_int64(util::sv_to_int64(val)); break; case gp::FieldDescriptor::CPPTYPE_UINT32: field.add_uint32(util::sv_to_uint32(val)); break; case gp::FieldDescriptor::CPPTYPE_UINT64: field.add_uint64(util::sv_to_uint64(val)); break; case gp::FieldDescriptor::CPPTYPE_DOUBLE: field.add_double(util::sv_to_double(val)); break; case gp::FieldDescriptor::CPPTYPE_FLOAT: field.add_float(util::sv_to_float(val)); break; case gp::FieldDescriptor::CPPTYPE_BOOL: field.add_bool(util::sv_to_bool(val)); break; case gp::FieldDescriptor::CPPTYPE_ENUM: field.add_enum(util::sv_to_int32(val)); break; case gp::FieldDescriptor::CPPTYPE_STRING: field.add_string(util::sv_to_string(val)); break; case gp::FieldDescriptor::CPPTYPE_MESSAGE: _add_msg(field, val); break; default: throw Error("unknown type"); } } long long AppendCommand::_append_str(MutableFieldRef &field, const std::vector &elements) const { std::string str; for (const auto &ele : elements) { str += std::string(ele.data(), ele.size()); } if (field.is_array_element()) { str = field.get_repeated_string() + str; field.set_repeated_string(str); } else { // TODO: map element str = field.get_string() + str; field.set_string(str); } return str.size(); } void AppendCommand::_add_msg(MutableFieldRef &field, const StringView &val) const { auto msg = RedisProtobuf::instance().proto_factory()->create(field.msg_type(), val); assert(msg); field.add_msg(*msg); } } } } ================================================ FILE: src/sw/redis-protobuf/append_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_APPEND_COMMANDS_H #define SEWENEW_REDISPROTOBUF_APPEND_COMMANDS_H #include "module_api.h" #include #include #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.APPEND key type path element [element, element...] // return: Integer reply: return the length of the array after the append operations. // Or return the length of the string after the append operations. // error: If the path doesn't exist, or the corresponding field is not an array, or // a string, return an error reply. class AppendCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; Path path; std::vector elements; }; Args _parse_args(RedisModuleString **argv, int argc) const; long long _append(MutableFieldRef &field, const std::vector &elements) const; void _append_arr(MutableFieldRef &field, const StringView &val) const; long long _append_str(MutableFieldRef &field, const std::vector &elements) const; void _add_msg(MutableFieldRef &field, const StringView &val) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_APPEND_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/clear_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "clear_command.h" #include "errors.h" #include "redis_protobuf.h" namespace sw { namespace redis { namespace pb { int ClearCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); auto key = api::open_key(ctx, args.key_name, api::KeyMode::READONLY); if (!api::key_exists(key.get(), RedisProtobuf::instance().type())) { RedisModule_ReplyWithLongLong(ctx, 0); } else { auto *msg = api::get_msg_by_key(key.get()); assert(msg != nullptr); if (msg->GetTypeName() != args.path.type()) { throw Error("type mismatch"); } _clear(*msg, args.path); RedisModule_ReplyWithLongLong(ctx, 1); } RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } ClearCommand::Args ClearCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc != 3 && argc != 4) { throw WrongArityError(); } Path path; if (argc == 3) { path = Path(argv[2]); } else { path = Path(argv[2], argv[3]); } return {argv[1], std::move(path)}; } void ClearCommand::_clear(gp::Message &msg, const Path &path) const { if (path.empty()) { // Clear the message. msg.Clear(); } else { // Clear a field. MutableFieldRef field(&msg, path); field.clear(); } } } } } ================================================ FILE: src/sw/redis-protobuf/clear_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_CLEAR_COMMANDS_H #define SEWENEW_REDISPROTOBUF_CLEAR_COMMANDS_H #include "module_api.h" #include #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.CLEAR key type [path] // return: Integer reply: If the key exist, return 1. Otherwise, return 0. // error: If the type doesn't match the protobuf message type of the key, // or the path doesn't exist, return an error reply. class ClearCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; Path path; }; Args _parse_args(RedisModuleString **argv, int argc) const; void _clear(gp::Message &msg, const Path &path) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_CLEAR_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/commands.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "commands.h" #include "set_command.h" #include "get_command.h" #include "type_command.h" #include "clear_command.h" #include "len_command.h" #include "append_command.h" #include "del_command.h" #include "schema_command.h" #include "merge_command.h" #include "import_command.h" #include "last_import_command.h" namespace sw { namespace redis { namespace pb { namespace cmd { void create_commands(RedisModuleCtx *ctx) { if (RedisModule_CreateCommand(ctx, "PB.TYPE", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { TypeCommand cmd; return cmd.run(ctx, argv, argc); }, "readonly", 1, 1, 1) == REDISMODULE_ERR) { throw Error("failed to create PB.TYPE command"); } if (RedisModule_CreateCommand(ctx, "PB.SET", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { SetCommand cmd; return cmd.run(ctx, argv, argc); }, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) { throw Error("fail to create PB.SET command"); } if (RedisModule_CreateCommand(ctx, "PB.GET", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { GetCommand cmd; return cmd.run(ctx, argv, argc); }, "readonly", 1, 1, 1) == REDISMODULE_ERR) { throw Error("failed to create PB.GET command"); } if (RedisModule_CreateCommand(ctx, "PB.CLEAR", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { ClearCommand cmd; return cmd.run(ctx, argv, argc); }, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) { throw Error("fail to create PB.CLEAR command"); } if (RedisModule_CreateCommand(ctx, "PB.LEN", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { LenCommand cmd; return cmd.run(ctx, argv, argc); }, "readonly", 1, 1, 1) == REDISMODULE_ERR) { throw Error("failed to create PB.LEN command"); } if (RedisModule_CreateCommand(ctx, "PB.APPEND", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { AppendCommand cmd; return cmd.run(ctx, argv, argc); }, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) { throw Error("fail to create PB.APPEND command"); } if (RedisModule_CreateCommand(ctx, "PB.DEL", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { DelCommand cmd; return cmd.run(ctx, argv, argc); }, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) { throw Error("fail to create PB.DEL command"); } if (RedisModule_CreateCommand(ctx, "PB.SCHEMA", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { SchemaCommand cmd; return cmd.run(ctx, argv, argc); }, "readonly getkeys-api", 1, 1, 1) == REDISMODULE_ERR) { throw Error("failed to create PB.SCHEMA command"); } if (RedisModule_CreateCommand(ctx, "PB.MERGE", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { MergeCommand cmd; return cmd.run(ctx, argv, argc); }, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) { throw Error("fail to create PB.MERGE command"); } if (RedisModule_CreateCommand(ctx, "PB.IMPORT", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { ImportCommand cmd; return cmd.run(ctx, argv, argc); }, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) { throw Error("fail to create PB.IMPORT command"); } if (RedisModule_CreateCommand(ctx, "PB.LASTIMPORT", [](RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { LastImportCommand cmd; return cmd.run(ctx, argv, argc); }, "readonly", 1, 1, 1) == REDISMODULE_ERR) { throw Error("failed to create PB.LASTIMPORT command"); } } } } } } ================================================ FILE: src/sw/redis-protobuf/commands.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_COMMANDS_H #define SEWENEW_REDISPROTOBUF_COMMANDS_H #include "module_api.h" namespace sw { namespace redis { namespace pb { namespace cmd { void create_commands(RedisModuleCtx *ctx); } } } } #endif // end SEWENEW_REDISPROTOBUF_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/del_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "del_command.h" #include "errors.h" #include "redis_protobuf.h" namespace sw { namespace redis { namespace pb { int DelCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); auto key = api::open_key(ctx, args.key_name, api::KeyMode::WRITEONLY); assert(key); if (!api::key_exists(key.get(), RedisProtobuf::instance().type())) { RedisModule_ReplyWithLongLong(ctx, 0); } else { auto *msg = api::get_msg_by_key(key.get()); assert(msg != nullptr); const auto &path = args.path; if (msg->GetTypeName() != path.type()) { throw Error("type mismatch"); } if (path.empty()) { // Delete key. RedisModule_DeleteKey(key.get()); } else { // Delete an item from array or map. _del(*msg, path); } RedisModule_ReplyWithLongLong(ctx, 1); } RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } DelCommand::Args DelCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc != 3 && argc != 4) { throw WrongArityError(); } Path path; if (argc == 3) { path = Path(argv[2]); } else { path = Path(argv[2], argv[3]); } return {argv[1], std::move(path)}; } void DelCommand::_del(gp::Message &msg, const Path &path) const { MutableFieldRef field(&msg, path); if (!field.is_array_element()) { // TODO: support map element throw Error("not an array or map"); } field.del(); } } } } ================================================ FILE: src/sw/redis-protobuf/del_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_DEL_COMMANDS_H #define SEWENEW_REDISPROTOBUF_DEL_COMMANDS_H #include "module_api.h" #include #include #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.DEL key type [path] // return: Integer reply: return 1, if the key exists. 0, otherwise. // error: If the path doesn't exist, or the corresponding field is not an array, // or map or the message itself, return an error reply. class DelCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; Path path; }; Args _parse_args(RedisModuleString **argv, int argc) const; void _del(gp::Message &msg, const Path &path) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_DEL_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/errors.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_ERRORS_H #define SEWENEW_REDISPROTOBUF_ERRORS_H #include #include namespace sw { namespace redis { namespace pb { 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() = default; virtual const char* what() const noexcept { return _msg.data(); } private: std::string _msg; }; class WrongTypeError : public Error { public: explicit WrongTypeError(const std::string &msg) : Error(msg) {} WrongTypeError(const WrongTypeError &) = default; WrongTypeError& operator=(const WrongTypeError &) = default; WrongTypeError(WrongTypeError &&) = default; WrongTypeError& operator=(WrongTypeError &&) = default; virtual ~WrongTypeError() = default; }; class WrongArityError : public Error { public: WrongArityError() : Error("WrongArity") {} WrongArityError(const WrongArityError &) = default; WrongArityError& operator=(const WrongArityError &) = default; WrongArityError(WrongArityError &&) = default; WrongArityError& operator=(WrongArityError &&) = default; virtual ~WrongArityError() = default; }; class MapKeyNotFoundError : public Error { public: explicit MapKeyNotFoundError(const std::string &key) : Error("key not found: " + key) {} MapKeyNotFoundError(const MapKeyNotFoundError &) = default; MapKeyNotFoundError& operator=(const MapKeyNotFoundError &) = default; MapKeyNotFoundError(MapKeyNotFoundError &&) = default; MapKeyNotFoundError& operator=(MapKeyNotFoundError &&) = default; virtual ~MapKeyNotFoundError() = default; }; } } } #endif // end SEWENEW_REDISPROTOBUF_ERRORS_H ================================================ FILE: src/sw/redis-protobuf/field_ref.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_FIELD_REF_H #define SEWENEW_REDISPROTOBUF_FIELD_REF_H #include #include #include #include #include #include #include #include "module_api.h" #include "utils.h" #include "path.h" namespace sw { namespace redis { namespace pb { namespace gp = google::protobuf; template class FieldRef { public: FieldRef(Msg *root_msg, const Path &path); gp::FieldDescriptor::CppType type() const { if (_field_desc == nullptr) { throw Error("invalid path: null field"); } return _field_desc->cpp_type(); } gp::FieldDescriptor::CppType map_value_type() const { const auto *val_desc = _mapped_value_desc(); return val_desc->cpp_type(); } std::string msg_type() const; std::string mapped_msg_type() const; bool is_array() const { return _field_desc != nullptr && _field_desc->is_repeated(); } bool is_array_element() const { return _arr_idx >= 0; } bool is_map() const { return _field_desc != nullptr && _field_desc->is_map(); } bool is_map_element() const { return bool(_map_key); } int size() const; FieldRef get_array_element(int idx) const; auto get_map_range() const -> std::pair::const_iterator, gp::Map::const_iterator>; explicit operator bool() const { return _field_desc != nullptr; } int32_t get_int32() const { return _msg->GetReflection()->GetInt32(*_msg, _field_desc); } int64_t get_int64() const { return _msg->GetReflection()->GetInt64(*_msg, _field_desc); } uint32_t get_uint32() const { return _msg->GetReflection()->GetUInt32(*_msg, _field_desc); } uint64_t get_uint64() const { return _msg->GetReflection()->GetUInt64(*_msg, _field_desc); } float get_float() const { return _msg->GetReflection()->GetFloat(*_msg, _field_desc); } double get_double() const { return _msg->GetReflection()->GetDouble(*_msg, _field_desc); } bool get_bool() const { return _msg->GetReflection()->GetBool(*_msg, _field_desc); } int get_enum() const { return _msg->GetReflection()->GetEnumValue(*_msg, _field_desc); } std::string get_string() const { return _msg->GetReflection()->GetString(*_msg, _field_desc); } const gp::Message& get_msg() const { return _msg->GetReflection()->GetMessage(*_msg, _field_desc); } int32_t get_repeated_int32() const { return _msg->GetReflection()->GetRepeatedInt32(*_msg, _field_desc, _arr_idx); } int64_t get_repeated_int64() const { return _msg->GetReflection()->GetRepeatedInt64(*_msg, _field_desc, _arr_idx); } uint32_t get_repeated_uint32() const { return _msg->GetReflection()->GetRepeatedUInt32(*_msg, _field_desc, _arr_idx); } uint64_t get_repeated_uint64() const { return _msg->GetReflection()->GetRepeatedUInt64(*_msg, _field_desc, _arr_idx); } float get_repeated_float() const { return _msg->GetReflection()->GetRepeatedFloat(*_msg, _field_desc, _arr_idx); } double get_repeated_double() const { return _msg->GetReflection()->GetRepeatedDouble(*_msg, _field_desc, _arr_idx); } bool get_repeated_bool() const { return _msg->GetReflection()->GetRepeatedBool(*_msg, _field_desc, _arr_idx); } int get_repeated_enum() const { return _msg->GetReflection()->GetRepeatedEnumValue(*_msg, _field_desc, _arr_idx); } std::string get_repeated_string() const { return _msg->GetReflection()->GetRepeatedString(*_msg, _field_desc, _arr_idx); } const gp::Message& get_repeated_msg() const { return _msg->GetReflection()->GetRepeatedMessage(*_msg, _field_desc, _arr_idx); } int32_t get_mapped_int32() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetInt32Value(); } int64_t get_mapped_int64() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetInt64Value(); } uint32_t get_mapped_uint32() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetUInt32Value(); } uint64_t get_mapped_uint64() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetUInt64Value(); } float get_mapped_float() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetFloatValue(); } double get_mapped_double() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetDoubleValue(); } bool get_mapped_bool() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetBoolValue(); } int get_mapped_enum() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetEnumValue(); } std::string get_mapped_string() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetStringValue(); } const gp::Message& get_mapped_msg() const { const auto &val = _get_map_value_const(_msg, _field_desc, *_map_key); return val.GetMessageValue(); } void set_mapped_int32(int32_t val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetInt32Value(val); } void set_mapped_int64(int64_t val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetInt64Value(val); } void set_mapped_uint32(uint32_t val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetUInt32Value(val); } void set_mapped_uint64(uint64_t val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetUInt64Value(val); } void set_mapped_float(float val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetFloatValue(val); } void set_mapped_double(double val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetDoubleValue(val); } void set_mapped_bool(bool val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetBoolValue(val); } void set_mapped_enum(int val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetEnumValue(val); } void set_mapped_string(const std::string &val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); val_ref.SetStringValue(val); } void set_mapped_msg(const gp::Message &val) { auto &val_ref = _get_map_value(_msg, _field_desc, *_map_key); auto *msg = val_ref.MutableMessageValue(); msg->CopyFrom(val); } void set_int32(int32_t val); void set_int64(int64_t val); void set_uint32(uint32_t val); void set_uint64(uint64_t val); void set_float(float val); void set_double(double val); void set_bool(bool val); void set_enum(int val); void set_string(const std::string &val); void set_msg(gp::Message &msg); void set_repeated_int32(int32_t val); void set_repeated_int64(int64_t val); void set_repeated_uint32(uint32_t val); void set_repeated_uint64(uint64_t val); void set_repeated_float(float val); void set_repeated_double(double val); void set_repeated_bool(bool val); void set_repeated_enum(int val); void set_repeated_string(const std::string &val); void set_repeated_msg(gp::Message &msg); void add_int32(int32_t val); void add_int64(int64_t val); void add_uint32(uint32_t val); void add_uint64(uint64_t val); void add_float(float val); void add_double(double val); void add_bool(bool val); void add_enum(int val); void add_string(const std::string &val); void add_msg(gp::Message &msg); void clear(); void del(); void merge(const gp::Message &msg); private: Msg* _get_sub_msg(Msg *msg, const gp::FieldDescriptor *field_desc, std::true_type) { return &(msg->GetReflection()->GetMessage(*msg, field_desc)); } Msg* _get_sub_msg(Msg *msg, const gp::FieldDescriptor *field_desc, std::false_type) { return msg->GetReflection()->MutableMessage(msg, field_desc); } Msg* _get_sub_msg(Msg *msg, const gp::FieldDescriptor *field_desc) { assert(msg != nullptr); return _get_sub_msg(msg, field_desc, typename std::is_const::type()); } Msg* _get_sub_repeated_msg(Msg *msg, const gp::FieldDescriptor *field_desc, int idx, std::true_type) { return &(msg->GetReflection()->GetRepeatedMessage(*msg, field_desc, idx)); } Msg* _get_sub_repeated_msg(Msg *msg, const gp::FieldDescriptor *field_desc, int idx, std::false_type) { return msg->GetReflection()->MutableRepeatedMessage(msg, field_desc, idx); } Msg* _get_sub_repeated_msg(Msg *msg, const gp::FieldDescriptor *field_desc, int idx) { assert(msg != nullptr); return _get_sub_repeated_msg(msg, field_desc, idx, typename std::is_const::type()); } class NotFoundError : public Error { public: NotFoundError() : Error("key not found") {} }; const gp::MapValueRef& _get_map_value_const(Msg *msg, const gp::FieldDescriptor *field_desc, const gp::MapKey &key) const { // The following is hacking, hacking, and hacking!!! const auto *reflection = static_cast(msg->GetReflection()); const auto &map_base = reflection->GetRaw(*msg, field_desc); const auto &dynamic_map = static_cast(map_base); const auto &m = dynamic_map.GetMap(); auto iter = m.find(key); if (iter == m.end()) { throw NotFoundError(); } return iter->second; } gp::MapValueRef& _get_map_value(Msg *msg, const gp::FieldDescriptor *field_desc, const gp::MapKey &key) { // The following is hacking, hacking, and hacking!!! const auto *reflection = static_cast(msg->GetReflection()); auto *map_base = reflection->MutableRaw(msg, field_desc); auto *dynamic_map = static_cast(map_base); // Ensure the value is initialized. gp::MapValueRef val; dynamic_map->InsertOrLookupMapValue(key, &val); auto *m = dynamic_map->MutableMap(); auto iter = m->find(key); assert(iter != m->end()); return iter->second; } Msg* _get_map_msg(Msg *msg, const gp::FieldDescriptor *field_desc, const gp::MapKey &key) { return _get_map_msg(msg, field_desc, key, typename std::is_const::type()); } Msg* _get_map_msg(Msg *msg, const gp::FieldDescriptor *field_desc, const gp::MapKey &key, std::true_type) { const auto &val = _get_map_value_const(msg, field_desc, key); const auto *val_desc = _mapped_value_desc(); if (val_desc->cpp_type() != gp::FieldDescriptor::CPPTYPE_MESSAGE) { throw Error("map value is not of message type"); } return &val.GetMessageValue(); } Msg* _get_map_msg(Msg *msg, const gp::FieldDescriptor *field_desc, const gp::MapKey &key, std::false_type) { auto &val = _get_map_value(msg, field_desc, key); auto *val_desc = _mapped_value_desc(); if (val_desc->cpp_type() != gp::FieldDescriptor::CPPTYPE_MESSAGE) { throw Error("map value is not of message type"); } return val.MutableMessageValue(); } const gp::FieldDescriptor* _mapped_value_desc() const { assert(is_map()); auto *desc = _field_desc->message_type(); assert(desc != nullptr); auto *val_desc = desc->FindFieldByName("value"); assert(val_desc != nullptr); return val_desc; } void _validate_parameters(Msg *root_msg, const Path &path) const; void _parse_aggregate_field(const std::string &field); Optional _parse_map_key(const std::string &key) { return _parse_map_key_impl(key, typename std::is_const::type()); } Optional _parse_map_key_impl(const std::string &key); Optional _parse_map_key_impl(const std::string &key, std::true_type); Optional _parse_map_key_impl(const std::string &key, std::false_type) { return _parse_map_key_impl(key); } void _del_array_element(); Msg *_msg = nullptr; const gp::FieldDescriptor *_field_desc = nullptr; int _arr_idx = -1; Optional _map_key; }; using ConstFieldRef = FieldRef; using MutableFieldRef = FieldRef; template FieldRef::FieldRef(Msg *root_msg, const Path &path) { _validate_parameters(root_msg, path); // Here we have to give _map_key a valid value. _map_key->SetBoolValue(false); _msg = root_msg; const auto &fields = path.fields(); for (auto idx = 0U; idx != fields.size(); ++idx) { const auto &field = fields[idx]; assert(!field.empty() && _msg != nullptr); if (is_map_element()) { assert(_field_desc != nullptr); _msg = _get_map_msg(_msg, _field_desc, *_map_key); _map_key.reset(); _map_key->SetBoolValue(false); _field_desc = nullptr; } else if (is_array_element()) { assert(_field_desc != nullptr); _msg = _get_sub_repeated_msg(_msg, _field_desc, _arr_idx); _arr_idx = -1; _field_desc = nullptr; } if (_field_desc == nullptr) { _field_desc = _msg->GetDescriptor()->FindFieldByName(field); if (_field_desc == nullptr) { throw Error("field not found: " + field); } } else { if (_field_desc->is_map()) { _map_key = _parse_map_key(field); if (!_map_key) { throw Error("invalid path: not valid map key"); } } else if (_field_desc->is_repeated()) { try { _arr_idx = std::stoi(field); } catch (const std::exception &e) { throw Error("invalid array index: " + field); } auto size = _msg->GetReflection()->FieldSize(*_msg, _field_desc); if (_arr_idx >= size) { throw Error("array index is out-of-range: " + field + " : " + std::to_string(size)); } if (_arr_idx < 0) { throw Error("invalid path: array index should larger or equal to 0"); } } else { if (type() != gp::FieldDescriptor::CPPTYPE_MESSAGE) { throw Error("invalid path: not a nested type: " + field); } _msg = _get_sub_msg(_msg, _field_desc); _field_desc = _msg->GetDescriptor()->FindFieldByName(field); if (_field_desc == nullptr) { throw Error("field not found: " + field); } } } } } template FieldRef FieldRef::get_array_element(int idx) const { assert(is_array() && idx < size()); FieldRef element(*this); element._arr_idx = idx; return element; } template auto FieldRef::get_map_range() const -> std::pair::const_iterator, gp::Map::const_iterator> { assert(is_map()); // The following is hacking, hacking, and hacking!!! const auto *reflection = static_cast(_msg->GetReflection()); const auto &map_base = reflection->GetRaw(*_msg, _field_desc); const auto &dynamic_map = static_cast(map_base); const auto &m = dynamic_map.GetMap(); return {m.begin(), m.end()}; } template std::string FieldRef::msg_type() const { assert(_field_desc != nullptr); if (type() != gp::FieldDescriptor::CPPTYPE_MESSAGE) { throw Error("not a message"); } return _field_desc->message_type()->full_name(); } template std::string FieldRef::mapped_msg_type() const { assert(_field_desc != nullptr); if (type() != gp::FieldDescriptor::CPPTYPE_MESSAGE) { throw Error("not a message"); } auto *value_desc = _field_desc->message_type()->FindFieldByName("value"); assert(value_desc != nullptr); return value_desc->message_type()->full_name(); } template int FieldRef::size() const { if (!(is_map() && !is_map_element()) && !(is_array() && !is_array_element())) { throw Error("not an array or map"); } return _msg->GetReflection()->FieldSize(*_msg, _field_desc); } template void FieldRef::_validate_parameters(Msg *root_msg, const Path &path) const { assert(root_msg != nullptr); if (root_msg->GetTypeName() != path.type()) { throw Error("type missmatch"); } } template void FieldRef::_parse_aggregate_field(const std::string &field) { assert(!field.empty() && field.back() == ']'); auto pos = field.find('['); if (pos == std::string::npos) { throw Error("invalid array or map"); } auto name = field.substr(0, pos); auto key = field.substr(pos + 1, field.size() - pos - 2); _field_desc = _msg->GetDescriptor()->FindFieldByName(name); if (_field_desc == nullptr) { throw Error("invalid field: " + name); } if (is_map()) { _map_key = _parse_map_key(key); } else if (is_array()) { try { _arr_idx = std::stoi(key); } catch (const std::exception &e) { throw Error("invalid array index: " + key); } auto size = _msg->GetReflection()->FieldSize(*_msg, _field_desc); if (_arr_idx >= size) { throw Error("array index is out-of-range: " + key + " : " + std::to_string(size)); } } else { throw Error("not an array or map"); } } template Optional FieldRef::_parse_map_key_impl(const std::string &key, std::true_type) { auto map_key = _parse_map_key_impl(key); try { _get_map_value_const(_msg, _field_desc, *map_key); } catch (const NotFoundError &e) { throw MapKeyNotFoundError(key); } return map_key; } template Optional FieldRef::_parse_map_key_impl(const std::string &key) { assert(is_map() && !_map_key); auto *desc = _field_desc->message_type(); assert(desc != nullptr); auto *key_desc = desc->FindFieldByName("key"); assert(key_desc != nullptr); // TODO: it seems we don't need a new map_key, just use _map_key gp::MapKey map_key; switch (key_desc->cpp_type()) { case gp::FieldDescriptor::CPPTYPE_INT32: map_key.SetInt32Value(util::sv_to_int32(key)); break; case gp::FieldDescriptor::CPPTYPE_INT64: map_key.SetInt64Value(util::sv_to_int64(key)); break; case gp::FieldDescriptor::CPPTYPE_UINT32: map_key.SetUInt32Value(util::sv_to_uint32(key)); break; case gp::FieldDescriptor::CPPTYPE_UINT64: map_key.SetUInt64Value(util::sv_to_uint64(key)); break; case gp::FieldDescriptor::CPPTYPE_BOOL: map_key.SetBoolValue(util::sv_to_bool(key)); break; case gp::FieldDescriptor::CPPTYPE_STRING: map_key.SetStringValue(key); break; default: // TODO: or assert? throw Error("invalid map key type"); } return Optional(map_key); } template void FieldRef::set_int32(int32_t val) { _msg->GetReflection()->SetInt32(_msg, _field_desc, val); } template void FieldRef::set_int64(int64_t val) { _msg->GetReflection()->SetInt64(_msg, _field_desc, val); } template void FieldRef::set_uint32(uint32_t val) { _msg->GetReflection()->SetUInt32(_msg, _field_desc, val); } template void FieldRef::set_uint64(uint64_t val) { _msg->GetReflection()->SetUInt64(_msg, _field_desc, val); } template void FieldRef::set_float(float val) { _msg->GetReflection()->SetFloat(_msg, _field_desc, val); } template void FieldRef::set_double(double val) { _msg->GetReflection()->SetDouble(_msg, _field_desc, val); } template void FieldRef::set_bool(bool val) { _msg->GetReflection()->SetBool(_msg, _field_desc, val); } template void FieldRef::set_enum(int val) { _msg->GetReflection()->SetEnumValue(_msg, _field_desc, val); } template void FieldRef::set_string(const std::string &val) { _msg->GetReflection()->SetString(_msg, _field_desc, val); } template void FieldRef::set_msg(gp::Message &msg) { auto sub_msg = _msg->GetReflection()->MutableMessage(_msg, _field_desc); assert(sub_msg->GetTypeName() == msg.GetTypeName()); sub_msg->GetReflection()->Swap(sub_msg, &msg); } template void FieldRef::set_repeated_int32(int32_t val) { _msg->GetReflection()->SetRepeatedInt32(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_int64(int64_t val) { _msg->GetReflection()->SetRepeatedInt64(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_uint32(uint32_t val) { _msg->GetReflection()->SetRepeatedUInt32(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_uint64(uint64_t val) { _msg->GetReflection()->SetRepeatedUInt64(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_float(float val) { _msg->GetReflection()->SetRepeatedFloat(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_double(double val) { _msg->GetReflection()->SetRepeatedDouble(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_bool(bool val) { _msg->GetReflection()->SetRepeatedBool(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_enum(int val) { _msg->GetReflection()->SetRepeatedEnumValue(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_string(const std::string &val) { _msg->GetReflection()->SetRepeatedString(_msg, _field_desc, _arr_idx, val); } template void FieldRef::set_repeated_msg(gp::Message &msg) { auto *sub_msg = _msg->GetReflection()->MutableRepeatedMessage(_msg, _field_desc, _arr_idx); sub_msg->GetReflection()->Swap(sub_msg, &msg); } template void FieldRef::add_int32(int32_t val) { _msg->GetReflection()->AddInt32(_msg, _field_desc, val); } template void FieldRef::add_int64(int64_t val) { _msg->GetReflection()->AddInt64(_msg, _field_desc, val); } template void FieldRef::add_uint32(uint32_t val) { _msg->GetReflection()->AddUInt32(_msg, _field_desc, val); } template void FieldRef::add_uint64(uint64_t val) { _msg->GetReflection()->AddUInt64(_msg, _field_desc, val); } template void FieldRef::add_float(float val) { _msg->GetReflection()->AddFloat(_msg, _field_desc, val); } template void FieldRef::add_double(double val) { _msg->GetReflection()->AddDouble(_msg, _field_desc, val); } template void FieldRef::add_bool(bool val) { _msg->GetReflection()->AddBool(_msg, _field_desc, val); } template void FieldRef::add_enum(int val) { _msg->GetReflection()->AddEnumValue(_msg, _field_desc, val); } template void FieldRef::add_string(const std::string &val) { _msg->GetReflection()->AddString(_msg, _field_desc, val); } template void FieldRef::add_msg(gp::Message &msg) { auto sub_msg = _msg->GetReflection()->AddMessage(_msg, _field_desc); assert(sub_msg->GetTypeName() == msg.GetTypeName()); sub_msg->GetReflection()->Swap(sub_msg, &msg); } template void FieldRef::clear() { if (is_array_element()) { throw Error("cannot clear an array element"); } // TODO: map element. if (_field_desc == nullptr) { _msg->Clear(); } else { _msg->GetReflection()->ClearField(_msg, _field_desc); } } template void FieldRef::del() { if (is_array_element()) { _del_array_element(); } else { // TODO: support map throw Error("can only delete array element"); } } template void FieldRef::merge(const gp::Message &msg) { assert(_field_desc != nullptr); if (type() != gp::FieldDescriptor::CPPTYPE_MESSAGE) { throw Error("not a message"); } auto sub_msg = _msg->GetReflection()->MutableMessage(_msg, _field_desc); assert(sub_msg->GetTypeName() == msg.GetTypeName()); sub_msg->MergeFrom(msg); } template void FieldRef::_del_array_element() { assert(is_array_element()); const auto *reflection = _msg->GetReflection(); auto size = reflection->FieldSize(*_msg, _field_desc); for (auto idx = _arr_idx; idx != size - 1; ++idx) { reflection->SwapElements(_msg, _field_desc, idx, idx + 1); } reflection->RemoveLast(_msg, _field_desc); } } } } #endif // end SEWENEW_REDISPROTOBUF_FIELD_REF_H ================================================ FILE: src/sw/redis-protobuf/get_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "get_command.h" #include "errors.h" #include "redis_protobuf.h" #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { int GetCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); auto key = api::open_key(ctx, args.key_name, api::KeyMode::READONLY); if (!api::key_exists(key.get(), RedisProtobuf::instance().type())) { _reply_with_nil(ctx); } else { auto *msg = api::get_msg_by_key(key.get()); assert(msg != nullptr); _reply_with_msg(ctx, *msg, args); } return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } } GetCommand::Args GetCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc < 3) { throw WrongArityError(); } Args args; args.key_name = argv[1]; auto pos = _parse_opts(argv, argc, args); if (pos + 1 != argc && pos + 2 != argc) { throw WrongArityError(); } if (pos + 2 == argc) { args.path = Path(argv[pos], argv[pos + 1]); } else { args.path = Path(argv[pos]); } return args; } int GetCommand::_parse_opts(RedisModuleString **argv, int argc, Args &args) const { auto idx = 2; while (idx < argc) { auto opt = StringView(argv[idx]); if (util::str_case_equal(opt, "--FORMAT")) { if (idx + 1 >= argc) { throw Error("syntax error"); } ++idx; args.format = _parse_format(argv[idx]); } else { // Finish parsing options. break; } ++idx; } return idx; } GetCommand::Args::Format GetCommand::_parse_format(const StringView &format) const { if (util::str_case_equal(format, "BINARY")) { return Args::Format::BINARY; } else if (util::str_case_equal(format, "JSON")) { return Args::Format::JSON; } else { throw Error("unknown format"); } } void GetCommand::_get_msg(RedisModuleCtx *ctx, const gp::Message &msg, Args::Format format) const { std::string result; switch (format) { case Args::Format::BINARY: if (!msg.SerializeToString(&result)) { throw Error("failed to serialize message to binary string"); } break; case Args::Format::JSON: result = util::msg_to_json(msg); break; case Args::Format::NONE: throw Error("option --FORMAT not specified"); break; default: assert(false); } RedisModule_ReplyWithStringBuffer(ctx, result.data(), result.size()); } void GetCommand::_get_field(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const { if (field.is_map_element()) { _get_map_element(ctx, field, format); } else if (field.is_map()) { _get_map(ctx, field, format); } else if (field.is_array_element()) { _get_array_element(ctx, field, format); } else if (field.is_array()) { _get_array(ctx, field, format); } else { // Non-aggregate type. _get_scalar_field(ctx, field, format); } } void GetCommand::_get_scalar_field(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const { switch (field.type()) { case gp::FieldDescriptor::CPPTYPE_INT32: { auto val = field.get_int32(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_INT64: { auto val = field.get_int64(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT32: { auto val = field.get_uint32(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT64: { auto val = field.get_uint64(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_DOUBLE: { auto val = field.get_double(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_FLOAT: { auto val = field.get_float(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_BOOL: { auto val = field.get_bool(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_ENUM: { auto val = field.get_enum(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_STRING: { auto val = field.get_string(); RedisModule_ReplyWithStringBuffer(ctx, val.data(), val.size()); break; } case gp::FieldDescriptor::CPPTYPE_MESSAGE: { _get_msg(ctx, field.get_msg(), format); break; } default: assert(false); } } void GetCommand::_get_array_element(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const { switch (field.type()) { case gp::FieldDescriptor::CPPTYPE_INT32: { auto val = field.get_repeated_int32(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_INT64: { auto val = field.get_repeated_int64(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT32: { auto val = field.get_repeated_uint32(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT64: { auto val = field.get_repeated_uint64(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_DOUBLE: { auto val = field.get_repeated_double(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_FLOAT: { auto val = field.get_repeated_float(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_BOOL: { auto val = field.get_repeated_bool(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_ENUM: { auto val = field.get_repeated_enum(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_STRING: { const auto &val = field.get_repeated_string(); RedisModule_ReplyWithStringBuffer(ctx, val.data(), val.size()); break; } case gp::FieldDescriptor::CPPTYPE_MESSAGE: { _get_msg(ctx, field.get_repeated_msg(), format); break; } default: assert(false); } } void GetCommand::_get_array(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const { auto arr_size = field.size(); RedisModule_ReplyWithArray(ctx, arr_size); for (auto idx = 0; idx != arr_size; ++idx) { try { _get_field(ctx, field.get_array_element(idx), format); } catch (const Error &e) { api::reply_with_error(ctx, e); } } } void GetCommand::_get_map_element(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const { switch (field.map_value_type()) { case gp::FieldDescriptor::CPPTYPE_INT32: { auto val = field.get_mapped_int32(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_INT64: { auto val = field.get_mapped_int64(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT32: { auto val = field.get_mapped_uint32(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT64: { auto val = field.get_mapped_uint64(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_DOUBLE: { auto val = field.get_mapped_double(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_FLOAT: { auto val = field.get_mapped_float(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_BOOL: { auto val = field.get_mapped_bool(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_ENUM: { auto val = field.get_mapped_enum(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_STRING: { const auto &val = field.get_mapped_string(); RedisModule_ReplyWithStringBuffer(ctx, val.data(), val.size()); break; } case gp::FieldDescriptor::CPPTYPE_MESSAGE: { _get_msg(ctx, field.get_mapped_msg(), format); break; } default: assert(false); } } void GetCommand::_get_map(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const { auto arr_size = field.size(); RedisModule_ReplyWithArray(ctx, arr_size); auto range = field.get_map_range(); for (auto iter = range.first; iter != range.second; ++iter) { const auto &key = iter->first; const auto &val = iter->second; try { _get_map_kv(ctx, field, format, key, val); } catch (const Error &e) { api::reply_with_error(ctx, e); } } } void GetCommand::_get_map_kv(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format, const gp::MapKey &key, const gp::MapValueRef &value) const { RedisModule_ReplyWithArray(ctx, 2); // Reply with key. switch (key.type()) { case gp::FieldDescriptor::CPPTYPE_INT32: { auto val = key.GetInt32Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_INT64: { auto val = key.GetInt64Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT32: { auto val = key.GetUInt32Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT64: { auto val = key.GetUInt64Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_BOOL: { auto val = key.GetBoolValue(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_STRING: { auto val = key.GetStringValue(); RedisModule_ReplyWithStringBuffer(ctx, val.data(), val.size()); break; } default: assert(false); } // Reply with value. switch (field.map_value_type()) { case gp::FieldDescriptor::CPPTYPE_INT32: { auto val = value.GetInt32Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_INT64: { auto val = value.GetInt64Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT32: { auto val = value.GetUInt32Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_UINT64: { auto val = value.GetUInt64Value(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_DOUBLE: { auto val = value.GetDoubleValue(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_FLOAT: { auto val = value.GetFloatValue(); auto str = std::to_string(val); RedisModule_ReplyWithSimpleString(ctx, str.data()); break; } case gp::FieldDescriptor::CPPTYPE_BOOL: { auto val = value.GetBoolValue(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_ENUM: { auto val = value.GetEnumValue(); RedisModule_ReplyWithLongLong(ctx, val); break; } case gp::FieldDescriptor::CPPTYPE_STRING: { auto val = value.GetStringValue(); RedisModule_ReplyWithStringBuffer(ctx, val.data(), val.size()); break; } case gp::FieldDescriptor::CPPTYPE_MESSAGE: { const auto &msg = value.GetMessageValue(); _get_msg(ctx, msg, format); break; } default: assert(false); } } void GetCommand::_reply_with_nil(RedisModuleCtx *ctx) const { RedisModule_ReplyWithNull(ctx); } void GetCommand::_reply_with_msg(RedisModuleCtx *ctx, gp::Message &msg, const Args &args) const { const auto &path = args.path; if (msg.GetTypeName() != path.type()) { throw Error("type mismatch"); } if (path.empty()) { // Get the whole message. return _get_msg(ctx, msg, args.format); } // Get field. _get_field(ctx, ConstFieldRef(&msg, path), args.format); } } } } ================================================ FILE: src/sw/redis-protobuf/get_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_GET_COMMANDS_H #define SEWENEW_REDISPROTOBUF_GET_COMMANDS_H #include "module_api.h" #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.GET key [--FORMAT BINARY|JSON] type [path] // return: If no path is specified, return the protobuf message of the key // as a bulk string reply. If path is specified, return the value // of the field specified with the path, and the reply type depends // on the definition of the protobuf. If the key doesn't exist, // return a nil reply. // error: If the path doesn't exist, or type mismatch return an error reply. class GetCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; enum class Format { BINARY = 0, JSON, NONE }; Format format = Format::NONE; Path path; }; Args _parse_args(RedisModuleString **argv, int argc) const; int _parse_opts(RedisModuleString **argv, int argc, Args &args) const; Args::Format _parse_format(const StringView &format) const; void _reply_with_nil(RedisModuleCtx *ctx) const; void _reply_with_msg(RedisModuleCtx *ctx, gp::Message &msg, const Args &args) const; void _get_scalar_field(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const; void _get_array_element(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const; void _get_array(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const; void _get_map_element(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const; void _get_map(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const; void _get_map_kv(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format, const gp::MapKey &key, const gp::MapValueRef &value) const; void _get_msg(RedisModuleCtx *ctx, const gp::Message &msg, Args::Format format) const; void _get_field(RedisModuleCtx *ctx, const ConstFieldRef &field, Args::Format format) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_GET_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/import_command.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "import_command.h" #include #include "utils.h" #include "errors.h" #include "redis_protobuf.h" namespace sw { namespace redis { namespace pb { int ImportCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); auto &m= RedisProtobuf::instance(); m.proto_factory()->load(args.filename, args.content); RedisModule_ReplicateVerbatim(ctx); RedisModule_ReplyWithSimpleString(ctx, "OK"); return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } auto ImportCommand::_parse_args(RedisModuleString **argv, int argc) const -> Args { assert(argv != nullptr); if (argc != 3) { throw WrongArityError(); } Args args; args.filename = util::sv_to_string(argv[1]); args.content = util::sv_to_string(argv[2]); return args; } } } } ================================================ FILE: src/sw/redis-protobuf/import_command.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_IMPORT_COMMAND_H #define SEWENEW_REDISPROTOBUF_IMPORT_COMMAND_H #include "module_api.h" #include #include #include #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.IMPORT file-path content // return: OK status reply. // error: If failing to import, return an error reply. class ImportCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { std::string filename; std::string content; }; Args _parse_args(RedisModuleString **argv, int argc) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_IMPORT_COMMAND_H ================================================ FILE: src/sw/redis-protobuf/last_import_command.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "last_import_command.h" #include #include "utils.h" #include "errors.h" #include "redis_protobuf.h" namespace sw { namespace redis { namespace pb { int LastImportCommand::run(RedisModuleCtx *ctx, RedisModuleString ** /*argv*/, int /*argc*/) const { try { assert(ctx != nullptr); auto &m = RedisProtobuf::instance(); auto last_loaded_files = m.proto_factory()->last_loaded(); RedisModule_ReplyWithArray(ctx, last_loaded_files.size()); for (const auto &ele : last_loaded_files) { const auto &filename = ele.first; const auto &status = ele.second; RedisModule_ReplyWithArray(ctx, 2); RedisModule_ReplyWithStringBuffer(ctx, filename.data(), filename.size()); RedisModule_ReplyWithStringBuffer(ctx, status.data(), status.size()); } return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } } } } ================================================ FILE: src/sw/redis-protobuf/last_import_command.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_LAST_IMPORT_COMMAND_H #define SEWENEW_REDISPROTOBUF_LAST_IMPORT_COMMAND_H #include "module_api.h" #include #include #include #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.LASTIMPORT // return: Array reply of the status of last imported proto files. // note: This command returns status of all imported proto files since last // call to this command. Once this command is called, the underlying // records will be cleared. class LastImportCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_LAST_IMPORT_COMMAND_H ================================================ FILE: src/sw/redis-protobuf/len_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "len_command.h" #include "errors.h" #include "redis_protobuf.h" #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { int LenCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); auto key = api::open_key(ctx, args.key_name, api::KeyMode::READONLY); if (!api::key_exists(key.get(), RedisProtobuf::instance().type())) { RedisModule_ReplyWithLongLong(ctx, 0); } else { auto *msg = api::get_msg_by_key(key.get()); assert(msg != nullptr); auto len = _len(*msg, args.path); RedisModule_ReplyWithLongLong(ctx, len); } return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } } LenCommand::Args LenCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc != 3 && argc != 4) { throw WrongArityError(); } Path path; if (argc == 3) { path = Path(argv[2]); } else { path = Path(argv[2], argv[3]); } return {argv[1], std::move(path)}; } long long LenCommand::_len(gp::Message &msg, const Path &path) const { if (msg.GetTypeName() != path.type()) { throw Error("type mismatch"); } if (path.empty()) { // Return the length of the message. return msg.ByteSizeLong(); } return _len(ConstFieldRef(&msg, path)); } long long LenCommand::_len(const ConstFieldRef &field) const { if (field.is_map() || field.is_array()) { return field.size(); } // Scalar type. switch (field.type()) { case gp::FieldDescriptor::CPPTYPE_MESSAGE: return field.get_msg().ByteSizeLong(); case gp::FieldDescriptor::CPPTYPE_STRING: // TODO: use GetStringReference instead. return field.get_string().size(); default: throw Error("cannot get length of this field"); } } } } } ================================================ FILE: src/sw/redis-protobuf/len_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_LEN_COMMANDS_H #define SEWENEW_REDISPROTOBUF_LEN_COMMANDS_H #include "module_api.h" #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.LEN key type [path] // return: Integer reply: If the specified path is a string, return the // length of the string in bytes; if the field is an array or a map, // return the size of the array or map. If the field is a message, // return the size of the message in bytes, i.e. Message::ByteSizeLong. // If the key doesn't exist, return 0. // error: If the path doesn't exist, or the corresponding field is not a // message or a string or an array or a map, return an error reply. class LenCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; Path path; }; Args _parse_args(RedisModuleString **argv, int argc) const; long long _len(gp::Message &msg, const Path &path) const; long long _len(const ConstFieldRef &field) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_LEN_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/merge_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "merge_command.h" #include "errors.h" #include "redis_protobuf.h" #include "utils.h" #include "field_ref.h" #include "set_command.h" namespace sw { namespace redis { namespace pb { int MergeCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); auto key = api::open_key(ctx, args.key_name, api::KeyMode::WRITEONLY); if (!api::key_exists(key.get(), RedisProtobuf::instance().type())) { SetCommand set_cmd; set_cmd._run(ctx, argv, argc); return RedisModule_ReplyWithLongLong(ctx, 0); } auto *msg = api::get_msg_by_key(key.get()); assert(msg != nullptr); _merge(args, *msg); RedisModule_ReplyWithLongLong(ctx, 1); RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } MergeCommand::Args MergeCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc != 4 && argc != 5) { throw WrongArityError(); } Path path; StringView val; if (argc == 4) { path = Path(argv[2]); val = StringView(argv[3]); } else { path = Path(argv[2], argv[3]); val = StringView(argv[4]); } return {argv[1], std::move(path), std::move(val)}; } void MergeCommand::_merge(const Args &args, gp::Message &msg) const { const auto &path = args.path; if (path.empty()) { _merge_msg(path.type(), args.val, msg); } else { _merge_sub_msg(path, args.val, msg); } } void MergeCommand::_merge_msg(const std::string &type, const StringView &val, gp::Message &msg) const { if (type != msg.GetTypeName()) { throw Error("type mismatch"); } auto other = RedisProtobuf::instance().proto_factory()->create(type, val); assert(other); msg.MergeFrom(*other); } void MergeCommand::_merge_sub_msg(const Path &path, const StringView &val, gp::Message &msg) const { MutableFieldRef field(&msg, path); auto sub_msg = RedisProtobuf::instance().proto_factory()->create(field.msg_type(), val); assert(sub_msg); field.merge(*sub_msg); } } } } ================================================ FILE: src/sw/redis-protobuf/merge_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_MERGE_COMMANDS_H #define SEWENEW_REDISPROTOBUF_MERGE_COMMANDS_H #include "module_api.h" #include #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.MERGE key type [path] value // return: Integer reply: If the key exists, return 1. Otherwise, return 0. // If key doesn't exist, this command behaves as PB.SET. // error: If the type doesn't match the protobuf message type of the key, // or path doesn't exist, return an error reply. class MergeCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; Path path; StringView val; }; Args _parse_args(RedisModuleString **argv, int argc) const; void _merge(const Args &args, gp::Message &msg) const; void _merge_msg(const std::string &type, const StringView &val, gp::Message &msg) const; void _merge_sub_msg(const Path &path, const StringView &val, gp::Message &msg) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_MERGE_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/module_api.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "module_api.h" #include namespace sw { namespace redis { namespace pb { namespace api { RedisKey open_key(RedisModuleCtx *ctx, RedisModuleString *name, KeyMode key_mode) { if (name == nullptr) { throw Error("cannot open key with a null name"); } int mode = 0; switch (key_mode) { case KeyMode::READONLY: mode = REDISMODULE_READ; break; case KeyMode::WRITEONLY: mode = REDISMODULE_WRITE; break; case KeyMode::READWRITE: mode = REDISMODULE_READ | REDISMODULE_WRITE; break; default: assert(false); } return RedisKey(static_cast(RedisModule_OpenKey(ctx, name, mode))); } bool key_exists(RedisModuleKey *key, RedisModuleType *key_type) { // key can be nullptr. auto type = RedisModule_KeyType(key); if (type == REDISMODULE_KEYTYPE_EMPTY) { return false; } if (RedisModule_ModuleTypeGetType(key) == key_type) { return true; } throw WrongTypeError(REDISMODULE_ERRORMSG_WRONGTYPE); } int reply_with_error(RedisModuleCtx *ctx, const Error &err) { auto msg = std::string("ERR ") + err.what(); return RedisModule_ReplyWithError(ctx, msg.data()); } google::protobuf::Message* get_msg_by_key(RedisModuleKey *key) { auto *msg = static_cast(RedisModule_ModuleTypeGetValue(key)); if (msg == nullptr) { throw Error("failed to get message by key"); } return msg; } } } } } ================================================ FILE: src/sw/redis-protobuf/module_api.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_MODULE_API_H #define SEWENEW_REDISPROTOBUF_MODULE_API_H #ifdef __cplusplus extern "C" { #endif #include "redismodule.h" #ifdef __cplusplus } #endif #include #include #include "errors.h" namespace sw { namespace redis { namespace pb { namespace api { template void warning(RedisModuleCtx *ctx, const char *fmt, Args &&...args) { RedisModule_Log(ctx, "warning", fmt, std::forward(args)...); } template void notice(RedisModuleCtx *ctx, const char *fmt, Args &&...args) { RedisModule_Log(ctx, "notice", fmt, std::forward(args)...); } template void debug(RedisModuleCtx *ctx, const char *fmt, Args &&...args) { RedisModule_Log(ctx, "debug", fmt, std::forward(args)...); } template void verbose(RedisModuleCtx *ctx, const char *fmt, Args &&...args) { RedisModule_Log(ctx, "verbose", fmt, std::forward(args)...); } struct RedisKeyCloser { void operator()(RedisModuleKey *key) const { RedisModule_CloseKey(key); } }; using RedisKey = std::unique_ptr; enum class KeyMode { READONLY, WRITEONLY, READWRITE }; RedisKey open_key(RedisModuleCtx *ctx, RedisModuleString *name, KeyMode mode); // If key doesn't exist return false. // If key type is NOT *key_type*, throw WrongTypeError. // Otherwise, return true. bool key_exists(RedisModuleKey *key, RedisModuleType *key_type); int reply_with_error(RedisModuleCtx *ctx, const Error &err); google::protobuf::Message* get_msg_by_key(RedisModuleKey *key); } } } } #endif // end SEWENEW_REDISPROTOBUF_MODULE_API_H ================================================ FILE: src/sw/redis-protobuf/module_entry.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "module_entry.h" #include #include "redis_protobuf.h" #include "errors.h" #include "module_api.h" int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { assert(ctx != nullptr); using namespace sw::redis::pb; try { auto &m = RedisProtobuf::instance(); m.load(ctx, argv, argc); } catch (const Error &e) { api::warning(ctx, "%s", e.what()); return REDISMODULE_ERR; } return REDISMODULE_OK; } ================================================ FILE: src/sw/redis-protobuf/module_entry.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_MODULE_ENTRY_H #define SEWENEW_REDISPROTOBUF_MODULE_ENTRY_H #include "module_api.h" #ifdef __cplusplus extern "C" { #endif int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); #ifdef __cplusplus } #endif #endif // end SEWENEW_REDISPROTOBUF_MODULE_ENTRY_H ================================================ FILE: src/sw/redis-protobuf/options.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "options.h" #include #include "redis_protobuf.h" #include "errors.h" #include "utils.h" namespace sw { namespace redis { namespace pb { void Options::load(RedisModuleString **argv, int argc) { Options opts; auto idx = 0; while (idx < argc) { auto opt = StringView(argv[idx]); if (util::str_case_equal(opt, "--DIR")) { if (!opts.proto_dir.empty()) { throw Error("duplicate --DIR option"); } ++idx; opts.proto_dir = util::sv_to_string(StringView(argv[idx])); } else { throw Error("unknown option: " + util::sv_to_string(opt)); } ++idx; } if (opts.proto_dir.empty()) { throw Error("option '--DIR dir' is required"); } *this = std::move(opts); } } } } ================================================ FILE: src/sw/redis-protobuf/options.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_OPTIONS_H #define SEWENEW_REDISPROTOBUF_OPTIONS_H #include "module_api.h" #include namespace sw { namespace redis { namespace pb { struct Options { void load(RedisModuleString **argv, int argc); std::string proto_dir; }; } } } #endif // end SEWENEW_REDISPROTOBUF_OPTIONS_H ================================================ FILE: src/sw/redis-protobuf/path.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "path.h" #include "errors.h" namespace sw { namespace redis { namespace pb { Path::Path(const StringView &type, const StringView &path) : _type(type.data(), type.size()), _fields(_parse_fields(path)) {} std::vector Path::_parse_fields(const StringView &path) const { if (path.size() <= 1) { throw Error("empty path"); } if (*(path.data()) != '/') { throw Error("invalid path: should begin with /"); } std::vector fields; auto start = 1U; const auto *ptr = path.data(); for (auto idx = start; idx != path.size(); ++idx) { if (ptr[idx] == '/') { if (idx <= start) { throw Error("empty field"); } fields.emplace_back(ptr + start, idx - start); start = idx + 1; } } if (path.size() <= start) { throw Error("empty field"); } fields.emplace_back(ptr + start, path.size() - start); return fields; } } } } ================================================ FILE: src/sw/redis-protobuf/path.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_PATH_H #define SEWENEW_REDISPROTOBUF_PATH_H #include #include #include "utils.h" namespace sw { namespace redis { namespace pb { class Path { public: Path() = default; Path(const StringView &type, const StringView &path); explicit Path(const StringView &type) : _type(type.data(), type.size()) {} const std::string& type() const { return _type; } const std::vector& fields() const { return _fields; } bool empty() const { return _fields.empty(); } private: std::vector _parse_fields(const StringView &path) const; std::string _type; std::vector _fields; }; } } } #endif // end SEWENEW_REDISPROTOBUF_PATH_H ================================================ FILE: src/sw/redis-protobuf/proto_factory.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "proto_factory.h" #include #include #include "utils.h" #include "errors.h" namespace sw { namespace redis { namespace pb { void FactoryErrorCollector::_add_error(const std::string &type, const std::string &filename, int line, int column, const std::string &message) { auto err = type + ":" + filename + ":" + std::to_string(line) + ":" + std::to_string(column) + ":" + message; _errors.push_back(std::move(err)); } std::string FactoryErrorCollector::last_errors() const { std::string err_str; for (const auto &err : _errors) { if (!err_str.empty()) { err_str += "\n"; } err_str += err; } return err_str; } ProtoFactory::ProtoFactory(const std::string &proto_dir) : _proto_dir(_canonicalize_path(proto_dir)), _importer(&_source_tree, &_error_collector) { _source_tree.MapPath("", _proto_dir); _load_protos(_proto_dir); _async_loader = std::thread([this]() { this->_async_load(); }); } ProtoFactory::~ProtoFactory() { _stop_loader = true; _cv.notify_one(); if (_async_loader.joinable()) { _async_loader.join(); } } MsgUPtr ProtoFactory::create(const std::string &type) { const auto *desc = descriptor(type); if (desc == nullptr) { throw Error("unknown protobuf type: " + type); } const auto *prototype = _factory.GetPrototype(desc); assert(prototype != nullptr); return MsgUPtr(prototype->New()); } MsgUPtr ProtoFactory::create(const std::string &type, const StringView &sv) { auto msg = create(type); const auto *ptr = sv.data(); auto len = sv.size(); if (len >= 2 && ptr[0] == '{' && ptr[len - 1] == '}') { auto status = gp::util::JsonStringToMessage(gp::StringPiece(ptr, len), msg.get()); if (!status.ok()) { throw Error("failed to parse json to " + type + ": " + status.ToString()); } } else { if (!msg->ParseFromArray(ptr, len)) { throw Error("failed to parse binary to " + type); } } return msg; } const gp::Descriptor* ProtoFactory::descriptor(const std::string &type) { auto iter = _descriptor_cache.find(type); if (iter != _descriptor_cache.end()) { return iter->second; } const auto *desc = _importer.pool()->FindMessageTypeByName(type); if (desc != nullptr) { _descriptor_cache.emplace(type, desc); } return desc; } void ProtoFactory::load(const std::string &filename, const std::string &content) { { std::lock_guard lock(_mtx); _tasks[filename] = content; } _cv.notify_one(); } std::unordered_map ProtoFactory::last_loaded() { std::unordered_map last_loaded_files; { std::lock_guard lock(_mtx); _last_loaded_files.swap(last_loaded_files); } return last_loaded_files; } void ProtoFactory::_load_protos(const std::string &proto_dir) { auto files = io::list_dir(proto_dir); for (const auto &file : files) { if (!io::is_regular(file) || io::extension(file) != "proto") { continue; } auto prefix_size = proto_dir.size() + 1; if (file.size() < prefix_size) { continue; } _load(file.substr(prefix_size)); } } void ProtoFactory::_load(const std::string &file) { // Clear last errors. _error_collector.clear(); auto *desc = _importer.Import(file); if (desc == nullptr || _error_collector.has_error()) { throw Error("failed to load " + file + "\n" + _error_collector.last_errors()); } _loaded_files.insert(file); } std::string ProtoFactory::_canonicalize_path(std::string proto_dir) const { // Remove trailing '/' while (!proto_dir.empty() && proto_dir.back() == '/') { proto_dir.resize(proto_dir.size() - 1); } if (proto_dir.empty()) { throw Error("invalid proto dir: " + proto_dir); } return proto_dir; } void ProtoFactory::_async_load() { while (!_stop_loader) { std::unordered_map tasks; { std::unique_lock lock(_mtx); _cv.wait(lock, [this]() { return this->_stop_loader || !(this->_tasks).empty(); }); tasks.swap(_tasks); } std::unordered_map status; for (const auto &task : tasks) { const auto &filename = task.first; const auto &content = task.second; try { _load(filename, content); status[filename] = "OK"; } catch (const Error &err) { status[filename] = std::string("ERR ") + err.what(); } } if (!status.empty()) { std::lock_guard lock(_mtx); for (auto &&ele : status) { _last_loaded_files[ele.first] = std::move(ele.second); } } } } void ProtoFactory::_load(const std::string &filename, const std::string &content) { auto iter = _loaded_files.find(filename); if (iter != _loaded_files.end()) { throw Error("already imported"); } _dump_to_disk(filename, content); try { _load(filename); } catch (const Error &) { io::remove_file(_absolute_path(filename)); throw; } } void ProtoFactory::_dump_to_disk(const std::string &filename, const std::string &content) const { auto path = _absolute_path(filename); std::ofstream file(path); if (!file) { throw Error("failed to open proto file for writing: " + path); } file << content; } std::string ProtoFactory::_absolute_path(const std::string &path) const { return _proto_dir + "/" + path; } } } } ================================================ FILE: src/sw/redis-protobuf/proto_factory.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_PROTO_FACTORY_H #define SEWENEW_REDISPROTOBUF_PROTO_FACTORY_H #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" namespace sw { namespace redis { namespace pb { class FactoryErrorCollector : public gp::compiler::MultiFileErrorCollector { public: virtual void AddError(const std::string &file_name, int line, int column, const std::string &message) override { _add_error("error", file_name, line, column, message); } virtual void AddWarning(const std::string &file_name, int line, int column, const std::string &message) override { _add_error("warning", file_name, line, column, message); } std::string last_errors() const; bool has_error() const { return !_errors.empty(); } void clear() { _errors.clear(); } private: void _add_error(const std::string &type, const std::string &filename, int line, int column, const std::string &message); std::vector _errors; }; class ProtoFactory { public: explicit ProtoFactory(const std::string &proto_dir); ProtoFactory(const ProtoFactory &) = delete; ProtoFactory& operator=(const ProtoFactory &) = delete; ProtoFactory(ProtoFactory &&) = delete; ProtoFactory& operator=(ProtoFactory &&) = delete; ~ProtoFactory(); MsgUPtr create(const std::string &type); MsgUPtr create(const std::string &type, const StringView &sv); const gp::Descriptor* descriptor(const std::string &type); void load(const std::string &file, const std::string &content); std::unordered_map last_loaded(); private: void _load_protos(const std::string &proto_dir); void _load(const std::string &file); void _load(const std::string &filename, const std::string &content); std::string _canonicalize_path(std::string proto_dir) const; void _async_load(); void _dump_to_disk(const std::string &filename, const std::string &content) const; std::string _absolute_path(const std::string &path) const; // Dir where .proto file are saved. std::string _proto_dir; gp::compiler::DiskSourceTree _source_tree; FactoryErrorCollector _error_collector; gp::compiler::Importer _importer; gp::DynamicMessageFactory _factory; std::unordered_map _descriptor_cache; std::unordered_set _loaded_files; std::mutex _mtx; std::condition_variable _cv; // map std::unordered_map _tasks; // map std::unordered_map _last_loaded_files; std::atomic _stop_loader{false}; std::thread _async_loader; }; } } } #endif // end SEWENEW_REDISPROTOBUF_PROTO_FACTORY_H ================================================ FILE: src/sw/redis-protobuf/redis_protobuf.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "redis_protobuf.h" #include #include #include #include "errors.h" #include "commands.h" namespace { struct StringDeleter { void operator()(char *str) const { if (str != nullptr) { RedisModule_Free(str); } } }; using StringUPtr = std::unique_ptr; struct RDBString { StringUPtr str; std::size_t len; }; RDBString rdb_load_string(RedisModuleIO *rdb); std::pair rdb_load_value(RedisModuleIO *rdb); std::pair serialize_message(void *value); } namespace sw { namespace redis { namespace pb { RedisProtobuf& RedisProtobuf::instance() { static RedisProtobuf redis_proto; return redis_proto; } void RedisProtobuf::load(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { assert(ctx != nullptr); if (RedisModule_Init(ctx, module_name().data(), module_version(), REDISMODULE_APIVER_1) == REDISMODULE_ERR) { throw Error("fail to init module of " + module_name() + " type"); } _options.load(argv, argc); RedisModuleTypeMethods methods = { REDISMODULE_TYPE_METHOD_VERSION, _rdb_load, _rdb_save, _aof_rewrite, nullptr, nullptr, _free_msg }; _module_type = RedisModule_CreateDataType(ctx, type_name().data(), encoding_version(), &methods); if (_module_type == nullptr) { throw Error(std::string("failed to create ") + type_name() + " type"); } _proto_factory = std::unique_ptr(new ProtoFactory(options().proto_dir)); cmd::create_commands(ctx); } void* RedisProtobuf::_rdb_load(RedisModuleIO *rdb, int encver) { try { assert(rdb != nullptr); auto &m = RedisProtobuf::instance(); if (encver != m.encoding_version()) { throw Error("cannot load data of version: " + std::to_string(encver)); } RDBString type_str; RDBString data_str; std::tie(type_str, data_str) = rdb_load_value(rdb); auto type = std::string(type_str.str.get(), type_str.len); auto *factory = m.proto_factory(); assert(factory != nullptr); auto msg = factory->create(type); if (!msg) { throw Error("unknown protobuf type: " + type); } if (!msg->ParseFromArray(data_str.str.get(), data_str.len)) { throw Error("failed to parse protobuf of type: " + type); } return msg.release(); } catch (const Error &e) { RedisModule_LogIOError(rdb, "warning", e.what()); return nullptr; } } void RedisProtobuf::_rdb_save(RedisModuleIO *rdb, void *value) { try { assert(rdb != nullptr); std::string type; std::string buf; std::tie(type, buf) = serialize_message(value); RedisModule_SaveStringBuffer(rdb, type.data(), type.size()); RedisModule_SaveStringBuffer(rdb, buf.data(), buf.size()); } catch (const Error &e) { RedisModule_LogIOError(rdb, "warning", e.what()); } } void RedisProtobuf::_aof_rewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) { try { assert(aof != nullptr); if (key == nullptr) { throw Error("null key to rewrite aof"); } std::string type; std::string buf; std::tie(type, buf) = serialize_message(value); RedisModule_EmitAOF(aof, "PB.SET", "sbb", key, type.data(), type.size(), buf.data(), buf.size()); } catch (const Error &e) { RedisModule_LogIOError(aof, "warning", e.what()); } } void RedisProtobuf::_free_msg(void *value) { if (value != nullptr) { auto *msg = static_cast(value); delete msg; } } } } } namespace { using sw::redis::pb::Error; RDBString rdb_load_string(RedisModuleIO *rdb) { std::size_t len = 0; auto *buf = RedisModule_LoadStringBuffer(rdb, &len); if (buf == nullptr) { throw Error("failed to load string buffer from rdb"); } return {StringUPtr(buf), len}; } std::pair rdb_load_value(RedisModuleIO *rdb) { auto type = rdb_load_string(rdb); auto data = rdb_load_string(rdb); return {std::move(type), std::move(data)}; } std::pair serialize_message(void *value) { if (value == nullptr) { throw Error("Null value to serialize"); } auto *msg = static_cast(value); auto type = msg->GetTypeName(); std::string buf; if (!msg->SerializeToString(&buf)) { throw Error("failed to serialize protobuf message of type " + type); } return {std::move(type), std::move(buf)}; } } ================================================ FILE: src/sw/redis-protobuf/redis_protobuf.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_REDIS_PROTOBUF_H #define SEWENEW_REDISPROTOBUF_REDIS_PROTOBUF_H #include "module_api.h" #include "proto_factory.h" #include "options.h" namespace sw { namespace redis { namespace pb { class RedisProtobuf { public: static RedisProtobuf& instance(); void load(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); int module_version() const { return _MODULE_VERSION; } int encoding_version() const { return _ENCODING_VERSION; } const std::string& module_name() const { return _MODULE_NAME; } const std::string& type_name() const { return _TYPE_NAME; } RedisModuleType* type() { return _module_type; } const Options& options() const { return _options; } ProtoFactory* proto_factory() { return _proto_factory.get(); } private: RedisProtobuf() = default; static void* _rdb_load(RedisModuleIO *rdb, int encver); static void _rdb_save(RedisModuleIO *rdb, void *value); static void _aof_rewrite(RedisModuleIO *aof, RedisModuleString *key, void *value); static void _free_msg(void *value); const int _MODULE_VERSION = 1; const int _ENCODING_VERSION = 0; const std::string _MODULE_NAME = "PB"; const std::string _TYPE_NAME = "PROTOC-SW"; RedisModuleType *_module_type = nullptr; std::unique_ptr _proto_factory; Options _options; }; } } } #endif // end SEWENEW_REDISPROTOBUF_REDIS_PROTOBUF_H ================================================ FILE: src/sw/redis-protobuf/redismodule.cpp ================================================ // This file is copied from Redis 4.0, and I splited the original header into // a header file and a cpp file, i.e. redismodule.h and redismodule.cpp. // So that it can be compiled with C++ compiler. #include "redismodule.h" #ifndef REDISMODULE_CORE void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes); void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size); char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str); int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *); int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver); int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name); int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll); int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid); void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode); void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp); int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp); size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp); int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where); RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len); void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply); int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply); long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply); size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply); RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...); void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str); const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len); int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err); int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg); int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len); void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len); int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len); int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str); int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d); int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply); int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll); int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d); void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx); const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply); int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str); char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode); int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen); mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire); int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr); int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore); int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score); int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted); void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score); int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...); int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx); void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos); unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx); void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes); RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods); int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value); RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key); void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...); void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s); void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io); char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr); void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value); double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value); float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void); void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len); void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele); void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md); #ifdef REDISMODULE_EXPERIMENTAL_API RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms); int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata); int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx); void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc); RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc); void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx); void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx); void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx); #endif #endif ================================================ FILE: src/sw/redis-protobuf/redismodule.h ================================================ // This file is copied from Redis 4.0, and I splited the original header into // a header file and a cpp file, i.e. redismodule.h and redismodule.cpp. // So that it can be compiled with C++ compiler. #ifndef REDISMODULE_H #define REDISMODULE_H #include #include #include /* ---------------- Defines common between core and modules --------------- */ /* Error status return values. */ #define REDISMODULE_OK 0 #define REDISMODULE_ERR 1 /* API versions. */ #define REDISMODULE_APIVER_1 1 /* API flags and constants */ #define REDISMODULE_READ (1<<0) #define REDISMODULE_WRITE (1<<1) #define REDISMODULE_LIST_HEAD 0 #define REDISMODULE_LIST_TAIL 1 /* Key types. */ #define REDISMODULE_KEYTYPE_EMPTY 0 #define REDISMODULE_KEYTYPE_STRING 1 #define REDISMODULE_KEYTYPE_LIST 2 #define REDISMODULE_KEYTYPE_HASH 3 #define REDISMODULE_KEYTYPE_SET 4 #define REDISMODULE_KEYTYPE_ZSET 5 #define REDISMODULE_KEYTYPE_MODULE 6 /* Reply types. */ #define REDISMODULE_REPLY_UNKNOWN -1 #define REDISMODULE_REPLY_STRING 0 #define REDISMODULE_REPLY_ERROR 1 #define REDISMODULE_REPLY_INTEGER 2 #define REDISMODULE_REPLY_ARRAY 3 #define REDISMODULE_REPLY_NULL 4 /* Postponed array length. */ #define REDISMODULE_POSTPONED_ARRAY_LEN -1 /* Expire */ #define REDISMODULE_NO_EXPIRE -1 /* Sorted set API flags. */ #define REDISMODULE_ZADD_XX (1<<0) #define REDISMODULE_ZADD_NX (1<<1) #define REDISMODULE_ZADD_ADDED (1<<2) #define REDISMODULE_ZADD_UPDATED (1<<3) #define REDISMODULE_ZADD_NOP (1<<4) /* Hash API flags. */ #define REDISMODULE_HASH_NONE 0 #define REDISMODULE_HASH_NX (1<<0) #define REDISMODULE_HASH_XX (1<<1) #define REDISMODULE_HASH_CFIELDS (1<<2) #define REDISMODULE_HASH_EXISTS (1<<3) /* Context Flags: Info about the current context returned by RM_GetContextFlags */ /* The command is running in the context of a Lua script */ #define REDISMODULE_CTX_FLAGS_LUA 0x0001 /* The command is running inside a Redis transaction */ #define REDISMODULE_CTX_FLAGS_MULTI 0x0002 /* The instance is a master */ #define REDISMODULE_CTX_FLAGS_MASTER 0x0004 /* The instance is a slave */ #define REDISMODULE_CTX_FLAGS_SLAVE 0x0008 /* The instance is read-only (usually meaning it's a slave as well) */ #define REDISMODULE_CTX_FLAGS_READONLY 0x0010 /* The instance is running in cluster mode */ #define REDISMODULE_CTX_FLAGS_CLUSTER 0x0020 /* The instance has AOF enabled */ #define REDISMODULE_CTX_FLAGS_AOF 0x0040 // /* The instance has RDB enabled */ #define REDISMODULE_CTX_FLAGS_RDB 0x0080 // /* The instance has Maxmemory set */ #define REDISMODULE_CTX_FLAGS_MAXMEMORY 0x0100 /* Maxmemory is set and has an eviction policy that may delete keys */ #define REDISMODULE_CTX_FLAGS_EVICT 0x0200 /* A special pointer that we can use between the core and the module to signal * field deletion, and that is impossible to be a valid pointer. */ #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) /* Error messages. */ #define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value" #define REDISMODULE_POSITIVE_INFINITE (1.0/0.0) #define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0) #define REDISMODULE_NOT_USED(V) ((void) V) /* ------------------------- End of common defines ------------------------ */ #ifndef REDISMODULE_CORE typedef long long mstime_t; /* Incomplete structures for compiler checks but opaque access. */ typedef struct RedisModuleCtx RedisModuleCtx; typedef struct RedisModuleKey RedisModuleKey; typedef struct RedisModuleString RedisModuleString; typedef struct RedisModuleCallReply RedisModuleCallReply; typedef struct RedisModuleIO RedisModuleIO; typedef struct RedisModuleType RedisModuleType; typedef struct RedisModuleDigest RedisModuleDigest; typedef struct RedisModuleBlockedClient RedisModuleBlockedClient; typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc); typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value); typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); typedef void (*RedisModuleTypeFreeFunc)(void *value); #define REDISMODULE_TYPE_METHOD_VERSION 1 typedef struct RedisModuleTypeMethods { uint64_t version; RedisModuleTypeLoadFunc rdb_load; RedisModuleTypeSaveFunc rdb_save; RedisModuleTypeRewriteFunc aof_rewrite; RedisModuleTypeMemUsageFunc mem_usage; RedisModuleTypeDigestFunc digest; RedisModuleTypeFreeFunc free; } RedisModuleTypeMethods; #define REDISMODULE_GET_API(name) \ RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name)) #define REDISMODULE_API_FUNC(x) (*x) extern void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); extern void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes); extern void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); extern void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size); extern char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str); extern int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *); extern int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); extern void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver); extern int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name); extern int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll); extern int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx); extern int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid); extern void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode); extern void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp); extern int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp); extern size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp); extern int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where); extern RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); extern const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len); extern void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply); extern int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply); extern long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply); extern size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply); extern RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...); extern void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str); extern const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len); extern void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d); extern int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply); extern int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll); extern int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d); extern void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx); extern int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); extern int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx); extern const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply); extern int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key); extern int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str); extern char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode); extern int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen); extern mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key); extern int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire); extern int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr); extern int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore); extern int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score); extern int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted); extern void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key); extern int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); extern int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); extern int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); extern int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score); extern int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key); extern int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); extern int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); extern int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); extern int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...); extern int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx); extern void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos); extern unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx); extern int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx); extern void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes); extern RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods); extern int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value); extern RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key); extern void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); extern void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); extern uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); extern void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); extern int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io); extern void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...); extern void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s); extern void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len); extern RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io); extern char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr); extern void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value); extern double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io); extern void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value); extern float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); extern void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); extern void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); extern int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); extern void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); extern int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); extern RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); extern long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void); extern void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len); extern void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele); extern void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md); /* Experimental APIs */ #ifdef REDISMODULE_EXPERIMENTAL_API extern RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms); extern int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata); extern int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx); extern int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx); extern void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx); extern int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc); extern RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc); extern void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx); extern void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx); extern void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx); #endif /* This is included inline inside each Redis module. */ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused)); static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { void *getapifuncptr = ((void**)ctx)[0]; RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; REDISMODULE_GET_API(Alloc); REDISMODULE_GET_API(Calloc); REDISMODULE_GET_API(Free); REDISMODULE_GET_API(Realloc); REDISMODULE_GET_API(Strdup); REDISMODULE_GET_API(CreateCommand); REDISMODULE_GET_API(SetModuleAttribs); REDISMODULE_GET_API(IsModuleNameBusy); REDISMODULE_GET_API(WrongArity); REDISMODULE_GET_API(ReplyWithLongLong); REDISMODULE_GET_API(ReplyWithError); REDISMODULE_GET_API(ReplyWithSimpleString); REDISMODULE_GET_API(ReplyWithArray); REDISMODULE_GET_API(ReplySetArrayLength); REDISMODULE_GET_API(ReplyWithStringBuffer); REDISMODULE_GET_API(ReplyWithString); REDISMODULE_GET_API(ReplyWithNull); REDISMODULE_GET_API(ReplyWithCallReply); REDISMODULE_GET_API(ReplyWithDouble); REDISMODULE_GET_API(ReplySetArrayLength); REDISMODULE_GET_API(GetSelectedDb); REDISMODULE_GET_API(SelectDb); REDISMODULE_GET_API(OpenKey); REDISMODULE_GET_API(CloseKey); REDISMODULE_GET_API(KeyType); REDISMODULE_GET_API(ValueLength); REDISMODULE_GET_API(ListPush); REDISMODULE_GET_API(ListPop); REDISMODULE_GET_API(StringToLongLong); REDISMODULE_GET_API(StringToDouble); REDISMODULE_GET_API(Call); REDISMODULE_GET_API(CallReplyProto); REDISMODULE_GET_API(FreeCallReply); REDISMODULE_GET_API(CallReplyInteger); REDISMODULE_GET_API(CallReplyType); REDISMODULE_GET_API(CallReplyLength); REDISMODULE_GET_API(CallReplyArrayElement); REDISMODULE_GET_API(CallReplyStringPtr); REDISMODULE_GET_API(CreateStringFromCallReply); REDISMODULE_GET_API(CreateString); REDISMODULE_GET_API(CreateStringFromLongLong); REDISMODULE_GET_API(CreateStringFromString); REDISMODULE_GET_API(CreateStringPrintf); REDISMODULE_GET_API(FreeString); REDISMODULE_GET_API(StringPtrLen); REDISMODULE_GET_API(AutoMemory); REDISMODULE_GET_API(Replicate); REDISMODULE_GET_API(ReplicateVerbatim); REDISMODULE_GET_API(DeleteKey); REDISMODULE_GET_API(StringSet); REDISMODULE_GET_API(StringDMA); REDISMODULE_GET_API(StringTruncate); REDISMODULE_GET_API(GetExpire); REDISMODULE_GET_API(SetExpire); REDISMODULE_GET_API(ZsetAdd); REDISMODULE_GET_API(ZsetIncrby); REDISMODULE_GET_API(ZsetScore); REDISMODULE_GET_API(ZsetRem); REDISMODULE_GET_API(ZsetRangeStop); REDISMODULE_GET_API(ZsetFirstInScoreRange); REDISMODULE_GET_API(ZsetLastInScoreRange); REDISMODULE_GET_API(ZsetFirstInLexRange); REDISMODULE_GET_API(ZsetLastInLexRange); REDISMODULE_GET_API(ZsetRangeCurrentElement); REDISMODULE_GET_API(ZsetRangeNext); REDISMODULE_GET_API(ZsetRangePrev); REDISMODULE_GET_API(ZsetRangeEndReached); REDISMODULE_GET_API(HashSet); REDISMODULE_GET_API(HashGet); REDISMODULE_GET_API(IsKeysPositionRequest); REDISMODULE_GET_API(KeyAtPos); REDISMODULE_GET_API(GetClientId); REDISMODULE_GET_API(GetContextFlags); REDISMODULE_GET_API(PoolAlloc); REDISMODULE_GET_API(CreateDataType); REDISMODULE_GET_API(ModuleTypeSetValue); REDISMODULE_GET_API(ModuleTypeGetType); REDISMODULE_GET_API(ModuleTypeGetValue); REDISMODULE_GET_API(SaveUnsigned); REDISMODULE_GET_API(LoadUnsigned); REDISMODULE_GET_API(SaveSigned); REDISMODULE_GET_API(LoadSigned); REDISMODULE_GET_API(SaveString); REDISMODULE_GET_API(SaveStringBuffer); REDISMODULE_GET_API(LoadString); REDISMODULE_GET_API(LoadStringBuffer); REDISMODULE_GET_API(SaveDouble); REDISMODULE_GET_API(LoadDouble); REDISMODULE_GET_API(SaveFloat); REDISMODULE_GET_API(LoadFloat); REDISMODULE_GET_API(EmitAOF); REDISMODULE_GET_API(Log); REDISMODULE_GET_API(LogIOError); REDISMODULE_GET_API(StringAppendBuffer); REDISMODULE_GET_API(RetainString); REDISMODULE_GET_API(StringCompare); REDISMODULE_GET_API(GetContextFromIO); REDISMODULE_GET_API(Milliseconds); REDISMODULE_GET_API(DigestAddStringBuffer); REDISMODULE_GET_API(DigestAddLongLong); REDISMODULE_GET_API(DigestEndSequence); #ifdef REDISMODULE_EXPERIMENTAL_API REDISMODULE_GET_API(GetThreadSafeContext); REDISMODULE_GET_API(FreeThreadSafeContext); REDISMODULE_GET_API(ThreadSafeContextLock); REDISMODULE_GET_API(ThreadSafeContextUnlock); REDISMODULE_GET_API(BlockClient); REDISMODULE_GET_API(UnblockClient); REDISMODULE_GET_API(IsBlockedReplyRequest); REDISMODULE_GET_API(IsBlockedTimeoutRequest); REDISMODULE_GET_API(GetBlockedClientPrivateData); REDISMODULE_GET_API(AbortBlock); #endif if (RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; RedisModule_SetModuleAttribs(ctx,name,ver,apiver); return REDISMODULE_OK; } #else /* Things only defined for the modules core, not exported to modules * including this file. */ #define RedisModuleString robj #endif /* REDISMODULE_CORE */ #endif /* REDISMOUDLE_H */ ================================================ FILE: src/sw/redis-protobuf/schema_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "schema_command.h" #include "errors.h" #include "redis_protobuf.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { int SchemaCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); const auto &type = args.type; auto *desc = RedisProtobuf::instance().proto_factory()->descriptor(type); if (desc == nullptr) { RedisModule_ReplyWithNull(ctx); } else { //auto schema = _format(desc->DebugString()); auto schema = desc->DebugString(); RedisModule_ReplyWithStringBuffer(ctx, schema.data(), schema.size()); } return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } SchemaCommand::Args SchemaCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc != 2) { throw WrongArityError(); } return {Path(argv[1]).type()}; } std::string SchemaCommand::_format(const std::string &schema) const { std::string formated_schema; formated_schema.reserve(schema.size() * 2); for (auto ch : schema) { if (ch != '.') { formated_schema.push_back(ch); } else { if (formated_schema.empty()) { // This should not happen. throw Error("invalid schema"); } if (formated_schema.back() != ' ') { // '.' => '::' formated_schema.append("::"); } // else discard leading '.' } } return formated_schema; } } } } ================================================ FILE: src/sw/redis-protobuf/schema_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_SCHEMA_COMMANDS_H #define SEWENEW_REDISPROTOBUF_SCHEMA_COMMANDS_H #include #include "module_api.h" #include "utils.h" namespace sw { namespace redis { namespace pb { // command: PB.SCHEMA type // return: Bulk string reply: return the schema of the specified type. If // the type doesn't exist, return a nil reply. class SchemaCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { std::string type; }; Args _parse_args(RedisModuleString **argv, int argc) const; std::string _format(const std::string &schema) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_SCHEMA_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/set_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "set_command.h" #include "errors.h" #include "redis_protobuf.h" #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { int SetCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { auto res = _run(ctx, argv, argc); RedisModule_ReplyWithLongLong(ctx, res); RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } return REDISMODULE_ERR; } int SetCommand::_run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { assert(ctx != nullptr); // TODO: if the ByteSize is too large, serialization might fail. auto args = _parse_args(argv, argc); const auto &path = args.path; auto key = api::open_key(ctx, args.key_name, api::KeyMode::WRITEONLY); assert(key); if (!api::key_exists(key.get(), RedisProtobuf::instance().type())) { if (args.opt == Args::Opt::XX) { return 0; } _create_msg(*key, path, args.val); } else { if (args.opt == Args::Opt::NX) { return 0; } _set_msg(*key, path, args.val); } auto expire = args.expire.count(); if (expire > 0) { RedisModule_SetExpire(key.get(), expire); } return 1; } SetCommand::Args SetCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc < 4) { throw WrongArityError(); } Args args; args.key_name = argv[1]; auto pos = _parse_opts(argv, argc, args); if (pos + 2 != argc && pos + 3 != argc) { throw WrongArityError(); } Path path; StringView val; if (pos + 2 == argc) { path = Path(argv[pos]); val = argv[pos + 1]; } else { path = Path(argv[pos], argv[pos + 1]); val = argv[pos + 2]; } args.path = std::move(path); args.val = std::move(val); return args; } int SetCommand::_parse_opts(RedisModuleString **argv, int argc, Args &args) const { auto idx = 2; while (idx < argc) { auto opt = StringView(argv[idx]); if (util::str_case_equal(opt, "--NX")) { if (args.opt != Args::Opt::NONE) { throw Error("syntax error"); } args.opt = Args::Opt::NX; } else if (util::str_case_equal(opt, "--XX")) { if (args.opt != Args::Opt::NONE) { throw Error("syntax error"); } args.opt = Args::Opt::XX; } else if (util::str_case_equal(opt, "--EX")) { if (args.expire != std::chrono::milliseconds(0) || idx + 1 >= argc) { throw Error("syntax error"); } // NOTE: this is tricky, that we modified idx in the loop. ++idx; auto expire = _parse_expire(argv[idx]); args.expire = std::chrono::seconds(expire); } else if (util::str_case_equal(opt, "--PX")) { if (args.expire != std::chrono::milliseconds(0) || idx + 1 >= argc) { throw Error("syntax error"); } // NOTE: this is tricky, that we modified idx in the loop. ++idx; auto expire = _parse_expire(argv[idx]); args.expire = std::chrono::milliseconds(expire); } else { // Finish parsing options. break; } ++idx; } return idx; } int64_t SetCommand::_parse_expire(const StringView &sv) const { auto expire = util::sv_to_int64(sv); if (expire <= 0) { throw Error("expire must larger than 0"); } return expire; } void SetCommand::_create_msg(RedisModuleKey &key, const Path &path, const StringView &val) const { MsgUPtr msg; auto &m = RedisProtobuf::instance(); if (path.empty()) { msg = m.proto_factory()->create(path.type(), val); } else { msg = m.proto_factory()->create(path.type()); MutableFieldRef field(msg.get(), path); _set_field(field, val); } if (RedisModule_ModuleTypeSetValue(&key, m.type(), msg.get()) != REDISMODULE_OK) { throw Error("failed to set message"); } msg.release(); } void SetCommand::_set_msg(RedisModuleKey &key, const Path &path, const StringView &val) const { auto *msg = api::get_msg_by_key(&key); assert(msg != nullptr); if (path.empty()) { // Set the whole message. if (msg->GetTypeName() != path.type()) { throw Error("type mismatch"); } auto &m = RedisProtobuf::instance(); auto msg = m.proto_factory()->create(path.type(), val); if (RedisModule_ModuleTypeSetValue(&key, m.type(), msg.get()) != REDISMODULE_OK) { throw Error("failed to set message"); } msg.release(); } else { // Set field. MutableFieldRef field(msg, path); _set_field(field, val); } } void SetCommand::_set_field(MutableFieldRef &field, const StringView &val) const { if (field.is_map_element()) { return _set_map_element(field, val); } else if (field.is_map()) { throw Error("cannot set the whole map field"); } else if (field.is_array_element()) { return _set_array_element(field, val); } else if (field.is_array()) { throw Error("cannot set the whole array field"); } _set_scalar_field(field, val); } void SetCommand::_set_scalar_field(MutableFieldRef &field, const StringView &val) const { switch (field.type()) { case gp::FieldDescriptor::CPPTYPE_INT32: _set_int32(field, val); break; case gp::FieldDescriptor::CPPTYPE_INT64: _set_int64(field, val); break; case gp::FieldDescriptor::CPPTYPE_UINT32: _set_uint32(field, val); break; case gp::FieldDescriptor::CPPTYPE_UINT64: _set_uint64(field, val); break; case gp::FieldDescriptor::CPPTYPE_DOUBLE: _set_double(field, val); break; case gp::FieldDescriptor::CPPTYPE_FLOAT: _set_float(field, val); break; case gp::FieldDescriptor::CPPTYPE_BOOL: _set_bool(field, val); break; case gp::FieldDescriptor::CPPTYPE_ENUM: _set_enum(field, val); break; case gp::FieldDescriptor::CPPTYPE_STRING: _set_string(field, val); break; case gp::FieldDescriptor::CPPTYPE_MESSAGE: _set_msg(field, val); break; default: throw Error("unknown type"); } } void SetCommand::_set_map_element(MutableFieldRef &field, const StringView &val) const { switch (field.map_value_type()) { case gp::FieldDescriptor::CPPTYPE_INT32: _set_mapped_int32(field, val); break; case gp::FieldDescriptor::CPPTYPE_INT64: _set_mapped_int64(field, val); break; case gp::FieldDescriptor::CPPTYPE_UINT32: _set_mapped_uint32(field, val); break; case gp::FieldDescriptor::CPPTYPE_UINT64: _set_mapped_uint64(field, val); break; case gp::FieldDescriptor::CPPTYPE_DOUBLE: _set_mapped_double(field, val); break; case gp::FieldDescriptor::CPPTYPE_FLOAT: _set_mapped_float(field, val); break; case gp::FieldDescriptor::CPPTYPE_BOOL: _set_mapped_bool(field, val); break; case gp::FieldDescriptor::CPPTYPE_ENUM: _set_mapped_enum(field, val); break; case gp::FieldDescriptor::CPPTYPE_STRING: _set_mapped_string(field, val); break; case gp::FieldDescriptor::CPPTYPE_MESSAGE: _set_mapped_msg(field, val); break; default: throw Error("unknown type"); } } void SetCommand::_set_array_element(MutableFieldRef &field, const StringView &val) const { switch (field.type()) { case gp::FieldDescriptor::CPPTYPE_INT32: _set_repeated_int32(field, val); break; case gp::FieldDescriptor::CPPTYPE_INT64: _set_repeated_int64(field, val); break; case gp::FieldDescriptor::CPPTYPE_UINT32: _set_repeated_uint32(field, val); break; case gp::FieldDescriptor::CPPTYPE_UINT64: _set_repeated_uint64(field, val); break; case gp::FieldDescriptor::CPPTYPE_DOUBLE: _set_repeated_double(field, val); break; case gp::FieldDescriptor::CPPTYPE_FLOAT: _set_repeated_float(field, val); break; case gp::FieldDescriptor::CPPTYPE_BOOL: _set_repeated_bool(field, val); break; case gp::FieldDescriptor::CPPTYPE_ENUM: _set_repeated_enum(field, val); break; case gp::FieldDescriptor::CPPTYPE_STRING: _set_repeated_string(field, val); break; case gp::FieldDescriptor::CPPTYPE_MESSAGE: _set_repeated_msg(field, val); break; default: throw Error("unknown type"); } } void SetCommand::_set_int32(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_INT32); auto val = util::sv_to_int32(sv); field.set_int32(val); } void SetCommand::_set_int64(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_INT64); auto val = util::sv_to_int64(sv); field.set_int64(val); } void SetCommand::_set_uint32(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_UINT32); auto val = util::sv_to_uint32(sv); field.set_uint32(val); } void SetCommand::_set_uint64(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_UINT64); auto val = util::sv_to_uint64(sv); field.set_uint64(val); } void SetCommand::_set_double(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_DOUBLE); auto val = util::sv_to_double(sv); field.set_double(val); } void SetCommand::_set_float(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_FLOAT); auto val = util::sv_to_float(sv); field.set_float(val); } void SetCommand::_set_bool(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_BOOL); auto val = util::sv_to_bool(sv); field.set_bool(val); } void SetCommand::_set_enum(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_ENUM); auto val = util::sv_to_int32(sv); field.set_enum(val); } void SetCommand::_set_string(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_STRING); field.set_string(util::sv_to_string(sv)); } void SetCommand::_set_msg(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_MESSAGE); auto new_msg = RedisProtobuf::instance().proto_factory()->create(field.msg_type(), sv); assert(new_msg); field.set_msg(*new_msg); } void SetCommand::_set_repeated_int32(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_INT32); auto val = util::sv_to_int32(sv); field.set_repeated_int32(val); } void SetCommand::_set_repeated_int64(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_INT64); auto val = util::sv_to_int64(sv); field.set_repeated_int64(val); } void SetCommand::_set_repeated_uint32(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_UINT32); auto val = util::sv_to_uint32(sv); field.set_repeated_uint32(val); } void SetCommand::_set_repeated_uint64(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_UINT64); auto val = util::sv_to_uint64(sv); field.set_repeated_uint64(val); } void SetCommand::_set_repeated_double(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_DOUBLE); auto val = util::sv_to_double(sv); field.set_repeated_double(val); } void SetCommand::_set_repeated_float(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_FLOAT); auto val = util::sv_to_float(sv); field.set_repeated_float(val); } void SetCommand::_set_repeated_bool(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_BOOL); auto val = util::sv_to_bool(sv); field.set_repeated_bool(val); } void SetCommand::_set_repeated_enum(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_ENUM); auto val = util::sv_to_int32(sv); field.set_repeated_enum(val); } void SetCommand::_set_repeated_string(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_STRING); field.set_repeated_string(util::sv_to_string(sv)); } void SetCommand::_set_repeated_msg(MutableFieldRef &field, const StringView &sv) const { assert(field.type() == gp::FieldDescriptor::CPPTYPE_MESSAGE); auto new_msg = RedisProtobuf::instance().proto_factory()->create(field.msg_type(), sv); assert(new_msg); field.set_repeated_msg(*new_msg); } void SetCommand::_set_mapped_int32(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_INT32); auto val = util::sv_to_int32(sv); field.set_mapped_int32(val); } void SetCommand::_set_mapped_int64(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_INT64); auto val = util::sv_to_int64(sv); field.set_mapped_int64(val); } void SetCommand::_set_mapped_uint32(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_UINT32); auto val = util::sv_to_uint32(sv); field.set_mapped_uint32(val); } void SetCommand::_set_mapped_uint64(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_UINT64); auto val = util::sv_to_uint64(sv); field.set_mapped_uint64(val); } void SetCommand::_set_mapped_double(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_DOUBLE); auto val = util::sv_to_double(sv); field.set_mapped_double(val); } void SetCommand::_set_mapped_float(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_FLOAT); auto val = util::sv_to_float(sv); field.set_mapped_float(val); } void SetCommand::_set_mapped_bool(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_BOOL); auto val = util::sv_to_bool(sv); field.set_mapped_bool(val); } void SetCommand::_set_mapped_enum(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_ENUM); auto val = util::sv_to_int32(sv); field.set_mapped_enum(val); } void SetCommand::_set_mapped_string(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_STRING); field.set_mapped_string(util::sv_to_string(sv)); } void SetCommand::_set_mapped_msg(MutableFieldRef &field, const StringView &sv) const { assert(field.map_value_type() == gp::FieldDescriptor::CPPTYPE_MESSAGE); auto new_msg = RedisProtobuf::instance().proto_factory()->create(field.mapped_msg_type(), sv); assert(new_msg); field.set_mapped_msg(*new_msg); } } } } ================================================ FILE: src/sw/redis-protobuf/set_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_SET_COMMANDS_H #define SEWENEW_REDISPROTOBUF_SET_COMMANDS_H #include "module_api.h" #include #include #include #include "utils.h" #include "field_ref.h" namespace sw { namespace redis { namespace pb { // command: PB.SET key [--NX|--XX] [--EX seconds | --PX milliseconds] type [path] value // return: Integer reply: 1 if set successfully. 0, otherwise, e.g. option --NX has // been set, while key already exists. // error: If the type doesn't match the protobuf message type of the // key, or the path doesn't exist, return an error reply. class SetCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; enum class Opt { NX = 0, XX, NONE }; Opt opt = Opt::NONE; std::chrono::milliseconds expire{0}; Path path; StringView val; }; int _run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; friend class MergeCommand; Args _parse_args(RedisModuleString **argv, int argc) const; // Return the position of the first non-option argument. int _parse_opts(RedisModuleString **argv, int argc, Args &args) const; int64_t _parse_expire(const StringView &sv) const; void _create_msg(RedisModuleKey &key, const Path &path, const StringView &val) const; void _set_msg(RedisModuleKey &key, const Path &path, const StringView &val) const; void _set_scalar_field(MutableFieldRef &field, const StringView &val) const; void _set_map_element(MutableFieldRef &field, const StringView &val) const; void _set_array_element(MutableFieldRef &field, const StringView &val) const; void _set_field(MutableFieldRef &field, const StringView &sv) const; void _set_int32(MutableFieldRef &field, const StringView &sv) const; void _set_int64(MutableFieldRef &field, const StringView &sv) const; void _set_uint32(MutableFieldRef &field, const StringView &sv) const; void _set_uint64(MutableFieldRef &field, const StringView &sv) const; void _set_double(MutableFieldRef &field, const StringView &sv) const; void _set_float(MutableFieldRef &field, const StringView &sv) const; void _set_bool(MutableFieldRef &field, const StringView &sv) const; void _set_enum(MutableFieldRef &field, const StringView &sv) const; void _set_string(MutableFieldRef &field, const StringView &sv) const; void _set_msg(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_int32(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_int64(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_uint32(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_uint64(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_double(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_float(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_bool(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_enum(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_string(MutableFieldRef &field, const StringView &sv) const; void _set_repeated_msg(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_int32(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_int64(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_uint32(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_uint64(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_double(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_float(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_bool(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_enum(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_string(MutableFieldRef &field, const StringView &sv) const; void _set_mapped_msg(MutableFieldRef &field, const StringView &sv) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_SET_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/type_command.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "type_command.h" #include "errors.h" #include "redis_protobuf.h" #include "utils.h" namespace sw { namespace redis { namespace pb { int TypeCommand::run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const { try { assert(ctx != nullptr); auto args = _parse_args(argv, argc); auto key = api::open_key(ctx, args.key_name, api::KeyMode::READONLY); if (!api::key_exists(key.get(), RedisProtobuf::instance().type())) { return RedisModule_ReplyWithNull(ctx); } auto *msg = api::get_msg_by_key(key.get()); assert(msg != nullptr); auto type = _format_type(msg->GetTypeName()); return RedisModule_ReplyWithSimpleString(ctx, type.data()); } catch (const WrongArityError &err) { return RedisModule_WrongArity(ctx); } catch (const Error &err) { return api::reply_with_error(ctx, err); } } TypeCommand::Args TypeCommand::_parse_args(RedisModuleString **argv, int argc) const { assert(argv != nullptr); if (argc != 2) { throw WrongArityError(); } return Args{argv[1]}; } std::string TypeCommand::_format_type(std::string type) const { auto pos = type.find('.'); if (pos == std::string::npos) { // No namespace. return type; } std::string type_str; type_str.reserve(type.size() * 2); type_str = type.substr(0, pos); for (std::size_t idx = pos; idx != type.size(); ++idx) { auto ch = type[idx]; if (ch != '.') { type_str.push_back(ch); } else { type_str.append("::"); } } return type_str; } } } } ================================================ FILE: src/sw/redis-protobuf/type_command.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_TYPE_COMMANDS_H #define SEWENEW_REDISPROTOBUF_TYPE_COMMANDS_H #include "module_api.h" #include #include #include "utils.h" namespace sw { namespace redis { namespace pb { // command: PB.TYPE key // return: Simple string reply: the protobuf type of the key. // If key doesn't exist, return a nil reply. class TypeCommand { public: int run(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) const; private: struct Args { RedisModuleString *key_name; }; Args _parse_args(RedisModuleString **argv, int argc) const; std::string _format_type(std::string type) const; }; } } } #endif // end SEWENEW_REDISPROTOBUF_TYPE_COMMANDS_H ================================================ FILE: src/sw/redis-protobuf/utils.cpp ================================================ /************************************************************************** Copyright (c) 2019 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 "utils.h" #include #include #include #include #include #include #include "errors.h" namespace { mode_t file_type(const std::string &file); } namespace sw { namespace redis { namespace pb { StringView::StringView(RedisModuleString *str) { if (str == nullptr) { throw Error("null string"); } _data = RedisModule_StringPtrLen(str, &_size); } namespace util { std::string msg_to_json(const gp::Message &msg) { std::string json; auto status = gp::util::MessageToJsonString(msg, &json); if (!status.ok()) { throw Error("failed to parse message to json"); } return json; } int32_t sv_to_int32(const StringView &sv) { try { return std::stoi(std::string(sv.data(), sv.size())); } catch (const std::exception &e) { throw Error("not int32"); } } int64_t sv_to_int64(const StringView &sv) { try { return std::stoll(std::string(sv.data(), sv.size())); } catch (const std::exception &e) { throw Error("not int64"); } } uint32_t sv_to_uint32(const StringView &sv) { try { // TODO: check if it's overflow return std::stoul(std::string(sv.data(), sv.size())); } catch (const std::exception &e) { throw Error("not uint32"); } } uint64_t sv_to_uint64(const StringView &sv) { try { return std::stoull(std::string(sv.data(), sv.size())); } catch (const std::exception &e) { throw Error("not uint64"); } } double sv_to_double(const StringView &sv) { try { return std::stod(std::string(sv.data(), sv.size())); } catch (const std::exception &e) { throw Error("not double"); } } float sv_to_float(const StringView &sv) { try { return std::stof(std::string(sv.data(), sv.size())); } catch (const std::exception &e) { throw Error("not float"); } } bool sv_to_bool(const StringView &sv) { bool b = false; auto s = std::string(sv.data(), sv.size()); // TODO: make it case insensitive if (s == "true") { b = true; } else if (s == "false") { b = false; } else { try { auto val = std::stoi(s); if (val == 0) { b = false; } else { b = true; } } catch (const std::exception &e) { throw Error("not bool"); } } return b; } std::string sv_to_string(const StringView &sv) { return std::string(sv.data(), sv.size()); } bool str_case_equal(const StringView &s1, const StringView &s2) { if (s1.size() != s2.size()) { return false; } const auto *p1 = s1.data(); const auto *p2 = s2.data(); for (std::size_t idx = 0; idx != s1.size(); ++idx) { if (static_cast(std::toupper(static_cast(p1[idx]))) != static_cast(std::toupper(static_cast(p2[idx])))) { return false; } } return true; } } namespace io { bool is_regular(const std::string &file) { return S_ISREG(file_type(file)); } bool is_directory(const std::string &file) { return S_ISDIR(file_type(file)); } std::vector list_dir(const std::string &path) { if (!is_directory(path)) { throw Error(path + " is not a directory"); } auto *dir = opendir(path.c_str()); if (dir == nullptr) { throw Error("failed to open directory: " + path); } std::vector files; dirent *entry = nullptr; while ((entry = readdir(dir)) != nullptr) { std::string name = entry->d_name; // Skip "." and ".." if (name == "." || name == "..") { continue; } auto file_path = path + "/" + name; if (is_directory(file_path)) { auto sub_files = list_dir(file_path); files.insert(files.end(), sub_files.begin(), sub_files.end()); } else { files.push_back(file_path); } } closedir(dir); return files; } std::string extension(const std::string &file) { auto pos = file.rfind("."); if (pos == std::string::npos) { return {}; } return file.substr(pos + 1); } void remove_file(const std::string &path) { std::remove(path.data()); } } } } } namespace { mode_t file_type(const std::string &file) { struct stat buf; if (stat(file.c_str(), &buf) < 0) { throw sw::redis::pb::Error("failed to get file status: " + file); } return buf.st_mode; } } ================================================ FILE: src/sw/redis-protobuf/utils.h ================================================ /************************************************************************** Copyright (c) 2019 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_REDISPROTOBUF_UTILS_H #define SEWENEW_REDISPROTOBUF_UTILS_H #include #include #include #include #include #include "module_api.h" namespace sw { namespace redis { namespace pb { namespace gp = google::protobuf; using MsgUPtr = std::unique_ptr; // By now, not all compilers support std::string_view, // 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()) {} StringView(RedisModuleString *str); 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; } constexpr bool empty() const noexcept { return _size == 0; } private: const char *_data = nullptr; std::size_t _size = 0; }; template class Optional { public: Optional() = default; Optional(const Optional &) = default; Optional& operator=(const Optional &) = default; Optional(Optional &&) = default; Optional& operator=(Optional &&) = default; ~Optional() = default; template explicit Optional(Args &&...args) : _value(true, T(std::forward(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; } void reset() noexcept { _value.first = false; } private: std::pair _value; }; namespace util { std::string msg_to_json(const gp::Message &msg); int32_t sv_to_int32(const StringView &sv); int64_t sv_to_int64(const StringView &sv); uint32_t sv_to_uint32(const StringView &sv); uint64_t sv_to_uint64(const StringView &sv); double sv_to_double(const StringView &sv); float sv_to_float(const StringView &sv); bool sv_to_bool(const StringView &sv); std::string sv_to_string(const StringView &sv); bool str_case_equal(const StringView &s1, const StringView &s2); } namespace io { bool is_regular(const std::string &file); bool is_directory(const std::string &file); std::vector list_dir(const std::string &path); std::string extension(const std::string &file); void remove_file(const std::string &path); } } } } #endif // end SEWENEW_REDISPROTOBUF_UTILS_H ================================================ FILE: test/src/sw/redis-protobuf/append_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "append_test.h" #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void AppendTest::_run(sw::redis::Redis &r) { auto key = test_key("append"); KeyDeleter deleter(r, key); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 1})") == 1, "failed to test pb.append command"); REDIS_ASSERT(r.command("PB.APPEND", key, "Msg", "/sub/s", "abc") == 3, "failed to test appending string"); REDIS_ASSERT(r.command("PB.APPEND", key, "Msg", "/sub/s", "123") == 6, "failed to test appending string"); REDIS_ASSERT(r.command("PB.GET", key, "Msg", "/sub/s") == "abc123", "failed to test pb.append"); REDIS_ASSERT(r.command("PB.APPEND", key, "Msg", "/arr", 1, 2) == 2, "failed to test appending array"); REDIS_ASSERT(r.command("PB.APPEND", key, "Msg", "/arr", 3, 4) == 4, "failed to test appending array"); REDIS_ASSERT(r.command("PB.GET", key, "Msg", "/arr/0") == 1, "failed to test pb.append"); try { r.command("PB.APPEND", key, "Msg", "/i", 2); REDIS_ASSERT(false, "failed to test pb.append"); } catch (const sw::redis::Error &) { } } } } } } ================================================ FILE: test/src/sw/redis-protobuf/append_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_APPEND_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_APPEND_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class AppendTest : public ProtoTest { public: explicit AppendTest(sw::redis::Redis &r) : ProtoTest("PB.APPEND", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_APPEND_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/clear_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "clear_test.h" #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void ClearTest::_run(sw::redis::Redis &r) { auto key = test_key("clear"); KeyDeleter deleter(r, key); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 1, "sub" : {"s" : "hello"}, "arr" : [1, 2]})") == 1, "failed to test pb.append command"); REDIS_ASSERT(r.command("PB.CLEAR", key, "Msg") == 1 && r.command("PB.GET", key, "Msg", "/i") == 0, "failed to test clear whole message"); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 1, "sub" : {"s" : "hello", "i" : 23}, "arr" : [1, 2]})") == 1, "failed to test pb.clear command"); REDIS_ASSERT(r.command("PB.CLEAR", key, "Msg", "/i") == 1 && r.command("PB.GET", key, "Msg", "/i") == 0, "failed to test clear int"); REDIS_ASSERT(r.command("PB.CLEAR", key, "Msg", "/sub/s") == 1 && r.command("PB.GET", key, "Msg", "/sub/s").empty(), "failed to test clear string"); REDIS_ASSERT(r.command("PB.CLEAR", key, "Msg", "/arr") == 1 && r.command("PB.LEN", key, "Msg", "/arr") == 0, "failed to test clear array"); REDIS_ASSERT(r.command("PB.CLEAR", key, "Msg", "/sub") == 1 && r.command("PB.GET", key, "Msg", "/sub/i") == 0, "failed to test clear sub message"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/clear_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_CLEAR_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_CLEAR_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class ClearTest : public ProtoTest { public: explicit ClearTest(sw::redis::Redis &r) : ProtoTest("PB.CLEAR", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_CLEAR_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/del_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "del_test.h" #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void DelTest::_run(sw::redis::Redis &r) { auto key = test_key("del"); KeyDeleter deleter(r, key); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 1, "arr" : [1, 2], "m" : {"key" : "val"}})") == 1, "failed to test pb.del command"); REDIS_ASSERT(r.command("PB.DEL", key, "Msg", "/arr/0") == 1 && r.command("PB.LEN", key, "Msg", "/arr") == 1, "failed to test del array element"); /* TODO: support delete whole array and whole map, and map element REDIS_ASSERT(r.command("PB.DEL", key, "Msg", "/arr") == 1 && r.command("PB.LEN", key, "Msg", "/arr") == 0, "failed to test del array"); REDIS_ASSERT(r.command("PB.DEL", key, "Msg", "/m") == 1 && r.command("PB.LEN", key, "Msg", "/m") == 0, "failed to test del map"); */ REDIS_ASSERT(r.command("PB.DEL", key, "Msg") == 1 && r.exists(key) == 0, "failed to test del message"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/del_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_DEL_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_DEL_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class DelTest : public ProtoTest { public: explicit DelTest(sw::redis::Redis &r) : ProtoTest("PB.DEL", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_DEL_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/import_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "import_test.h" #include #include #include #include #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void ImportTest::_run(sw::redis::Redis &r) { auto key = test_key("import"); KeyDeleter deleter(r, key); std::string name{"test_import.proto"}; auto proto = R"( syntax = "proto3"; package sw.redis.pb; message Msg { int32 i = 1; string s = 2; } )"; r.command("PB.IMPORT", name, proto); // Ensure proto has been loaded std::this_thread::sleep_for(std::chrono::seconds(1)); auto res = r.command>("PB.LASTIMPORT"); REDIS_ASSERT(res.size() == 1 && res[name] == "OK", "failed to test pb.import command"); REDIS_ASSERT(r.command("PB.SET", key, "sw.redis.pb.Msg", "/i", 123) && r.command("PB.GET", key, "sw.redis.pb.Msg", "/i") == 123, "failed to test pb.import command"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/import_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_IMPORT_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_IMPORT_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class ImportTest : public ProtoTest { public: explicit ImportTest(sw::redis::Redis &r) : ProtoTest("PB.IMPORT", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_IMPORT_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/len_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "len_test.h" #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void LenTest::_run(sw::redis::Redis &r) { auto key = test_key("len"); KeyDeleter deleter(r, key); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"sub" : {"s" : "hello"}, "arr" : [1, 2, 3], "m" : {"k1" : "v1", "k2" : "v2"}})") == 1, "failed to test pb.len command"); REDIS_ASSERT(r.command("PB.LEN", key, "Msg", "/sub/s") == 5, "failed to test pb.len with string"); REDIS_ASSERT(r.command("PB.LEN", key, "Msg", "/arr") == 3, "failed to test pb.len with array"); REDIS_ASSERT(r.command("PB.LEN", key, "Msg", "/m") == 2, "failed to test pb.len with map"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/len_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_LEN_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_LEN_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class LenTest : public ProtoTest { public: explicit LenTest(sw::redis::Redis &r) : ProtoTest("PB.LEN", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_LEN_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/merge_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "merge_test.h" #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void MergeTest::_run(sw::redis::Redis &r) { auto key = test_key("merge"); KeyDeleter deleter(r, key); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 1, "m" : {"k1" : "v1"}})") == 1, "failed to test pb.merge command"); REDIS_ASSERT(r.command("PB.MERGE", key, "Msg", R"({"arr" : [1, 2]})") == 1, "failed to test pb.merge command"); REDIS_ASSERT(r.command("PB.GET", key, "Msg", "/arr/0") == 1, "failed to test pb.merge command"); REDIS_ASSERT(r.command("PB.LEN", key, "Msg", "/arr") == 2, "failed to test pb.merge command"); REDIS_ASSERT(r.command("PB.MERGE", key, "Msg", R"({"m" : {"k2" : "v2"}})") == 1, "failed to test pb.merge command"); REDIS_ASSERT(r.command("PB.GET", key, "Msg", "/m/k2") == "v2", "failed to test pb.merge command"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/merge_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_MERGE_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_MERGE_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class MergeTest : public ProtoTest { public: explicit MergeTest(sw::redis::Redis &r) : ProtoTest("PB.MERGE", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_MERGE_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/proto_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "proto_test.h" #include namespace sw { namespace redis { namespace pb { namespace test { void ProtoTest::run() { std::cout << "Test " << _name << ": "; _run(_redis); std::cout << "pass" << std::endl; } } } } } ================================================ FILE: test/src/sw/redis-protobuf/proto_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_PROTO_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_PROTO_TEST_H #include namespace sw { namespace redis { namespace pb { namespace test { class ProtoTest { public: ProtoTest(const std::string &name, sw::redis::Redis &r) : _name(name), _redis(r) {} virtual ~ProtoTest() = default; void run(); const std::string& name() const { return _name; } private: virtual void _run(sw::redis::Redis &r) = 0; std::string _name; sw::redis::Redis &_redis; }; } } } } #endif // endif SEWENEW_REDISPROTOBUF_TEST_PROTO_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/schema_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "schema_test.h" #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void SchemaTest::_run(sw::redis::Redis &r) { auto schema = r.command("PB.SCHEMA", "Msg"); REDIS_ASSERT(schema && !schema->empty(), "failed to test pb.schema command"); schema = r.command("PB.SCHEMA", "sw.redis.pb.not-exist-Msg-type"); REDIS_ASSERT(!schema, "failed to test pb.schema command"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/schema_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_SCHEMA_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_SCHEMA_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class SchemaTest : public ProtoTest { public: explicit SchemaTest(sw::redis::Redis &r) : ProtoTest("PB.SCHEMA", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_SCHEMA_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/set_get_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "set_get_test.h" #include "utils.h" #include namespace sw { namespace redis { namespace pb { namespace test { void SetGetTest::_run(sw::redis::Redis &r) { auto key = test_key("set-get"); KeyDeleter deleter(r, key); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 1})") == 1 && r.command("PB.GET", key, "Msg", "/i") == 1, "failed to test pb.set and pb.get command"); REDIS_ASSERT(r.command("PB.SET", key, "Msg", "/i", 2) == 1 && r.command("PB.GET", key, "Msg", "/i") == 2, "failed to test pb.set and pb.get command"); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"sub" : {"s" : "hello"}})") == 1 && r.command("PB.GET", key, "Msg", "/sub/s") == "hello", "failed to test pb.set and pb.get command"); REDIS_ASSERT(r.command("PB.SET", key, "Msg", "/sub/s", "world") == 1 && r.command("PB.GET", key, "Msg", "/sub/s") == "world", "failed to test pb.set and pb.get command"); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 123, "sub" : {"s" : "hello", "i" : 123}, "arr" : [1, 2], "m" : {"k" : "v"}})") == 1 && r.command("PB.GET", key, "Msg", "/sub/s") == "hello" && r.command("PB.GET", key, "Msg", "/arr/0") == 1, "failed to test pb.set and pb.get command"); REDIS_ASSERT(r.command("PB.SET", key, "Msg", "/m/key", "world") == 1 && r.command("PB.GET", key, "Msg", "/m/key") == "world", "failed to test pb.set and pb.get command"); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"arr" : [4, 5, 6]})") == 1, "failed to test pb.set command"); auto arr = r.command>("PB.GET", key, "Msg", "/arr"); auto tmp = std::vector{4, 5, 6}; REDIS_ASSERT(arr == tmp, "failed to test pb.get command"); REDIS_ASSERT(r.command("PB.SET", key, "--NX", "Msg", "/sub/s", "world") == 0, "failed to test pb.set and pb.get command"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/set_get_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_SET_TEST_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_SET_TEST_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class SetGetTest : public ProtoTest { public: explicit SetGetTest(sw::redis::Redis &r) : ProtoTest("PB.GET PB.SET", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_SET_TEST_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/test_main.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 #include #include "append_test.h" #include "clear_test.h" #include "del_test.h" #include "type_test.h" #include "schema_test.h" #include "set_get_test.h" #include "len_test.h" #include "merge_test.h" #include "import_test.h" int main() { try { auto r = sw::redis::Redis("tcp://127.0.0.1"); sw::redis::pb::test::AppendTest append_test(r); append_test.run(); sw::redis::pb::test::ClearTest clear_test(r); clear_test.run(); sw::redis::pb::test::DelTest del_test(r); del_test.run(); sw::redis::pb::test::TypeTest type_test(r); type_test.run(); sw::redis::pb::test::SchemaTest schema_test(r); schema_test.run(); sw::redis::pb::test::SetGetTest set_get_test(r); set_get_test.run(); sw::redis::pb::test::LenTest len_test(r); len_test.run(); sw::redis::pb::test::MergeTest merge_test(r); merge_test.run(); sw::redis::pb::test::ImportTest import_test(r); import_test.run(); std::cout << "pass all tests" << std::endl; } catch (const sw::redis::Error &e) { std::cerr << "failed to do test: " << e.what() << std::endl; } return 0; } ================================================ FILE: test/src/sw/redis-protobuf/type_test.cpp ================================================ /************************************************************************** Copyright (c) 2022 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 "type_test.h" #include "utils.h" namespace sw { namespace redis { namespace pb { namespace test { void TypeTest::_run(sw::redis::Redis &r) { auto key = test_key("type"); KeyDeleter deleter(r, key); REDIS_ASSERT(r.command("PB.SET", key, "Msg", R"({"i" : 1})") == 1, "failed to test pb.type command"); auto type = r.command("PB.TYPE", key); REDIS_ASSERT(type && *type == "Msg", "failed to test pb.type command"); type = r.command("PB.TYPE", "sw.redis.pb.not-exist-Msg-type"); REDIS_ASSERT(!type, "failed to test pb.type command"); } } } } } ================================================ FILE: test/src/sw/redis-protobuf/type_test.h ================================================ /************************************************************************** Copyright (c) 2022 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_REDISPROTOBUF_TEST_TYPE_TEST_H #define SEWENEW_REDISPROTOBUF_TEST_TYPE_TEST_H #include "proto_test.h" namespace sw { namespace redis { namespace pb { namespace test { class TypeTest : public ProtoTest { public: explicit TypeTest(sw::redis::Redis &r) : ProtoTest("PB.TYPE", r) {} private: virtual void _run(sw::redis::Redis &r) override; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_TYPE_TEST_H ================================================ FILE: test/src/sw/redis-protobuf/utils.h ================================================ /************************************************************************** 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_REDISPROTOBUF_TEST_UTILS_H #define SEWENEW_REDISPROTOBUF_TEST_UTILS_H #include #include #include #define REDIS_ASSERT(condition, msg) \ sw::redis::pb::test::redis_assert((condition), (msg), __FILE__, __LINE__) namespace sw { namespace redis { namespace pb { namespace test { inline void redis_assert(bool condition, const std::string &msg, const std::string &file, int line) { if (!condition) { auto err_msg = "ASSERT: " + msg + ". " + file + ":" + std::to_string(line); throw Error(err_msg); } } inline std::string key_prefix(const std::string &key = "") { static std::string KEY_PREFIX = "sw::redis::test"; if (!key.empty()) { KEY_PREFIX = key; } return KEY_PREFIX; } inline std::string test_key(const std::string &k) { // Key prefix with hash tag, // so that we can call multiple-key commands on RedisCluster. return "{" + key_prefix() + "}::" + k; } class KeyDeleter { public: template KeyDeleter(sw::redis::Redis &redis, Input first, Input last) : _redis(redis), _keys(first, last) { _delete(); } KeyDeleter(sw::redis::Redis &redis, std::initializer_list il) : KeyDeleter(redis, il.begin(), il.end()) {} KeyDeleter(sw::redis::Redis &redis, const std::string &key) : KeyDeleter(redis, {key}) {} ~KeyDeleter() { _delete(); } private: void _delete() { if (!_keys.empty()) { _redis.del(_keys.begin(), _keys.end()); } } sw::redis::Redis &_redis; std::vector _keys; }; } } } } #endif // end SEWENEW_REDISPROTOBUF_TEST_UTILS_H