-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optional match #1043
Optional match #1043
Changes from 23 commits
a5b99a8
5325ad1
ec9371a
f95e5ac
6330061
85e7368
d2d7a65
67ae023
96af9a1
cf789b9
d8e58a2
7906473
a6332fe
b3843c1
8117f07
59083af
24cc90a
98f0574
0cbd1a6
c312e9a
de7a126
7defbfd
61f4965
a55e632
e9d78ad
b7a9884
c2c3145
fc0ed1a
2f98977
e240158
a993784
38ac976
732be69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ supported. | |
### Query structure | ||
|
||
- MATCH | ||
- OPTIONAL MATCH | ||
- WHERE | ||
- RETURN | ||
- ORDER BY | ||
|
@@ -128,6 +129,45 @@ The syntactic sugar `(person_a)<-[:KNOWS]->(person_b)` will return the same resu | |
|
||
The bracketed edge description can be omitted if all relations should be considered: `(person_a)--(person_b)`. | ||
|
||
#### OPTIONAL MATCH | ||
|
||
The OPTIONAL MATCH clause is a MATCH variant that produces null values for elements that do not match successfully, rather than the all-or-nothing logic for patterns in MATCH clauses. | ||
|
||
It can be considered to fill the same role as LEFT/RIGHT JOIN does in SQL, as MATCH entities must be resolved but nodes and edges introduced in OPTIONAL MATCH will be returned as nulls if they cannot be found. | ||
|
||
OPTIONAL MATCH clauses accept the same patterns as standard MATCH clauses, and may similarly be modified by WHERE clauses. | ||
|
||
Multiple MATCH and OPTIONAL MATCH clauses can be chained together, though a mandatory MATCH cannot follow an optional one. | ||
|
||
```sh | ||
GRAPH.QUERY DEMO_GRAPH | ||
"MATCH (p:Person) OPTIONAL MATCH (p)-[w:WORKS_AT]->(c:Company) | ||
WHERE w.start_date > 2016 | ||
RETURN p, w, c" | ||
``` | ||
|
||
All `Person` nodes are returned, as well as any `WORKS_AT` relations and `Company` nodes that can be resolved and satisfy the `start_date` constraint. For each `Person` that does not resolve the optional pattern, the person will be returned as normal and the non-matching elements will be returned as null. | ||
|
||
Cypher is lenient in its handling of null values, so actions like property accesses and function calls on null values will return null values rather than emit errors. | ||
|
||
```sh | ||
GRAPH.QUERY DEMO_GRAPH | ||
"MATCH (p:Person) OPTIONAL MATCH (p)-[w:WORKS_AT]->(c:Company) | ||
RETURN p, w.department, ID(c) as ID" | ||
``` | ||
|
||
In this case, `w.department` and `ID` will be returned if the OPTIONAL MATCH was successful, and will be null otherwise. | ||
|
||
Clauses like SET, CREATE, and DELETE will ignore null inputs and perform the expected updates on real inputs. One exception to this is that attempting to create a relation with a null endpoint will cause an error: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
```sh | ||
GRAPH.QUERY DEMO_GRAPH | ||
"MATCH (p:Person) OPTIONAL MATCH (p)-[w:WORKS_AT]->(c:Company) | ||
CREATE (c)-[:NEW_RELATION]->(:NEW_NODE)" | ||
``` | ||
|
||
If `c` is null for any record, this query will emit an error. In this case, no changes to the graph are committed, even if some values for `c` were resolved. | ||
|
||
#### WHERE | ||
|
||
This clause is not mandatory, but if you want to filter results, you can specify your predicates here. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -344,8 +344,12 @@ static bool _AR_EXP_UpdateEntityIdx(AR_OperandNode *node, const Record r) { | |
|
||
static AR_EXP_Result _AR_EXP_EvaluateProperty(AR_ExpNode *node, const Record r, SIValue *result) { | ||
RecordEntryType t = Record_GetType(r, node->operand.variadic.entity_alias_idx); | ||
// Property requested on a scalar value. | ||
if(!(t & (REC_TYPE_NODE | REC_TYPE_EDGE))) { | ||
if(t == REC_TYPE_UNKNOWN) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
/* If we attempt to access a null value as a graph entity (due to a | ||
* scenario like a failed OPTIONAL MATCH), return a null value. */ | ||
*result = SI_NullVal(); | ||
return EVAL_OK; | ||
} else if(!(t & (REC_TYPE_NODE | REC_TYPE_EDGE))) { | ||
/* Attempted to access a scalar value as a map. | ||
* Set an error and invoke the exception handler. */ | ||
char *error; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,14 @@ | |
|
||
/* returns the id of a relationship or node. */ | ||
SIValue AR_ID(SIValue *argv, int argc) { | ||
if(argv[0].type == T_NULL) return SI_NullVal(); | ||
jeffreylovitz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
GraphEntity *graph_entity = (GraphEntity *)argv[0].ptrval; | ||
return SI_LongVal(ENTITY_GET_ID(graph_entity)); | ||
} | ||
|
||
/* returns a string representations the label of a node. */ | ||
SIValue AR_LABELS(SIValue *argv, int argc) { | ||
if(argv[0].type == T_NULL) return SI_NullVal(); | ||
jeffreylovitz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
char *label = ""; | ||
Node *node = argv[0].ptrval; | ||
GraphContext *gc = QueryCtx_GetGraphCtx(); | ||
|
@@ -32,6 +34,7 @@ SIValue AR_LABELS(SIValue *argv, int argc) { | |
|
||
/* returns a string representation of the type of a relation. */ | ||
SIValue AR_TYPE(SIValue *argv, int argc) { | ||
if(argv[0].type == T_NULL) return SI_NullVal(); | ||
jeffreylovitz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
char *type = ""; | ||
Edge *e = argv[0].ptrval; | ||
GraphContext *gc = QueryCtx_GetGraphCtx(); | ||
|
@@ -51,6 +54,7 @@ SIValue AR_EXISTS(SIValue *argv, int argc) { | |
} | ||
|
||
SIValue _AR_NodeDegree(SIValue *argv, int argc, GRAPH_EDGE_DIR dir) { | ||
if(argv[0].type == T_NULL) return SI_NullVal(); | ||
jeffreylovitz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Node *n = (Node *)argv[0].ptrval; | ||
Edge *edges = array_new(Edge, 0); | ||
GraphContext *gc = QueryCtx_GetGraphCtx(); | ||
|
@@ -79,11 +83,13 @@ SIValue _AR_NodeDegree(SIValue *argv, int argc, GRAPH_EDGE_DIR dir) { | |
|
||
/* Returns the number of incoming edges for given node. */ | ||
SIValue AR_INCOMEDEGREE(SIValue *argv, int argc) { | ||
if(argv[0].type == T_NULL) return SI_NullVal(); | ||
jeffreylovitz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return _AR_NodeDegree(argv, argc, GRAPH_EDGE_DIR_INCOMING); | ||
} | ||
|
||
/* Returns the number of outgoing edges for given node. */ | ||
SIValue AR_OUTGOINGDEGREE(SIValue *argv, int argc) { | ||
if(argv[0].type == T_NULL) return SI_NullVal(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
return _AR_NodeDegree(argv, argc, GRAPH_EDGE_DIR_OUTGOING); | ||
} | ||
|
||
|
@@ -92,34 +98,35 @@ void Register_EntityFuncs() { | |
AR_FuncDesc *func_desc; | ||
|
||
types = array_new(SIType, 1); | ||
types = array_append(types, T_NODE | T_EDGE); | ||
types = array_append(types, T_NULL | T_NODE | T_EDGE); | ||
func_desc = AR_FuncDescNew("id", AR_ID, 1, 1, types, false); | ||
AR_RegFunc(func_desc); | ||
|
||
types = array_new(SIType, 1); | ||
types = array_append(types, T_NODE); | ||
types = array_append(types, T_NULL | T_NODE); | ||
func_desc = AR_FuncDescNew("labels", AR_LABELS, 1, 1, types, false); | ||
AR_RegFunc(func_desc); | ||
|
||
types = array_new(SIType, 1); | ||
types = array_append(types, T_EDGE); | ||
types = array_append(types, T_NULL | T_EDGE); | ||
func_desc = AR_FuncDescNew("type", AR_TYPE, 1, 1, types, false); | ||
AR_RegFunc(func_desc); | ||
|
||
types = array_new(SIType, 1); | ||
types = array_append(types, SI_ALL); | ||
types = array_append(types, T_NULL | SI_ALL); | ||
func_desc = AR_FuncDescNew("exists", AR_EXISTS, 1, 1, types, false); | ||
AR_RegFunc(func_desc); | ||
|
||
types = array_new(SIType, 2); | ||
types = array_append(types, T_NODE); | ||
types = array_append(types, T_NULL | T_NODE); | ||
types = array_append(types, T_STRING); | ||
func_desc = AR_FuncDescNew("indegree", AR_INCOMEDEGREE, 1, VAR_ARG_LEN, types, false); | ||
AR_RegFunc(func_desc); | ||
|
||
types = array_new(SIType, 1); | ||
types = array_append(types, T_NODE); | ||
types = array_new(SIType, 2); | ||
types = array_append(types, T_NULL | T_NODE); | ||
types = array_append(types, T_STRING); | ||
func_desc = AR_FuncDescNew("outdegree", AR_OUTGOINGDEGREE, 1, VAR_ARG_LEN, types, false); | ||
AR_RegFunc(func_desc); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if there are multiple connection between a person
p
and a number of companies say 10and out of those 10 only 4 satisfy the criteria how many null will be returned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0 nulls - if
p
has at least one valid connection, no nulls will be produced. If there is another personp
with no valid connections, it will return thatp
once with nullw
andc
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