Skip to content

Commit 6bab604

Browse files
committed
Add support for multi-labeled nodes
1 parent 312f4ea commit 6bab604

File tree

8 files changed

+131
-24
lines changed

8 files changed

+131
-24
lines changed

Diff for: src/node.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ class Node {
77
/**
88
* Builds a node object.
99
*
10-
* @param {string} label - node label.
10+
* @param {string|string[]} label - node label(s).
1111
* @param {Map} properties - properties map.
1212
*/
1313
constructor(label, properties) {
1414
this.id = undefined; //node's id - set by RedisGraph
15-
this.label = label; //node's label
15+
this.label = label; //node's label(s)
1616
this.properties = properties; //node's list of properties (list of Key:Value)
1717
}
1818

Diff for: src/resultSet.js

+29-13
Original file line numberDiff line numberDiff line change
@@ -174,31 +174,47 @@ class ResultSet {
174174
}
175175

176176
/**
177-
* Parse raw node representation into a Node object.
177+
* Parse label index into a label string.
178178
* @async
179-
* @param {object[]} cell raw node representation.
180-
* @returns {Promise<Node>} Node object.
179+
* @param {number} label_idx index of label.
180+
* @returns {Promise<string>} string representation of label.
181181
*/
182-
async parseNode(cell) {
183-
// Node ID (integer),
184-
// [label string offset (integer)],
185-
// [[name, value, value type] X N]
186-
187-
let node_id = cell[0];
188-
let label = this._graph.getLabel(cell[1][0]);
182+
async parseNodeLabel(label_idx) {
183+
let label = this._graph.getLabel(label_idx);
189184
// will try to get the right label for at most 10 times
190185
var tries = 0;
191186
while (label == undefined && tries < 10) {
192-
label = await this._graph.fetchAndGetLabel(cell[1][0]);
187+
label = await this._graph.fetchAndGetLabel(label_idx);
193188
tries++;
194189
}
195190
if (label == undefined) {
196191
console.warn(
197-
"unable to retrive label value for label index " + cell[1][0]
192+
"unable to retrieve label value for label index " + label_idx
198193
);
199194
}
195+
return label;
196+
}
197+
198+
/**
199+
* Parse raw node representation into a Node object.
200+
* @async
201+
* @param {object[]} cell raw node representation.
202+
* @returns {Promise<Node>} Node object.
203+
*/
204+
async parseNode(cell) {
205+
// Node ID (integer),
206+
// [label string offset (integer) X N],
207+
// [[name, value, value type] X N]
208+
209+
let node_id = cell[0];
210+
var labels = undefined;
211+
if (cell[1].length == 1) {
212+
labels = await this.parseNodeLabel(cell[1][0]);
213+
} else {
214+
labels = await Promise.all(cell[1].map(x => this.parseNodeLabel(x)));
215+
}
200216
let properties = await this.parseEntityProperties(cell[2]);
201-
let node = new Node(label, properties);
217+
let node = new Node(labels, properties);
202218
node.setId(node_id);
203219
return node;
204220
}

Diff for: test/redisGraphAPITest.js

+48
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,54 @@ describe("RedisGraphAPI Test", () => {
9595
);
9696
});
9797

98+
it("test Create Multi-Labeled Node", async () => {
99+
// Create a node with 2 labels
100+
let result = await api.query("CREATE (:human:male {name:'danny', age:12})");
101+
assert.equal(result.size(), 0);
102+
assert.ok(!result.hasNext());
103+
assert.equal(
104+
"1",
105+
result.getStatistics().getStringValue(Label.NODES_CREATED)
106+
);
107+
assert.equal(
108+
"2",
109+
result.getStatistics().getStringValue(Label.PROPERTIES_SET)
110+
);
111+
assert.ok(
112+
result
113+
.getStatistics()
114+
.getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)
115+
);
116+
117+
// Retrieve the node
118+
let resultSet = await api.query(
119+
"MATCH (a:human:male) RETURN a"
120+
);
121+
assert.equal(resultSet.size(), 1);
122+
assert.ok(resultSet.hasNext());
123+
assert.equal(0, resultSet.getStatistics().nodesCreated());
124+
assert.equal(0, resultSet.getStatistics().nodesDeleted());
125+
assert.equal(0, resultSet.getStatistics().labelsAdded());
126+
assert.equal(0, resultSet.getStatistics().propertiesSet());
127+
assert.equal(0, resultSet.getStatistics().relationshipsCreated());
128+
assert.equal(0, resultSet.getStatistics().relationshipsDeleted());
129+
assert.ok(
130+
resultSet
131+
.getStatistics()
132+
.getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)
133+
);
134+
135+
assert.deepStrictEqual(["a"], resultSet.getHeader());
136+
137+
let record = resultSet.next();
138+
let n = record.get(0);
139+
assert.equal(12, n.properties["age"]);
140+
assert.equal("danny", n.properties["name"]);
141+
assert.deepEqual(["human", "male"], n.label);
142+
assert.equal(0, n.id);
143+
144+
});
145+
98146
it("test Connect Nodes", async () => {
99147
// Create both source and destination nodes
100148
await api.query("CREATE (:person {name:'roi', age:34})");

Diff for: types/src/edge.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ declare class Edge {
3232
declare namespace Edge {
3333
export { Node };
3434
}
35-
type Node = import("./node");
35+
type Node = import('./node');

Diff for: types/src/graph.d.ts

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
export = Graph;
2+
/**
3+
* @typedef {import('ioredis') | redis.RedisClient} RedisClient
4+
*/
25
/**
36
* RedisGraph client
47
*/
@@ -8,11 +11,11 @@ declare class Graph {
811
* See: node_redis for more options on createClient
912
*
1013
* @param {string} graphId the graph id
11-
* @param {string | redis.RedisClient} [host] Redis host or node_redis client
14+
* @param {string | RedisClient} [host] Redis host or node_redis client or ioredis client
1215
* @param {string | number} [port] Redis port (integer)
1316
* @param {Object} [options] node_redis options
1417
*/
15-
constructor(graphId: string, host?: string | any, port?: string | number, options?: any);
18+
constructor(graphId: string, host?: string | RedisClient, port?: string | number, options?: any);
1619
_graphId: string;
1720
_labels: any[];
1821
_relationshipTypes: any[];
@@ -53,6 +56,26 @@ declare class Graph {
5356
* @returns {Promise<ResultSet>} a promise contains a result set
5457
*/
5558
query(query: string, params?: Map<any, any>): Promise<ResultSet>;
59+
/**
60+
* Execute a Cypher readonly query
61+
* @async
62+
* @param {string} query Cypher query
63+
* @param {Map} [params] Parameters map
64+
*
65+
* @returns {Promise<ResultSet>} a promise contains a result set
66+
*/
67+
readonlyQuery(query: string, params?: Map<any, any>): Promise<ResultSet>;
68+
/**
69+
* Execute a Cypher query
70+
* @private
71+
* @async
72+
* @param {'graph.QUERY'|'graph.RO_QUERY'} command
73+
* @param {string} query Cypher query
74+
* @param {Map} [params] Parameters map
75+
*
76+
* @returns {Promise<ResultSet>} a promise contains a result set
77+
*/
78+
private _query;
5679
/**
5780
* Deletes the entire graph
5881
* @async
@@ -122,4 +145,8 @@ declare class Graph {
122145
*/
123146
fetchAndGetProperty(id: number): Promise<string>;
124147
}
148+
declare namespace Graph {
149+
export { RedisClient };
150+
}
125151
import ResultSet = require("./resultSet");
152+
type RedisClient = any | any;

Diff for: types/src/node.d.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ declare class Node {
66
/**
77
* Builds a node object.
88
*
9-
* @param {string} label - node label.
9+
* @param {string|string[]} label - node label(s).
1010
* @param {Map} properties - properties map.
1111
*/
12-
constructor(label: string, properties: Map<any, any>);
12+
constructor(label: string | string[], properties: Map<any, any>);
1313
id: number;
14-
label: string;
14+
label: string | string[];
1515
properties: Map<any, any>;
1616
/**
1717
* Sets the node id.

Diff for: types/src/path.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,5 @@ declare class Path {
6464
declare namespace Path {
6565
export { Node, Edge };
6666
}
67-
type Node = import("./node");
68-
type Edge = import("./edge");
67+
type Node = import('./node');
68+
type Edge = import('./edge');

Diff for: types/src/resultSet.d.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ declare class ResultSet {
5050
* @returns {Promise<object>} Map with the parsed properties.
5151
*/
5252
parseEntityProperties(props: object[]): Promise<object>;
53+
/**
54+
* Parse label index into a label string
55+
* @async
56+
* @param {number} label_idx index of label
57+
* @returns {Promise<string>} string representation of label.
58+
*/
59+
parseNodeLabel(label_idx: number): Promise<string>;
5360
/**
5461
* Parse raw node representation into a Node object.
5562
* @async
@@ -85,6 +92,15 @@ declare class ResultSet {
8592
* @returns {Promise<object>} Map object.
8693
*/
8794
parseMap(rawMap: object[]): Promise<object>;
95+
/**
96+
* Parse a raw Point representation into a lat-lon Map object.
97+
* @param {object[]} rawPoint 2-valued lat-lon array representation
98+
* @returns {{ latitude: number, longitude: number }} Map object with latitude and longitude keys.
99+
*/
100+
parsePoint(rawPoint: object[]): {
101+
latitude: number;
102+
longitude: number;
103+
};
88104
/**
89105
* Parse a raw value into its actual value.
90106
* @async
@@ -121,4 +137,4 @@ import Node = require("./node");
121137
import Edge = require("./edge");
122138
import Path = require("./path");
123139
import Record = require("./record");
124-
type Graph = import("./graph");
140+
type Graph = import('./graph');

0 commit comments

Comments
 (0)