Skip to content

Commit

Permalink
Adding documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
TalZaccai committed May 9, 2024
1 parent 56bcdaf commit 7188887
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 40 deletions.
19 changes: 12 additions & 7 deletions playground/CommandInfoUpdater/CommandInfoUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace CommandInfoUpdater
{
/// <summary>
/// Main logic for CommandInfoUpdater tool
/// </summary>
public class CommandInfoUpdater
{
private static readonly string GarnetCommandInfoJsonPath = "GarnetCommandsInfo.json";
Expand All @@ -22,6 +25,7 @@ public class CommandInfoUpdater
/// <param name="respServerPort">RESP server port to query commands info</param>
/// <param name="respServerHost">RESP server host to query commands info</param>
/// <param name="ignoreCommands">Commands to ignore</param>
/// <param name="force">Force update all commands</param>
/// <param name="logger">Logger</param>
/// <returns>True if file generated successfully</returns>
public static bool TryUpdateCommandInfo(string outputPath, int respServerPort, IPAddress respServerHost,
Expand All @@ -40,7 +44,7 @@ public static bool TryUpdateCommandInfo(string outputPath, int respServerPort, I
var (commandsToAdd, commandsToRemove) =
GetCommandsToAddAndRemove(existingCommandsInfo, ignoreCommands);

if (!GetUserConfirmation(existingCommandsInfo, commandsToAdd, commandsToRemove, logger))
if (!GetUserConfirmation(commandsToAdd, commandsToRemove, logger))
{
logger.LogInformation($"User cancelled update operation.");
return false;
Expand Down Expand Up @@ -202,13 +206,11 @@ private static (IDictionary<SupportedCommand, bool>, IDictionary<SupportedComman
/// <summary>
/// Indicates to the user which commands and sub-commands are added / removed and get their confirmation to proceed
/// </summary>
/// <param name="existingCommandsInfo">Existing command names mapped to current command info</param>
/// <param name="commandsToAdd">Commands to add</param>
/// <param name="commandsToRemove">Commands to remove</param>
/// <param name="logger">Logger</param>
/// <returns>True if user wishes to continue, false otherwise</returns>
private static bool GetUserConfirmation(IReadOnlyDictionary<string, RespCommandsInfo> existingCommandsInfo,
IDictionary<SupportedCommand, bool> commandsToAdd, IDictionary<SupportedCommand, bool> commandsToRemove,
private static bool GetUserConfirmation(IDictionary<SupportedCommand, bool> commandsToAdd, IDictionary<SupportedCommand, bool> commandsToRemove,
ILogger logger)
{
var logCommandsToAdd = commandsToAdd.Where(kvp => kvp.Value).Select(c => c.Key.Command).ToList();
Expand Down Expand Up @@ -302,13 +304,16 @@ private static unsafe bool TryGetCommandsInfo(string[] commandsToQuery, int resp
var end = ptr + response.Length;

// Read the array length (# of commands info returned)
RespReadUtils.ReadArrayLength(out var cmdCount, ref ptr, end);
if (!RespReadUtils.ReadArrayLength(out var cmdCount, ref ptr, end))
{
logger.LogError($"Unable to read RESP command info count from server");
return false;
}

// Parse each command's command info
for (var cmdIdx = 0; cmdIdx < cmdCount; cmdIdx++)
{
if (!RespCommandInfoParser.TryReadFromResp(ref ptr, end, supportedCommands,
out RespCommandsInfo command) ||
if (!RespCommandInfoParser.TryReadFromResp(ref ptr, end, supportedCommands, out var command) ||
command == null)
{
logger.LogError(
Expand Down
15 changes: 11 additions & 4 deletions playground/CommandInfoUpdater/SupportedCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,20 @@ public SupportedCommand()

}

/// <summary>
/// SupportedCommand constructor
/// </summary>
/// <param name="command">Supported command name</param>
/// <param name="respCommand">RESP Command enum</param>
/// <param name="arrayCommand">Array Command byte (if applicable)</param>
/// <param name="subCommands">List of supported sub-command names (optional)</param>
public SupportedCommand(string command, RespCommand respCommand = RespCommand.NONE, byte? arrayCommand = null,
IEnumerable<string> subCommands = null) : this()
{
this.Command = command;
this.SubCommands = subCommands == null ? null : new HashSet<string>(subCommands);
this.RespCommand = respCommand;
this.ArrayCommand = arrayCommand;
Command = command;
SubCommands = subCommands == null ? null : new HashSet<string>(subCommands);
RespCommand = respCommand;
ArrayCommand = arrayCommand;
}
}
}
6 changes: 6 additions & 0 deletions website/docs/commands/api-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ Note that this list is subject to change as we continue to expand our API comman
| | BGREWRITEAOF || |
| | [BGSAVE](checkpoint.md#bgsave) || |
| | [COMMAND](server.md#command) || |
| | [COMMAND COUNT](server.md#command-count) || |
| | COMMAND DOCS || |
| | COMMAND GETKEYS || |
| | COMMAND GETKEYSANDFLAGS || |
| | [COMMAND INFO](server.md#command-info) || |
| | COMMAND LIST || |
| | [COMMITAOF](server.md#commitaof) || |
| | [CONFIG GET](server.md#config-get) || |
| | [CONFIG SET](server.md#config-set) || |
Expand Down
30 changes: 29 additions & 1 deletion website/docs/commands/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,35 @@ slug: server
COMMAND
```

Return an array with details about every Redis command.
Return an array with details about every Garnet command.

#### Resp Reply

Array reply: a nested list of command details.

---
### COMMAND COUNT
#### Syntax

```bash
COMMAND COUNT
```

Returns Integer reply of number of total commands in this Garnet server.

#### Resp Reply

Integer reply: the number of commands returned by COMMAND.

---
### COMMAND INFO
#### Syntax

```bash
COMMAND
```

Return an array with details about every Garnet command.

#### Resp Reply

Expand Down
60 changes: 32 additions & 28 deletions website/docs/dev/garnet-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,45 @@ sidebar_label: Garnet API
title: Garnet API
---

The IGarnetAPI interface contains the operators exposed to the public API, which ultimately perform operations over the keys stored in Garnet. It inherits from IGarnetReadApi and IGarnetAdvancedApi.
The **IGarnetApi** interface contains the operators exposed to the public API, which ultimately perform operations over the keys stored in Garnet. It inherits from **IGarnetReadApi** (read-only commands interface) and **IGarnetAdvancedApi** (advanced API calls).

For adding an new operator or command to the API, you should add a new method to the IGarnetApi, if this is a write operation, or to the IGarnetReadApi in the case of a read one.
For adding an new operator or command to the API, add a new method signature to the **IGarnetReadApi** interface in case the command performs read-only operations, or **IGarnetApi** otherwise.

### Adding a new command
### Adding a new command to Garnet

A new command is implemented with the following methods: (See ZADD for further details)
_If you are trying to add a command for your specific Garnet server instance, see [Custom Commands](custom_commands.md)_

* A private unsafe bool Network[CommandName] in the internal class RespServerSession.
To add a new command to Garnet, follow these steps:

**Example**
1. If your command operates on an **object** (i.e. List, SortedSet etc.), add a new enum value to the ```[ObjectName]Operation``` enum in ```Garnet.server/Objects/[ObjectName]/[ObjectName]Object.cs```\
Otherwise, add a new enum value to the ```RespCommand``` enum in ```Garnet.server/Resp/RespCommand.cs```.
2. Add the parsing logic of the new command name to ```Garnet.server/Resp/RespCommand.cs```. If the command has a **fixed number of arguments**, add parsing logic to the **FastParseCommand** method. Otherwise, add parsing logic to the **FastParseArrayCommand** method.
3. Add a new method signature to **IGarnetReadApi**, in case the command performs read-only operations, or to **IGarnetApi** otherwise (```Garnet.server/API/IGarnetAPI.cs```).
4. Add a new method to the **RespServerSession** class. This method will parse the command from the network buffer, call the storage layer API (method declared in step #3) and write the RESP formatted response back to the network buffer (note that the **RespServerSession** class is divided across several .cs files, object-specific commands will reside under ```Garnet.server/Resp/Objects/[ObjectName]Commands.cs```, while others will reside under ```Garnet.server/Resp/[Admin|Array|Basic|etc...]Commands.cs```, depending on the command type).
5. Back in ```Garnet.server/Resp/RespCommand.cs```, add the new command case to the **ProcessBasicCommands** or **ProcessArrayCommands** method respectively, calling the method that was added in step #4.
6. Add a new method to the **StorageSession** class. This method is part of the storage layer. This storage API ONLY performs the RMW or Read operation calls, and it wraps the Tsavorite API. (note that the **StorageSession** class is divided across several .cs files, **object store** operations will reside under ```Garnet.server/Storage/Session/ObjectStore/[ObjectName]Ops.cs```, while **main store** operations will mainly reside under ```Garnet.server/Storage/Session/MainStore/MainStoreOps.cs```, with some exceptions).
To implement the storage-level logic of the new command, follow these guidelines according to the new command type:
* ***Single-key object store command***: If you are adding a command that operates on a single object, the implementation of this method will simply be a call to ```[Read|RMW]ObjectStoreOperation[WithOutput]```, which in turn will call the ```Operate``` method in ```Garnet.server/Objects/[ObjectName]/[ObjectName]Object.cs```, where you will have to add a new case for the command and and the object-specific command implementation in ```Garnet.server/Objects/[ObjectName]/[ObjectName]ObjectImpl.cs```
* ***Multi-key object store command***: If you are adding a command that operates on multiple objects, you may need to create a transaction in which you will appropriately lock the keys (using the ```TransactionManager``` instance). You can then operate on multiple objects using the ```GET``` & ```SET``` commands.
* ***Main-store command***: If you are adding a command that operates on the main store, you'll need to call Tsavorite's ```Read``` or ```RMW``` methods. If you are calling ```RMW```, you will need to implement the initialization and in-place / copy update functionality of the new command in ```Garnet.server/Storage/Functions/MainStore/RMWMethods.cs```.
7. If the command supports being called in a transaction context, add a new case for the command in the ```TransactionManager.GetKeys``` method and return the appropriate key locks required by the command (```Garnet.server/Transaction/TxnKeyManager.cs```).
8. Add tests that run the command and check its output under valid and invalid conditions, test using both SE.Redis and LightClient, if applicable. For object commands, add tests to ```Garnet.test/Resp[ObjectName]Tests.cs```. For other commands, add to ```Garnet.test/RespTests.cs``` or ```Garnet.test/Resp[AdminCommands|etc...]Tests.cs```, depending on the command type.
9. Add newly supported command documentation to the appropriate markdown file under ```website/docs/commands/```, and specify the command as supported in ```website/docs/commands/api-compatibility.md```.
10. Add command info by following the next [section](#adding-supported-command-info)

```csharp
private unsafe bool NetworkZADD<TObjectContext>(int count, byte* ptr, ref TObjectContext objectStoreContext)
```
:::tip
Before you start implementing your command logic, add a basic test that calls the new command, it will be easier to debug and implement missing logic as you go along.
:::

This method contains the operations that read and parse the message from the network buffer and write the resp formatted response from the command, back to the network buffer.

* An internal method Try[CommandName] in the class StorageSession.

**Example**

```csharp
internal GarnetStatus Try[CommandName]<TObjectContext>(ref byte[] key, ref SpanByte input, ref FasterObjectOutput output, ref TObjectContext objectStoreContext)
```

This method is part of the Storage layer. This storage API ONLY performs the RMW or Read operation calls, and it wraps over the Tsavorite API. All the current implemented commands can be found under the folder *Storage*, inside the Garnet.Server project (folder */libs/server/Storage/Session/)*.

* An internal method Try[CommandName] using the ArgSlice type.

```csharp
internal GarnetStatus Try[CommandName]<TObjectContext>(ArgSlice key, ArgSlice score, ArgSlice member, out int result, ref TObjectContext objectStoreContext)
```

This method is optional. Notice this ArgSlice type is used instead of byte[], also this method does not read any value from the network buffer and it does not rely on the RESP protocol. It is the way to extended or add functionality to the API, that can be used in server procedures that use csharp code.
### Adding command info

Each supported RESP command in Garnet should have an entry in ```Garnet.server/Resp/RespCommandsInfo.json```, specifying the command's info.\
A command's info can be added manually, but we recommend using the ```CommandInfoUpdater``` tool to update the JSON file (can be found under ```playground/```).

The ```CommandInfoUpdater``` tool calculates the difference between existing commands in ```Garnet.server/Resp/RespCommandsInfo.json``` and commands specified in ```CommandInfoUpdater/SupportedCommands.cs```. It then attempts to add / remove commands' info as necessary.\
Info for Garnet-only commands is retrieved from ```CommandInfoUpdater/GarnetCommandsInfo.json```, and info for other RESP commands is retrieved from an external RESP server (which you will need to run locally / have access to in order to run this tool).

To add command info to Garnet, follow these steps:
1. Add the supported command and its supported sub-commands (if applicable) to ```CommandInfoUpdater/SupportedCommands.cs```.
2. If you are adding a Garnet-specific command, add its info to ```CommandInfoUpdater/GarnetCommandsInfo.json```.
3. Build & run the tool (for syntax help run the tool with `-h` or `--help`).

0 comments on commit 7188887

Please sign in to comment.