Full Code of sewenew/redis-protobuf for AI

master c3329b508f53 cached
77 files
285.5 KB
72.1k tokens
172 symbols
1 requests
Download .txt
Showing preview only (307K chars total). Download the full file or copy to clipboard to get everything.
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微信交流群

<img src="https://github.com/sewenew/data/blob/main/imgs/redis-plus-plus-wechat.jpg?raw=true" width="20%" height="20%"/>


================================================
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<long long>("PB.SET", "key", "Msg", s) == 1);

    // Get the message in binary format.
    s = redis.command<std::string>("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<long long>("PB.SET", "key", "Msg", "/i", 10) == 1);
    assert(redis.command<long long>("PB.GET", "key", "Msg", "/i") == 10);

    // Set and get a nested message field.
    assert(redis.command<long long>("PB.SET", "key", "Msg", "/sub/s", "redis-protobuf") == 1);
    assert(redis.command<std::string>("PB.GET", "key", "Msg", "/sub/s") == "redis-protobuf");

    // Delete the message.
    assert(redis.command<long long>("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<string, SubMsg> 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<string, string> 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": <string>,
//       "lastName": <string>
//     }
//
// 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<string, Value> 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<StringView> &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<StringView> &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 <string>
#include <vector>
#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<StringView> elements;
    };

    Args _parse_args(RedisModuleString **argv, int argc) const;

    long long _append(MutableFieldRef &field, const std::vector<StringView> &elements) const;

    void _append_arr(MutableFieldRef &field, const StringView &val) const;

    long long _append_str(MutableFieldRef &field, const std::vector<StringView> &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 <vector>
#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 <string>
#include <vector>
#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 <exception>
#include <string>

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 <cassert>
#include <string>
#include <type_traits>
#include <vector>
#include <google/protobuf/message.h>
#include <google/protobuf/map_field.h>
#include <google/protobuf/map.h>
#include "module_api.h"
#include "utils.h"
#include "path.h"

namespace sw {

namespace redis {

namespace pb {

namespace gp = google::protobuf;

template <typename Msg>
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<gp::Map<gp::MapKey, gp::MapValueRef>::const_iterator,
            gp::Map<gp::MapKey, gp::MapValueRef>::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<Msg>::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<Msg>::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<const gp::internal::GeneratedMessageReflection*>(msg->GetReflection());
        const auto &map_base = reflection->GetRaw<gp::internal::MapFieldBase>(*msg, field_desc);
        const auto &dynamic_map = static_cast<const gp::internal::DynamicMapField&>(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<const gp::internal::GeneratedMessageReflection*>(msg->GetReflection());
        auto *map_base = reflection->MutableRaw<gp::internal::MapFieldBase>(msg, field_desc);
        auto *dynamic_map = static_cast<gp::internal::DynamicMapField*>(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<Msg>::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<gp::MapKey> _parse_map_key(const std::string &key) {
        return _parse_map_key_impl(key, typename std::is_const<Msg>::type());
    }

    Optional<gp::MapKey> _parse_map_key_impl(const std::string &key);

    Optional<gp::MapKey> _parse_map_key_impl(const std::string &key, std::true_type);

    Optional<gp::MapKey> _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<gp::MapKey> _map_key;
};

using ConstFieldRef = FieldRef<const gp::Message>;
using MutableFieldRef = FieldRef<gp::Message>;

template <typename Msg>
FieldRef<Msg>::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 <typename Msg>
FieldRef<Msg> FieldRef<Msg>::get_array_element(int idx) const {
    assert(is_array() && idx < size());

    FieldRef<Msg> element(*this);
    element._arr_idx = idx;

    return element;
}

template <typename Msg>
auto FieldRef<Msg>::get_map_range() const ->
    std::pair<gp::Map<gp::MapKey, gp::MapValueRef>::const_iterator,
        gp::Map<gp::MapKey, gp::MapValueRef>::const_iterator> {
    assert(is_map());

    // The following is hacking, hacking, and hacking!!!
    const auto *reflection =
        static_cast<const gp::internal::GeneratedMessageReflection*>(_msg->GetReflection());
    const auto &map_base = reflection->GetRaw<gp::internal::MapFieldBase>(*_msg, _field_desc);
    const auto &dynamic_map = static_cast<const gp::internal::DynamicMapField&>(map_base);
    const auto &m = dynamic_map.GetMap();

    return {m.begin(), m.end()};
}

template <typename Msg>
std::string FieldRef<Msg>::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 <typename Msg>
std::string FieldRef<Msg>::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 <typename Msg>
int FieldRef<Msg>::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 <typename Msg>
void FieldRef<Msg>::_validate_parameters(Msg *root_msg, const Path &path) const {
    assert(root_msg != nullptr);

    if (root_msg->GetTypeName() != path.type()) {
        throw Error("type missmatch");
    }
}

template <typename Msg>
void FieldRef<Msg>::_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 <typename Msg>
Optional<gp::MapKey> FieldRef<Msg>::_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 <typename Msg>
Optional<gp::MapKey> FieldRef<Msg>::_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<gp::MapKey>(map_key);
}

template <typename Msg>
void FieldRef<Msg>::set_int32(int32_t val) {
    _msg->GetReflection()->SetInt32(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_int64(int64_t val) {
    _msg->GetReflection()->SetInt64(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_uint32(uint32_t val) {
    _msg->GetReflection()->SetUInt32(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_uint64(uint64_t val) {
    _msg->GetReflection()->SetUInt64(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_float(float val) {
    _msg->GetReflection()->SetFloat(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_double(double val) {
    _msg->GetReflection()->SetDouble(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_bool(bool val) {
    _msg->GetReflection()->SetBool(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_enum(int val) {
    _msg->GetReflection()->SetEnumValue(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::set_string(const std::string &val) {
    _msg->GetReflection()->SetString(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::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 <typename Msg>
void FieldRef<Msg>::set_repeated_int32(int32_t val) {
    _msg->GetReflection()->SetRepeatedInt32(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_int64(int64_t val) {
    _msg->GetReflection()->SetRepeatedInt64(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_uint32(uint32_t val) {
    _msg->GetReflection()->SetRepeatedUInt32(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_uint64(uint64_t val) {
    _msg->GetReflection()->SetRepeatedUInt64(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_float(float val) {
    _msg->GetReflection()->SetRepeatedFloat(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_double(double val) {
    _msg->GetReflection()->SetRepeatedDouble(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_bool(bool val) {
    _msg->GetReflection()->SetRepeatedBool(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_enum(int val) {
    _msg->GetReflection()->SetRepeatedEnumValue(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::set_repeated_string(const std::string &val) {
    _msg->GetReflection()->SetRepeatedString(_msg, _field_desc, _arr_idx, val);
}

template <typename Msg>
void FieldRef<Msg>::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 <typename Msg>
void FieldRef<Msg>::add_int32(int32_t val) {
    _msg->GetReflection()->AddInt32(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_int64(int64_t val) {
    _msg->GetReflection()->AddInt64(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_uint32(uint32_t val) {
    _msg->GetReflection()->AddUInt32(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_uint64(uint64_t val) {
    _msg->GetReflection()->AddUInt64(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_float(float val) {
    _msg->GetReflection()->AddFloat(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_double(double val) {
    _msg->GetReflection()->AddDouble(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_bool(bool val) {
    _msg->GetReflection()->AddBool(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_enum(int val) {
    _msg->GetReflection()->AddEnumValue(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::add_string(const std::string &val) {
    _msg->GetReflection()->AddString(_msg, _field_desc, val);
}

template <typename Msg>
void FieldRef<Msg>::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 <typename Msg>
void FieldRef<Msg>::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 <typename Msg>
void FieldRef<Msg>::del() {
    if (is_array_element()) {
        _del_array_element();
    } else {
        // TODO: support map
        throw Error("can only delete array element");
    }
}

template <typename Msg>
void FieldRef<Msg>::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 <typename Msg>
void FieldRef<Msg>::_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 <cassert>
#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 <string>
#include <unordered_map>
#include <vector>
#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 <cassert>
#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 <string>
#include <unordered_map>
#include <vector>
#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 <vector>
#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 <cassert>

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<RedisModuleKey *>(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<google::protobuf::Message *>(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 <memory>
#include <google/protobuf/message.h>
#include "errors.h"

namespace sw {

namespace redis {

namespace pb {

namespace api {

template <typename ...Args>
void warning(RedisModuleCtx *ctx, const char *fmt, Args &&...args) {
    RedisModule_Log(ctx, "warning", fmt, std::forward<Args>(args)...);
}

template <typename ...Args>
void notice(RedisModuleCtx *ctx, const char *fmt, Args &&...args) {
    RedisModule_Log(ctx, "notice", fmt, std::forward<Args>(args)...);
}

template <typename ...Args>
void debug(RedisModuleCtx *ctx, const char *fmt, Args &&...args) {
    RedisModule_Log(ctx, "debug", fmt, std::forward<Args>(args)...);
}

template <typename ...Args>
void verbose(RedisModuleCtx *ctx, const char *fmt, Args &&...args) {
    RedisModule_Log(ctx, "verbose", fmt, std::forward<Args>(args)...);
}

struct RedisKeyCloser {
    void operator()(RedisModuleKey *key) const {
        RedisModule_CloseKey(key);
    }
};

using RedisKey = std::unique_ptr<RedisModuleKey, RedisKeyCloser>;

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 <cassert>
#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 <vector>
#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 <string>

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<std::string> 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<std::string> 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 <vector>
#include <string>
#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<std::string>& fields() const {
        return _fields;
    }

    bool empty() const {
        return _fields.empty();
    }

private:
    std::vector<std::string> _parse_fields(const StringView &path) const;

    std::string _type;

    std::vector<std::string> _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 <fstream>
#include <google/protobuf/util/json_util.h>
#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<std::mutex> lock(_mtx);

        _tasks[filename] = content;
    }

    _cv.notify_one();
}

std::unordered_map<std::string, std::string> ProtoFactory::last_loaded() {
    std::unordered_map<std::string, std::string> last_loaded_files;
    {
        std::lock_guard<std::mutex> 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<std::string, std::string> tasks;
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _cv.wait(lock, [this]() { return this->_stop_loader || !(this->_tasks).empty(); });

            tasks.swap(_tasks);
        }

        std::unordered_map<std::string, std::string> 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<std::mutex> 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 <string>
#include <atomic>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <condition_variable>
#include <unordered_map>
#include <unordered_set>
#include <google/protobuf/message.h>
#include <google/protobuf/compiler/importer.h>
#include <google/protobuf/dynamic_message.h>
#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<std::string> _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<std::string, std::string> 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<std::string, const gp::Descriptor*> _descriptor_cache;

    std::unordered_set<std::string> _loaded_files;

    std::mutex _mtx;

    std::condition_variable _cv;

    // map<file, content>
    std::unordered_map<std::string, std::string> _tasks;

    // map<file, load status>
    std::unordered_map<std::string, std::string> _last_loaded_files;

    std::atomic<bool> _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 <cassert>
#include <string>
#include <google/protobuf/message.h>
#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<char, StringDeleter>;

struct RDBString {
    StringUPtr str;
    std::size_t len;
};

RDBString rdb_load_string(RedisModuleIO *rdb);

std::pair<RDBString, RDBString> rdb_load_value(RedisModuleIO *rdb);

std::pair<std::string, std::string> 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<ProtoFactory>(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<google::protobuf::Message *>(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<RDBString, RDBString> 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<std::string, std::string> serialize_message(void *value) {
    if (value == nullptr) {
        throw Error("Null value to serialize");
    }

    auto *msg = static_cast<google::protobuf::Message*>(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<ProtoFactory> _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(RedisModu
Download .txt
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
Download .txt
SYMBOL INDEX (172 symbols across 62 files)

FILE: src/sw/redis-protobuf/append_command.cpp
  type sw (line 21) | namespace sw {
    type redis (line 23) | namespace redis {
      type pb (line 25) | namespace pb {

FILE: src/sw/redis-protobuf/append_command.h
  function namespace (line 26) | namespace sw {

FILE: src/sw/redis-protobuf/clear_command.cpp
  type sw (line 21) | namespace sw {
    type redis (line 23) | namespace redis {
      type pb (line 25) | namespace pb {

FILE: src/sw/redis-protobuf/clear_command.h
  function namespace (line 25) | namespace sw {

FILE: src/sw/redis-protobuf/commands.cpp
  type sw (line 30) | namespace sw {
    type redis (line 32) | namespace redis {
      type pb (line 34) | namespace pb {
        type cmd (line 36) | namespace cmd {
          function create_commands (line 38) | void create_commands(RedisModuleCtx *ctx) {

FILE: src/sw/redis-protobuf/commands.h
  function namespace (line 22) | namespace sw {

FILE: src/sw/redis-protobuf/del_command.cpp
  type sw (line 21) | namespace sw {
    type redis (line 23) | namespace redis {
      type pb (line 25) | namespace pb {

FILE: src/sw/redis-protobuf/del_command.h
  function namespace (line 26) | namespace sw {

FILE: src/sw/redis-protobuf/errors.h
  function namespace (line 23) | namespace sw {

FILE: src/sw/redis-protobuf/field_ref.h
  function namespace (line 31) | namespace sw {

FILE: src/sw/redis-protobuf/get_command.cpp
  type sw (line 23) | namespace sw {
    type redis (line 25) | namespace redis {
      type pb (line 27) | namespace pb {

FILE: src/sw/redis-protobuf/get_command.h
  function namespace (line 24) | namespace sw {

FILE: src/sw/redis-protobuf/import_command.cpp
  type sw (line 23) | namespace sw {
    type redis (line 25) | namespace redis {
      type pb (line 27) | namespace pb {

FILE: src/sw/redis-protobuf/import_command.h
  function namespace (line 27) | namespace sw {

FILE: src/sw/redis-protobuf/last_import_command.cpp
  type sw (line 23) | namespace sw {
    type redis (line 25) | namespace redis {
      type pb (line 27) | namespace pb {

FILE: src/sw/redis-protobuf/last_import_command.h
  function namespace (line 27) | namespace sw {

FILE: src/sw/redis-protobuf/len_command.cpp
  type sw (line 23) | namespace sw {
    type redis (line 25) | namespace redis {
      type pb (line 27) | namespace pb {

FILE: src/sw/redis-protobuf/len_command.h
  function namespace (line 24) | namespace sw {

FILE: src/sw/redis-protobuf/merge_command.cpp
  type sw (line 24) | namespace sw {
    type redis (line 26) | namespace redis {
      type pb (line 28) | namespace pb {

FILE: src/sw/redis-protobuf/merge_command.h
  function namespace (line 25) | namespace sw {

FILE: src/sw/redis-protobuf/module_api.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type api (line 26) | namespace api {
          function RedisKey (line 28) | RedisKey open_key(RedisModuleCtx *ctx, RedisModuleString *name, ...
          function key_exists (line 54) | bool key_exists(RedisModuleKey *key, RedisModuleType *key_type) {
          function reply_with_error (line 68) | int reply_with_error(RedisModuleCtx *ctx, const Error &err) {

FILE: src/sw/redis-protobuf/module_api.h
  function namespace (line 38) | namespace sw {
  type RedisKeyCloser (line 66) | struct RedisKeyCloser {
  function KeyMode (line 74) | enum class KeyMode {

FILE: src/sw/redis-protobuf/module_entry.cpp
  function RedisModule_OnLoad (line 23) | int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, in...

FILE: src/sw/redis-protobuf/options.cpp
  type sw (line 23) | namespace sw {
    type redis (line 25) | namespace redis {
      type pb (line 27) | namespace pb {

FILE: src/sw/redis-protobuf/options.h
  function namespace (line 23) | namespace sw {

FILE: src/sw/redis-protobuf/path.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {

FILE: src/sw/redis-protobuf/path.h
  function namespace (line 24) | namespace sw {

FILE: src/sw/redis-protobuf/proto_factory.cpp
  type sw (line 23) | namespace sw {
    type redis (line 25) | namespace redis {
      type pb (line 27) | namespace pb {
        function MsgUPtr (line 73) | MsgUPtr ProtoFactory::create(const std::string &type) {
        function MsgUPtr (line 86) | MsgUPtr ProtoFactory::create(const std::string &type, const String...

FILE: src/sw/redis-protobuf/proto_factory.h
  function namespace (line 33) | namespace sw {

FILE: src/sw/redis-protobuf/redis_protobuf.cpp
  type StringDeleter (line 26) | struct StringDeleter {
  type RDBString (line 36) | struct RDBString {
  type sw (line 49) | namespace sw {
    type redis (line 51) | namespace redis {
      type pb (line 53) | namespace pb {
        function RedisProtobuf (line 55) | RedisProtobuf& RedisProtobuf::instance() {
  function RDBString (line 190) | RDBString rdb_load_string(RedisModuleIO *rdb) {
  function rdb_load_value (line 200) | std::pair<RDBString, RDBString> rdb_load_value(RedisModuleIO *rdb) {
  function serialize_message (line 208) | std::pair<std::string, std::string> serialize_message(void *value) {

FILE: src/sw/redis-protobuf/redis_protobuf.h
  function namespace (line 24) | namespace sw {

FILE: src/sw/redis-protobuf/redismodule.h
  type mstime_t (line 105) | typedef long long mstime_t;
  type RedisModuleCtx (line 108) | typedef struct RedisModuleCtx RedisModuleCtx;
  type RedisModuleKey (line 109) | typedef struct RedisModuleKey RedisModuleKey;
  type RedisModuleString (line 110) | typedef struct RedisModuleString RedisModuleString;
  type RedisModuleCallReply (line 111) | typedef struct RedisModuleCallReply RedisModuleCallReply;
  type RedisModuleIO (line 112) | typedef struct RedisModuleIO RedisModuleIO;
  type RedisModuleType (line 113) | typedef struct RedisModuleType RedisModuleType;
  type RedisModuleDigest (line 114) | typedef struct RedisModuleDigest RedisModuleDigest;
  type RedisModuleBlockedClient (line 115) | typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
  type RedisModuleTypeMethods (line 127) | typedef struct RedisModuleTypeMethods {
  function RedisModule_Init (line 261) | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int v...

FILE: src/sw/redis-protobuf/schema_command.cpp
  type sw (line 22) | namespace sw {
    type redis (line 24) | namespace redis {
      type pb (line 26) | namespace pb {

FILE: src/sw/redis-protobuf/schema_command.h
  function namespace (line 24) | namespace sw {

FILE: src/sw/redis-protobuf/set_command.cpp
  type sw (line 23) | namespace sw {
    type redis (line 25) | namespace redis {
      type pb (line 27) | namespace pb {

FILE: src/sw/redis-protobuf/set_command.h
  function namespace (line 27) | namespace sw {

FILE: src/sw/redis-protobuf/type_command.cpp
  type sw (line 22) | namespace sw {
    type redis (line 24) | namespace redis {
      type pb (line 26) | namespace pb {

FILE: src/sw/redis-protobuf/type_command.h
  function namespace (line 25) | namespace sw {

FILE: src/sw/redis-protobuf/utils.cpp
  type sw (line 32) | namespace sw {
    type redis (line 34) | namespace redis {
      type pb (line 36) | namespace pb {
        type util (line 46) | namespace util {
          function msg_to_json (line 48) | std::string msg_to_json(const gp::Message &msg) {
          function sv_to_int32 (line 58) | int32_t sv_to_int32(const StringView &sv) {
          function sv_to_int64 (line 66) | int64_t sv_to_int64(const StringView &sv) {
          function sv_to_uint32 (line 74) | uint32_t sv_to_uint32(const StringView &sv) {
          function sv_to_uint64 (line 83) | uint64_t sv_to_uint64(const StringView &sv) {
          function sv_to_double (line 91) | double sv_to_double(const StringView &sv) {
          function sv_to_float (line 99) | float sv_to_float(const StringView &sv) {
          function sv_to_bool (line 107) | bool sv_to_bool(const StringView &sv) {
          function sv_to_string (line 131) | std::string sv_to_string(const StringView &sv) {
          function str_case_equal (line 135) | bool str_case_equal(const StringView &s1, const StringView &s2) {
        type io (line 154) | namespace io {
          function is_regular (line 156) | bool is_regular(const std::string &file) {
          function is_directory (line 160) | bool is_directory(const std::string &file) {
          function list_dir (line 164) | std::vector<std::string> list_dir(const std::string &path) {
          function extension (line 198) | std::string extension(const std::string &file) {
          function remove_file (line 207) | void remove_file(const std::string &path) {
  function mode_t (line 221) | mode_t file_type(const std::string &file) {

FILE: src/sw/redis-protobuf/utils.h
  function namespace (line 27) | namespace sw {

FILE: test/src/sw/redis-protobuf/append_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/append_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/clear_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/clear_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/del_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/del_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/import_test.cpp
  type sw (line 24) | namespace sw {
    type redis (line 26) | namespace redis {
      type pb (line 28) | namespace pb {
        type test (line 30) | namespace test {

FILE: test/src/sw/redis-protobuf/import_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/len_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/len_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/merge_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/merge_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/proto_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/proto_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/schema_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/schema_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/set_get_test.cpp
  type sw (line 21) | namespace sw {
    type redis (line 23) | namespace redis {
      type pb (line 25) | namespace pb {
        type test (line 27) | namespace test {

FILE: test/src/sw/redis-protobuf/set_get_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/test_main.cpp
  function main (line 29) | int main() {

FILE: test/src/sw/redis-protobuf/type_test.cpp
  type sw (line 20) | namespace sw {
    type redis (line 22) | namespace redis {
      type pb (line 24) | namespace pb {
        type test (line 26) | namespace test {

FILE: test/src/sw/redis-protobuf/type_test.h
  function namespace (line 22) | namespace sw {

FILE: test/src/sw/redis-protobuf/utils.h
  function namespace (line 27) | namespace sw {
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (309K chars).
[
  {
    "path": ".gitignore",
    "chars": 270,
    "preview": "# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 1811,
    "preview": "cmake_minimum_required(VERSION 3.1)\n\nset(REDIS_PROTOBUF_VERSION \"0.1.0\")\nmessage(STATUS \"redis-protobuf version: ${REDIS"
  },
  {
    "path": "Chinese.md",
    "chars": 146,
    "preview": "欢迎加入redis-protobuf微信交流群\n\n<img src=\"https://github.com/sewenew/data/blob/main/imgs/redis-plus-plus-wechat.jpg?raw=true\" w"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 23837,
    "preview": "# redis-protobuf\n\n[中文交流群](http://github.com/sewenew/redis-protobuf/blob/master/Chinese.md)\n\n- [Overview](#overview)\n    "
  },
  {
    "path": "docker/Dockerfile",
    "chars": 1836,
    "preview": "From redis:latest\n\nENV LIBDIR /usr/lib/redis/modules\nENV DEPS \"make g++ curl cmake unzip\"\n\n# Install dependencies\nRUN se"
  },
  {
    "path": "docker/example.proto",
    "chars": 211,
    "preview": "syntax = \"proto3\";\n\n//package sw.redis.pb;\n\nmessage SubMsg {\n    string s = 1;\n    int32 i = 2;\n}\n\nmessage Msg {\n    int"
  },
  {
    "path": "docker/google/protobuf/any.proto",
    "chars": 5878,
    "preview": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://de"
  },
  {
    "path": "docker/google/protobuf/duration.proto",
    "chars": 4889,
    "preview": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://de"
  },
  {
    "path": "docker/google/protobuf/empty.proto",
    "chars": 2422,
    "preview": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://de"
  },
  {
    "path": "docker/google/protobuf/struct.proto",
    "chars": 3780,
    "preview": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://de"
  },
  {
    "path": "docker/google/protobuf/timestamp.proto",
    "chars": 6200,
    "preview": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://de"
  },
  {
    "path": "docker/google/protobuf/wrappers.proto",
    "chars": 4035,
    "preview": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://de"
  },
  {
    "path": "src/sw/redis-protobuf/append_command.cpp",
    "chars": 5563,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/append_command.h",
    "chars": 2060,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/clear_command.cpp",
    "chars": 2481,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/clear_command.h",
    "chars": 1604,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/commands.cpp",
    "chars": 5910,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/commands.h",
    "chars": 1014,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/del_command.cpp",
    "chars": 2724,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/del_command.h",
    "chars": 1614,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/errors.h",
    "chars": 2607,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/field_ref.h",
    "chars": 27497,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/get_command.cpp",
    "chars": 14533,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/get_command.h",
    "chars": 3260,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/import_command.cpp",
    "chars": 1838,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/import_command.h",
    "chars": 1466,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/last_import_command.cpp",
    "chars": 1848,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/last_import_command.h",
    "chars": 1488,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/len_command.cpp",
    "chars": 2896,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/len_command.h",
    "chars": 1929,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/merge_command.cpp",
    "chars": 3199,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/merge_command.h",
    "chars": 1882,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/module_api.cpp",
    "chars": 2257,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/module_api.h",
    "chars": 2451,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/module_entry.cpp",
    "chars": 1231,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/module_entry.h",
    "chars": 1057,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/options.cpp",
    "chars": 1567,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/options.h",
    "chars": 1065,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/path.cpp",
    "chars": 1750,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/path.h",
    "chars": 1525,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/proto_factory.cpp",
    "chars": 6848,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/proto_factory.h",
    "chars": 4055,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/redis_protobuf.cpp",
    "chars": 5715,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/redis_protobuf.h",
    "chars": 2261,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/redismodule.cpp",
    "chars": 10846,
    "preview": "// This file is copied from Redis 4.0, and I splited the original header into\n// a header file and a cpp file, i.e. redi"
  },
  {
    "path": "src/sw/redis-protobuf/redismodule.h",
    "chars": 21600,
    "preview": "// This file is copied from Redis 4.0, and I splited the original header into\n// a header file and a cpp file, i.e. redi"
  },
  {
    "path": "src/sw/redis-protobuf/schema_command.cpp",
    "chars": 2581,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/schema_command.h",
    "chars": 1483,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/set_command.cpp",
    "chars": 16896,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/set_command.h",
    "chars": 5101,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/type_command.cpp",
    "chars": 2403,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/type_command.h",
    "chars": 1461,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/utils.cpp",
    "chars": 5356,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "src/sw/redis-protobuf/utils.h",
    "chars": 3758,
    "preview": "/**************************************************************************\n   Copyright (c) 2019 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/append_test.cpp",
    "chars": 2191,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/append_test.h",
    "chars": 1184,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/clear_test.cpp",
    "chars": 2345,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/clear_test.h",
    "chars": 1178,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/del_test.cpp",
    "chars": 1965,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/del_test.h",
    "chars": 1166,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/import_test.cpp",
    "chars": 1768,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/import_test.h",
    "chars": 1184,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/len_test.cpp",
    "chars": 1600,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/len_test.h",
    "chars": 1166,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/merge_test.cpp",
    "chars": 1883,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/merge_test.h",
    "chars": 1178,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/proto_test.cpp",
    "chars": 988,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/proto_test.h",
    "chars": 1362,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/schema_test.cpp",
    "chars": 1240,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/schema_test.h",
    "chars": 1184,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/set_get_test.cpp",
    "chars": 3099,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/set_get_test.h",
    "chars": 1194,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/test_main.cpp",
    "chars": 1996,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/type_test.cpp",
    "chars": 1429,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/type_test.h",
    "chars": 1172,
    "preview": "/**************************************************************************\n   Copyright (c) 2022 sewenew\n\n   Licensed u"
  },
  {
    "path": "test/src/sw/redis-protobuf/utils.h",
    "chars": 2533,
    "preview": "/**************************************************************************\n   Copyright (c) 2017 sewenew\n\n   Licensed u"
  }
]

About this extraction

This page contains the full source code of the sewenew/redis-protobuf GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (285.5 KB), approximately 72.1k tokens, and a symbol index with 172 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!