Skip to content

Commit

Permalink
Adds JSON.MEMORY command
Browse files Browse the repository at this point in the history
  • Loading branch information
itamarhaber committed Dec 22, 2016
1 parent e2c85c9 commit 5eb725b
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 34 deletions.
12 changes: 12 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* [`JSON.OBJKEYS`](#objkeys) returns the keys in an object
* [`JSON.OBJLEN`](#objlen) reports the number of keys in an object
* [Other commands](#other-commands)
* [`JSON.MEMORY`](#memory) returns the memory usage of a ReJSON key
* [`JSON.RESP`](#resp) returns a JSON value using Redis Serialization Protocol

## JSON
Expand Down Expand Up @@ -324,6 +325,17 @@ If the `key` does not exist, null is returned

This command is an alias for [`JSON.DEL`](#del).

### <a name="memory" />`JSON.MEMORY <key>`

> **Available since 1.0.0.**
> **Time complexity:** O(N), where N is the size of the JSON value.
Compute the size in bytes of a JSON value.

#### Return value

[Integer][2], specifically the size in bytes of the value.

### <a name="resp" />`JSON.RESP <key>`

> **Available since 1.0.0.**
Expand Down
141 changes: 109 additions & 32 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ int NodeFromJSONPath(Node *root, const RedisModuleString *path, JSONPathNode_t *
}

void ReplyWithPathTypeError(RedisModuleCtx *ctx, NodeType expected, NodeType actual) {
sds err = sdscatfmt(sdsempty(), REJSON_ERROR_PATH_WRONGTYPE, NodeTypeStr(expected), NodeTypeStr(actual));
sds err = sdscatfmt(sdsempty(), REJSON_ERROR_PATH_WRONGTYPE, NodeTypeStr(expected),
NodeTypeStr(actual));
RedisModule_ReplyWithError(ctx, err);
sdsfree(err);
}
Expand All @@ -152,23 +153,22 @@ void ReplyWithPathError(RedisModuleCtx *ctx, const JSONPathNode_t *jpn) {
case E_BADTYPE:
if (NT_KEY == epn->type) {
err = sdscatfmt(err, "ERR invalid index '[\"%s\"]' at level %i in path",
epn->value.key, jpn->errlevel);
epn->value.key, jpn->errlevel);
} else {
err = sdscatfmt(err, "ERR invalid key '[%i]' at level %i in path",
epn->value.index, jpn->errlevel);
err = sdscatfmt(err, "ERR invalid key '[%i]' at level %i in path", epn->value.index,
jpn->errlevel);
}
break;
case E_NOINDEX:
err = sdscatfmt(err, "ERR index '[%i]' out of range at level %i in path",
epn->value.index, jpn->errlevel);
epn->value.index, jpn->errlevel);
break;
case E_NOKEY:
err = sdscatfmt(err, "ERR key '%s' does not exist at level %i in path",
epn->value.key, jpn->errlevel);
err = sdscatfmt(err, "ERR key '%s' does not exist at level %i in path", epn->value.key,
jpn->errlevel);
break;
default:
err = sdscatfmt(err, "ERR unknown path error at level %i in path",
jpn->errlevel);
err = sdscatfmt(err, "ERR unknown path error at level %i in path", jpn->errlevel);
break;
} // switch (err)
RedisModule_ReplyWithError(ctx, err);
Expand Down Expand Up @@ -224,7 +224,10 @@ void JSONTypeAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value)
* Reply: Array, specifically the JSON's RESP form.
*/
int JSONResp_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if ((argc != 2)) return RedisModule_WrongArity(ctx);
if ((argc != 2)) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key must be empty (reply with null) or a JSON type
Expand All @@ -234,14 +237,44 @@ int JSONResp_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_OK;
} else if (RedisModule_ModuleTypeGetType(key) != JSONType) {
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
{
RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
return REDISMODULE_ERR;
}
}

Object *objRoot = RedisModule_ModuleTypeGetValue(key);
ObjectTypeToRespReply(ctx, objRoot);
return REDISMODULE_OK;
}

/**
* JSON.MEMORY <key>
* Reply: Integer, specifically the memory usage of the key
*/
int JSONMemory_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if ((argc != 2)) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key must be empty (reply with null) or a JSON type
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
int type = RedisModule_KeyType(key);
if (REDISMODULE_KEYTYPE_EMPTY == type) {
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_OK;
} else if (RedisModule_ModuleTypeGetType(key) != JSONType) {
RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
return REDISMODULE_ERR;
}

Object *objRoot = RedisModule_ModuleTypeGetValue(key);
RedisModule_ReplyWithLongLong(ctx, ObjectTypeMemoryUsage(objRoot));
return REDISMODULE_OK;
}

