Skip to content

Commit

Permalink
Labeless collapsed entities (swilly22#75)
Browse files Browse the repository at this point in the history
* support labeless collapsed entities to be queried

* update docs

* SCAN as a utility function
  • Loading branch information
swilly22 authored Dec 19, 2017
1 parent eb17cb3 commit d0b4b3d
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 29 deletions.
12 changes: 6 additions & 6 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Arguments: `Graph name, Query`
Returns: `String representation of a query execution plan`

```sh
GRAPH.EXPLAIN us_government "MATCH (p:president)-[:born]->(h:state {name:Hawaii}) RETURN p"
GRAPH.EXPLAIN us_government "MATCH (p:president)-[:born]->(h:state {name:'Hawaii'}) RETURN p"
```

## GRAPH.QUERY
Expand All @@ -22,7 +22,7 @@ Arguments: `Graph name, Query`
Returns: `Result set`

```sh
GRAPH.QUERY us_government "MATCH (p:president)-[:born]->(:state {name:Hawaii}) RETURN p"
GRAPH.QUERY us_government "MATCH (p:president)-[:born]->(:state {name:'Hawaii'}) RETURN p"
```

### Query language
Expand Down Expand Up @@ -85,15 +85,15 @@ As such, we're interested in actor entities which have the relation "act" with *
It is possible to describe broader relationships by composing a multi-hop query such as:

```sh
(me {name:swilly})-[:friends_with]->()-[:friends_with]->(fof)
(me {name:'swilly'})-[:friends_with]->()-[:friends_with]->(fof)
```
Here we're interested in finding out who are my friends' friends.
Nodes can have more than one edge coming in or out of them, for instance:
```sh
(me {name:swilly})-[:visited]->(c:country)<-[:visited]-(friend)<-[:friends_with]-({name:swilly})
(me {name:'swilly'})-[:visited]->(c:country)<-[:visited]-(friend)<-[:friends_with]-({name:'swilly'})
```
Here we're interested in knowing which of my friends have visited at least one country i've been to.
Expand Down Expand Up @@ -228,7 +228,7 @@ CREATE (n),(m)
Label and properties can be specified at creation time

```sh
CREATE (:person {name: Kurt, age:27})
CREATE (:person {name: 'Kurt', age:27})
```

Adding relations between nodes, in the following example we first locate an existing source node,
Expand All @@ -237,7 +237,7 @@ once found we create a new relationship and destination node.
```sh
MATCH(a:person)
WHEREE a.name = 'Kurt'
CREATE (a)-[member {position:"lead singer"}]->(:band {name:Nirvana})
CREATE (a)-[member {position:"lead singer"}]->(:band {name:"Nirvana"})
RETURN
```

Expand Down
4 changes: 2 additions & 2 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ graph.QUERY IMDB 'CREATE (aldis:actor {name: "Aldis Hodge", birth_year: 1986}),
(oshea:actor {name: "OShea Jackson", birth_year: 1991}),
(corey:actor {name: "Corey Hawkins", birth_year: 1988}),
(neil:actor {name: "Neil Brown", birthyear: 1980}),
(compton:movie {title: "Straight Outta Compton", genre: Biography, votes: 127258, rating: 7.9, year: 2015}),
(neveregoback:movie {title: "Never Go Back", genre: Action, votes: 15821, rating: 6.4, year: 2016}),
(compton:movie {title: "Straight Outta Compton", genre: "Biography", votes: 127258, rating: 7.9, year: 2015}),
(neveregoback:movie {title: "Never Go Back", genre: "Action", votes: 15821, rating: 6.4, year: 2016}),
(aldis)-[act]->(neveregoback),
(aldis)-[act]->(compton),
(oshea)-[act]->(compton),
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ You can call Redis Graph's commands from any Redis client.

```sh
$ redis-cli
127.0.0.1:6379> GRAPH.QUERY social "CREATE (:person {name: roi, age: 32, gender: male, status: married})"
127.0.0.1:6379> GRAPH.QUERY social "CREATE (:person {name: 'roi', age: 32, gender: 'male', status: 'married'})"
```

### With any other client
Expand All @@ -88,7 +88,7 @@ This code snippet shows how to use Redis Graph with raw Redis commands from Pyth
import redis

r = redis.StrictRedis()
reply = r.execute_command('GRAPH.QUERY', 'social', 'CREATE (:person {name:roi, age:32, gender:male, status:married)')
reply = r.execute_command('GRAPH.QUERY', 'social', "CREATE (:person {name:'roi', age:32, gender:'male', status:'married')")
```

### Client libraries
Expand Down
76 changes: 57 additions & 19 deletions src/query_executor.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,6 @@ int ReturnClause_ContainsAggregation(AST_QueryExpressionNode *ast) {
}

void ReturnClause_ExpandCollapsedNodes(RedisModuleCtx *ctx, AST_QueryExpressionNode *ast, const char *graphName) {
/* Assumption, each collapsed node is tagged with a label */

/* Create a new return clause */
Vector *expandReturnElements = NewVector(AST_ReturnElementNode*, Vector_Size(ast->returnNode->returnElements));

Expand Down Expand Up @@ -207,28 +205,68 @@ void ReturnClause_ExpandCollapsedNodes(RedisModuleCtx *ctx, AST_QueryExpressionN
return;
}

/* Find label's known properties. */
/* Find label's properties. */
LabelStoreType store_type = (collapsed_entity->t == N_ENTITY) ? STORE_NODE : STORE_EDGE;
LabelStore *s = LabelStore_Get(ctx, store_type, graphName, collapsed_entity->label);

TrieMapIterator *it = TrieMap_Iterate(s->stats.properties, "", 0);
char *prop = NULL;
tm_len_t prop_len = 0;
void *ptr = NULL;
void *ptr = NULL; /* Label store property value, (not in use). */
char *prop = NULL; /* Entity property. */
tm_len_t prop_len = 0; /* Length of entity's property. */

/* Collapsed entity has a label. */
if(collapsed_entity->label) {
LabelStore *store = LabelStore_Get(ctx, store_type, graphName, collapsed_entity->label);
TrieMapIterator *it = TrieMap_Iterate(store->stats.properties, "", 0);
while(TrieMapIterator_Next(it, &prop, &prop_len, &ptr)) {
prop[prop_len] = 0;
/* Create a new return element foreach property. */
AST_ArithmeticExpressionNode *expanded_exp =
New_AST_AR_EXP_VariableOperandNode(collapsed_entity->alias, prop);

AST_ReturnElementNode *retElem =
New_AST_ReturnElementNode(expanded_exp, ret_elem->alias);

Vector_Push(expandReturnElements, retElem);
}
TrieMapIterator_Free(it);
} else {
/* Entity does have a label.
* We don't have a choice but to retrieve all know properties. */
size_t stores_len = 128; /* Limit number of labels we'll consider. */
LabelStore *stores[128]; /* Label stores. */

LabelStore_Get_ALL(ctx, store_type, graphName, stores, &stores_len);
TrieMap *properties = NewTrieMap(); /* Holds all properties, discards duplicates. */

/* Get properties out of label store. */
for(int store_idx = 0; store_idx < stores_len; store_idx++) {
LabelStore *s = stores[store_idx];
if(!s->label) continue; /* No label, (this is 'ALL' store). */
TrieMapIterator *it = TrieMap_Iterate(s->stats.properties, "", 0);

/* Add property to properties triemap. */
while(TrieMapIterator_Next(it, &prop, &prop_len, &ptr)) {
prop[prop_len] = 0;
TrieMap_Add(properties, prop, prop_len, prop, NULL);
}
/* TODO: Free iterator. */
// TrieMapIterator_Free(it);
}

while(TrieMapIterator_Next(it, &prop, &prop_len, &ptr)) {
prop[prop_len] = 0;
/* Create a new return element foreach property. */
AST_ArithmeticExpressionNode *expanded_exp =
New_AST_AR_EXP_VariableOperandNode(collapsed_entity->alias, prop);
/* Add each property to return cluase. */
TrieMapIterator *it = TrieMap_Iterate(properties, "", 0);
while(TrieMapIterator_Next(it, &prop, &prop_len, &ptr)) {
/* Create a new return element foreach property. */
prop[prop_len] = 0;
AST_ArithmeticExpressionNode *expanded_exp =
New_AST_AR_EXP_VariableOperandNode(collapsed_entity->alias, prop);

AST_ReturnElementNode *retElem =
New_AST_ReturnElementNode(expanded_exp, ret_elem->alias);
AST_ReturnElementNode *retElem =
New_AST_ReturnElementNode(expanded_exp, ret_elem->alias);

Vector_Push(expandReturnElements, retElem);
Vector_Push(expandReturnElements, retElem);
}
TrieMapIterator_Free(it);
TrieMap_Free(properties, TrieMap_NOP_CB);
}
TrieMapIterator_Free(it);

/* Discard collapsed return element. */
Free_AST_ReturnElementNode(ret_elem);
} else {
Expand Down
32 changes: 32 additions & 0 deletions src/rmutil/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,38 @@ RMUtilInfo *RMUtil_HGetAll(RedisModuleCtx *ctx, RedisModuleString *id) {
return hgetall;
}

void RMUtil_SCAN(RedisModuleCtx *ctx, const char *pattern, RedisModuleString **keys, size_t *key_count) {
int scan_idx = 0; /* SCAN cursor. */
size_t total_keys = 0; /* Number of keys retrieved. */
RedisModuleCallReply *reply;

/* Consume SCAN */
do {
reply = RedisModule_Call(ctx, "SCAN", "lcc", scan_idx, "MATCH", pattern);

/* First element is the scan cursor, 0 indicates end of SCAN. */
RedisModuleCallReply *element = RedisModule_CallReplyArrayElement(reply, 0);
scan_idx = RedisModule_CallReplyInteger(element);

/* Process SCAN results. */
RedisModuleCallReply *scan_results = RedisModule_CallReplyArrayElement(reply, 1);
/* Number of elements in replay. */
size_t keys_count = RedisModule_CallReplyLength(scan_results);

/* Extract SCAN result elements. */
for(int idx = 0; idx < keys_count && *key_count > total_keys; idx++) {
element = RedisModule_CallReplyArrayElement(scan_results, idx);
RedisModuleString *key = RedisModule_CreateStringFromCallReply(element);
keys[total_keys] = key;
total_keys++;
}
RedisModule_FreeCallReply(reply);
} while(scan_idx != 0);

/* Update number of stores fetched. */
*key_count = total_keys;
}

void RMUtilRedisInfo_Free(RMUtilInfo *info) {

free(info->entries);
Expand Down
9 changes: 9 additions & 0 deletions src/rmutil/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx);
*/
RMUtilInfo *RMUtil_HGetAll(RedisModuleCtx *ctx, RedisModuleString *id);

/**
* Performs SCAN against given pattern.
* `keys` will contain each key returned by SCAN.
* Each RedisModuleString within keys must be free by caller.
* `key_count` sets a limit on the maximum number of keys consumed by SCAN.
* `keys` must be at least of size key_count.
*/
void RMUtil_SCAN(RedisModuleCtx *ctx, const char *pattern, RedisModuleString **keys, size_t *key_count);

/**
* Free an RMUtilInfo object and its entries
*/
Expand Down
27 changes: 27 additions & 0 deletions src/stores/store.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "store.h"
#include "../rmutil/util.h"
#include "../rmutil/strings.h"
#include "../util/triemap/triemap_type.h"

Expand Down Expand Up @@ -62,6 +63,32 @@ LabelStore *LabelStore_Get(RedisModuleCtx *ctx, LabelStoreType type, const char
return store;
}

/* Get all stores of given type. */
void LabelStore_Get_ALL(RedisModuleCtx *ctx, LabelStoreType type, const char *graph, LabelStore **stores, size_t *stores_len) {
char *pattern;
LabelStore_Id(&pattern, type, graph, "*");

size_t key_count = 128; /* Maximum number of keys we're willing to process. */
RedisModuleString *str_keys[128]; /* Keys returned by SCAN. */

RMUtil_SCAN(ctx, pattern, str_keys, &key_count);
free(pattern);

/* Consume SCAN */
for(int i = 0; i < key_count; i++) {
RedisModuleString *store_key = str_keys[i];
if(i < *stores_len) {
RedisModuleKey *key = RedisModule_OpenKey(ctx, store_key, REDISMODULE_WRITE);
stores[i] = RedisModule_ModuleTypeGetValue(key);
RedisModule_CloseKey(key);
}
RedisModule_FreeString(ctx, store_key);
}

/* Update number of stores fetched. */
*stores_len = key_count;
}

int LabelStore_Cardinality(LabelStore *store) {
return store->items->cardinality;
}
Expand Down
3 changes: 3 additions & 0 deletions src/stores/store.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ int LabelStore_Id(char **id, LabelStoreType type, const char *graph, const char
/* Get a label store. */
LabelStore *LabelStore_Get(RedisModuleCtx *ctx, LabelStoreType type, const char *graph, const char* label);

/* Get all stores of given type. */
void LabelStore_Get_ALL(RedisModuleCtx *ctx, LabelStoreType type, const char *graph, LabelStore **stores, size_t *stores_len);

/* Returns the number of items within the store */
int LabelStore_Cardinality(LabelStore *store);

Expand Down
4 changes: 4 additions & 0 deletions src/util/triemap/triemap.c
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ size_t TrieMap_MemUsage(TrieMap *t) {
return TrieMapNode_MemUsage(t->root);
}

void TrieMap_NOP_CB(void *p) {
(void)(p);
}

void TrieMapNode_Free(TrieMapNode *n, void (*freeCB)(void *)) {
for (tm_len_t i = 0; i < n->numChildren; i++) {
TrieMapNode *child = __trieMapNode_children(n)[i];
Expand Down
3 changes: 3 additions & 0 deletions src/util/triemap/triemap.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ void *TrieMap_Find(TrieMap *t, char *str, tm_len_t len);
* node. If it doesn't, we simply call free() */
int TrieMap_Delete(TrieMap *t, char *str, tm_len_t len, void (*freeCB)(void *));

/* Fake free callback which does absolutly nothing. */
void TrieMap_NOP_CB(void *p);

/* Free the trie's root and all its children recursively. If freeCB is given, we
* call it to free individual payload values. If not, free() is used instead. */
void TrieMap_Free(TrieMap *t, void (*freeCB)(void *));
Expand Down

0 comments on commit d0b4b3d

Please sign in to comment.