/**
* JSON.TYPE <key> <path>
* Reports the type of JSON value at `path`.
Expand All @@ -250,7 +283,10 @@ int JSONResp_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
*/
int JSONType_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc != 3) return RedisModule_WrongArity(ctx);
if (argc != 3) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key must be empty or a JSON type
Expand Down Expand Up @@ -304,7 +340,10 @@ int JSONType_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
*/
int JSONLen_GenericCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc != 3) return RedisModule_WrongArity(ctx);
if (argc != 3) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// the actual command
Expand Down Expand Up @@ -371,7 +410,10 @@ int JSONLen_GenericCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int ar
*/
int JSONObjKeys_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc != 3) return RedisModule_WrongArity(ctx);
if (argc != 3) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key must be empty or a JSON type
Expand Down Expand Up @@ -430,16 +472,19 @@ int JSONObjKeys_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
/**
* JSON.SET <key> <path> <json>
* Sets the JSON value at `path` in `key`
*
*
* For new keys the `path` must be the root. For existing keys, when the entire `path` exists, the
* value that it contains is replaced with the `json` value. A key (with its respective value) is
* added to a JSON Object only if it is the last child in the `path`.
*
*
* Reply: Simple string, OK.
*/
int JSONSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc != 4) return RedisModule_WrongArity(ctx);
if (argc != 4) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key must be empty or a JSON type
Expand Down Expand Up @@ -553,9 +598,9 @@ int JSONSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
* JSON.GET <key> [INDENT indentation-string] [NEWLINE newline-string] [SPACE space-string]
* [path ...]
* Return the value at `path` in JSON serialized form.
*
*
* This command accepts multiple `path`s, and defaults to the value's root when none are given.
*
*
* The following subcommands change the reply's and are all set to the empty string by default:
* - `INDENT` sets the indentation string for nested levels
* - `NEWLINE` sets the string that's printed at the end of each line
Expand All @@ -567,7 +612,10 @@ int JSONSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
* is a key.
*/
int JSONGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if ((argc < 2)) return RedisModule_WrongArity(ctx);
if ((argc < 2)) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key must be empty (reply with null) or an object type
Expand Down Expand Up @@ -683,10 +731,14 @@ int JSONGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
* JSON.MGET <path> <key> [<key> ...]
* Returns the values at `path` from multiple `key`s. Non-existing keys and non-existing paths are
* reported as null.
* Reply: Array of Bulk Strings, specifically the JSON serialization of the value at each key's path.
* Reply: Array of Bulk Strings, specifically the JSON serialization of the value at each key's
* path.
*/
int JSONMGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if ((argc < 2)) return RedisModule_WrongArity(ctx);
if ((argc < 2)) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
if (RedisModule_IsKeysPositionRequest(ctx)) {
for (int i = 2; i < argc - 2; i++) RedisModule_KeyAtPos(ctx, i);
return REDISMODULE_OK;
Expand Down Expand Up @@ -767,7 +819,10 @@ int JSONMGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
*/
int JSONDel_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc != 3) return RedisModule_WrongArity(ctx);
if (argc != 3) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key must be empty or a JSON type
Expand Down Expand Up @@ -838,7 +893,10 @@ int JSONDel_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
* Reply: String, specifically the resulting JSON number value
*/
int JSONNum_GenericCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if ((argc < 4)) return RedisModule_WrongArity(ctx);
if ((argc < 4)) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

const char *cmd = RedisModule_StringPtrLen(argv[0], NULL);
Expand Down Expand Up @@ -960,7 +1018,10 @@ int JSONNum_GenericCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int ar
*/
int JSONStrAppend_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc != 4) return RedisModule_WrongArity(ctx);
if (argc != 4) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key can't be empty and must be a JSON type
Expand Down Expand Up @@ -1016,7 +1077,7 @@ int JSONStrAppend_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in
// the value must be a string
if (N_STRING != NODETYPE(jo)) {
sds err = sdscatfmt(sdsempty(), "ERR wrong type of value - expected %s but found %s",
NodeTypeStr(N_STRING), NodeTypeStr(NODETYPE(jpn.n)));
NodeTypeStr(N_STRING), NodeTypeStr(NODETYPE(jpn.n)));
RedisModule_ReplyWithError(ctx, err);
sdsfree(err);
}
Expand Down Expand Up @@ -1044,7 +1105,10 @@ int JSONStrAppend_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in
*/
int JSONArrInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc < 5) return RedisModule_WrongArity(ctx);
if (argc < 5) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key can't be empty and must be a JSON type
Expand Down Expand Up @@ -1152,7 +1216,10 @@ int JSONArrInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in
*/
int JSONArrAppend_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc < 4) return RedisModule_WrongArity(ctx);
if (argc < 4) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key can't be empty and must be a JSON type
Expand Down Expand Up @@ -1252,7 +1319,10 @@ int JSONArrAppend_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in
*/
int JSONArrIndex_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if ((argc < 4) || (argc > 6)) return RedisModule_WrongArity(ctx);
if ((argc < 4) || (argc > 6)) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key can't be empty and must be a JSON type
Expand Down Expand Up @@ -1343,7 +1413,10 @@ int JSONArrIndex_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
*/
int JSONArrTrim_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// check args
if (argc != 5) return RedisModule_WrongArity(ctx);
if (argc != 5) {
RedisModule_WrongArity(ctx);
return REDISMODULE_ERR;
}
RedisModule_AutoMemory(ctx);

// key can't be empty and must be a JSON type
Expand Down Expand Up @@ -1434,6 +1507,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx) {
REDISMODULE_ERR)
return REDISMODULE_ERR;

if (RedisModule_CreateCommand(ctx, "json.memory", JSONMemory_RedisCommand, "readonly", 1, 1,
1) == REDISMODULE_ERR)
return REDISMODULE_ERR;

if (RedisModule_CreateCommand(ctx, "json.type", JSONType_RedisCommand, "readonly", 1, 1, 1) ==
REDISMODULE_ERR)
return REDISMODULE_ERR;
Expand Down Expand Up @@ -1472,8 +1549,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx) {
1) == REDISMODULE_ERR)
return REDISMODULE_ERR;

if (RedisModule_CreateCommand(ctx, "json.strappend", JSONStrAppend_RedisCommand, "write deny-oom", 1,
1, 1) == REDISMODULE_ERR)
if (RedisModule_CreateCommand(ctx, "json.strappend", JSONStrAppend_RedisCommand,
"write deny-oom", 1, 1, 1) == REDISMODULE_ERR)
return REDISMODULE_ERR;

/* JSON array commands matey. */
Expand Down
46 changes: 45 additions & 1 deletion src/object_type.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,54 @@ void _ObjectTypeToResp_Begin(Node *n, void *ctx) {
}
}

void ObjectTypeToRespReply(RedisModuleCtx *ctx, Node *node) {
void ObjectTypeToRespReply(RedisModuleCtx *ctx, const Node *node) {
NodeSerializerOpt nso = {0};

nso.fBegin = _ObjectTypeToResp_Begin;
nso.xBegin = 0xff; // mask for all basic types
Node_Serializer(node, &nso, ctx);
}

void _ObjectTypeMemoryUsage(Node *n, void *ctx) {
long long *memory = (long long *)ctx;

if (!n) {
// the null node takes no memory
return;
} else {
// account for the struct's size
*memory += sizeof(Node);
switch (n->type) {
case N_BOOLEAN:
case N_INTEGER:
case N_NUMBER:
case N_NULL: // keeps the compiler from complaining
// these are stored in the node itself
return;
case N_STRING:
*memory += n->value.strval.len;
return;
case N_KEYVAL:
*memory += strlen(n->value.kvval.key);
return;
case N_DICT:
*memory += n->value.dictval.cap * sizeof(Node *);
return;
case N_ARRAY:
*memory += n->value.arrval.cap * sizeof(Node *);
return;
}
}
}

long long ObjectTypeMemoryUsage(const Node *node) {

NodeSerializerOpt nso = {0};
long long memory = 0;

nso.fBegin = _ObjectTypeMemoryUsage;
nso.xBegin = 0xff; // mask for all basic types
Node_Serializer(node, &nso, &memory);

return memory;
}
Loading

0 comments on commit 5eb725b

Please sign in to comment.