Skip to content

Commit 2d16c5f

Browse files
authored
Merge pull request #599 from neo4j/595-list-range-operator
595 list range operator
2 parents 3d1862e + 83b3f46 commit 2d16c5f

File tree

11 files changed

+183
-7
lines changed

11 files changed

+183
-7
lines changed

.changeset/mighty-bags-behave.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@neo4j/cypher-builder": minor
3+
---
4+
5+
Add support for range operator (`[ .. ]`) in `Cypher.List`, `PropertyRef.range` and using `Cypher.listRange`:
6+
7+
```js
8+
new Cypher.Variable().property("prop").range(1, -1); // var0["prop"][1..-1]
9+
new Cypher.List([1, 2, 3, 4]).range(1, -1); // [1, 2, 3, 4][1..-1]
10+
Cypher.listRange(expr, 2, -1); // expr[2..-1]
11+
```

docs/modules/ROOT/pages/variables-and-params/lists.adoc

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ RETURN [labels(node)]
3434
You can use instead `new Cypher.Literal(["element 1", "element 2"])` to avoid verbose code.
3535
====
3636

37-
38-
3937
== Index
4038
Use the `.index` method of a variable or `List` to create an index access:
4139

@@ -64,6 +62,34 @@ Cypher.listIndex(Cypher.collect(new Cypher.Variable()), 2);
6462
collect(var0)[2]
6563
----
6664

65+
== Range
66+
Use the `.range` method of a `List` to create a list range operator:
67+
68+
69+
[source, javascript]
70+
----
71+
new Cypher.List([1,2,3]).range(2, -1)
72+
----
73+
74+
[source, cypher]
75+
----
76+
[1, 2, 3][2..-1]
77+
----
78+
79+
=== Range operator on arbitrary expressions
80+
81+
To create an index on an arbitrary expression, like a function, use `Cypher.listRange`:
82+
83+
[source, javascript]
84+
----
85+
Cypher.listRange(Cypher.collect(new Cypher.Variable()), 1, -1);
86+
----
87+
88+
[source, cypher]
89+
----
90+
collect(var0)[1..-1]
91+
----
92+
6793
== List comprehension
6894

6995
link:https://neo4j.com/docs/cypher-manual/current/values-and-types/lists/#cypher-list-comprehension[List comprehension] can be created with `new Cypher.ListComprehension` and passing a variable to be used in the comprehension.

docs/modules/ROOT/pages/variables-and-params/variables.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ MATCH(this0:Movie)
211211
RETURN this0[($param0 + $param1)]
212212
----
213213

214-
=== Index
214+
== Index
215215

216216
Like properties, an index can also be accessed through the method `.index`:
217217

src/Cypher.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export * as vector from "./namespaces/vector/vector";
8383
export { ListComprehension } from "./expressions/list/ListComprehension";
8484
export { ListExpr as List } from "./expressions/list/ListExpr";
8585
export { listIndex, type ListIndex } from "./expressions/list/ListIndex";
86+
export { listRange, type ListRange } from "./expressions/list/ListRange";
8687
export { PatternComprehension } from "./expressions/list/PatternComprehension";
8788

8889
// --Map

src/expressions/list/ListExpr.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,13 @@ describe("List", () => {
3838
expect(queryResult.cypher).toMatchInlineSnapshot(`"[\\"1\\", \\"2\\", \\"3\\"][0]"`);
3939
expect(queryResult.params).toMatchInlineSnapshot(`{}`);
4040
});
41+
42+
test("list range", () => {
43+
const cypherList = new Cypher.List([new Cypher.Literal("1"), new Cypher.Literal("2"), new Cypher.Literal("3")]);
44+
const listIndex = cypherList.range(1, -1);
45+
const queryResult = new TestClause(listIndex).build();
46+
47+
expect(queryResult.cypher).toMatchInlineSnapshot(`"[\\"1\\", \\"2\\", \\"3\\"][1..-1]"`);
48+
expect(queryResult.params).toMatchInlineSnapshot(`{}`);
49+
});
4150
});

src/expressions/list/ListExpr.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import type { CypherEnvironment } from "../../Environment";
2121
import type { CypherCompilable, Expr } from "../../types";
2222
import type { ListIndex } from "./ListIndex";
2323
import { listIndex } from "./ListIndex";
24+
import type { ListRange } from "./ListRange";
25+
import { listRange } from "./ListRange";
2426

2527
/** Represents a List
2628
* @see {@link https://neo4j.com/docs/cypher-manual/current/syntax/lists/ | Cypher Documentation}
@@ -59,4 +61,9 @@ export class ListExpr implements CypherCompilable {
5961
public index(index: number): ListIndex {
6062
return listIndex(this, index);
6163
}
64+
65+
/** Adds a list range operator (`[ .. ]`) */
66+
public range(from: number, to: number): ListRange {
67+
return listRange(this, from, to);
68+
}
6269
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import Cypher from "../..";
21+
import { TestClause } from "../../utils/TestClause";
22+
23+
describe("ListRange", () => {
24+
test("get 0 .. 2 from list", () => {
25+
const list = new Cypher.List([new Cypher.Literal("1"), new Cypher.Literal("2"), new Cypher.Literal("3")]);
26+
const listIndex = Cypher.listRange(list, 0, 2);
27+
const queryResult = new TestClause(listIndex).build();
28+
29+
expect(queryResult.cypher).toMatchInlineSnapshot(`"[\\"1\\", \\"2\\", \\"3\\"][0..2]"`);
30+
expect(queryResult.params).toMatchInlineSnapshot(`{}`);
31+
});
32+
33+
test("get 2 .. -1 from list", () => {
34+
const list = new Cypher.List([new Cypher.Literal("1"), new Cypher.Literal("2"), new Cypher.Literal("3")]);
35+
const listIndex = Cypher.listRange(list, 2, -1);
36+
const queryResult = new TestClause(listIndex).build();
37+
38+
expect(queryResult.cypher).toMatchInlineSnapshot(`"[\\"1\\", \\"2\\", \\"3\\"][2..-1]"`);
39+
expect(queryResult.params).toMatchInlineSnapshot(`{}`);
40+
});
41+
42+
test("get range from arbitrary expression", () => {
43+
const collect = Cypher.collect(new Cypher.Variable());
44+
const listIndex = Cypher.listRange(collect, 2, 3);
45+
const queryResult = new TestClause(listIndex).build();
46+
47+
expect(queryResult.cypher).toMatchInlineSnapshot(`"collect(var0)[2..3]"`);
48+
expect(queryResult.params).toMatchInlineSnapshot(`{}`);
49+
});
50+
});

src/expressions/list/ListRange.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import type { CypherEnvironment } from "../../Environment";
21+
import type { CypherCompilable, Expr } from "../../types";
22+
23+
/**
24+
* @group Lists
25+
*/
26+
export class ListRange implements CypherCompilable {
27+
private readonly value: Expr;
28+
private readonly from: number;
29+
private readonly to: number;
30+
31+
/**
32+
* @internal
33+
*/
34+
constructor(variable: Expr, from: number, to: number) {
35+
this.value = variable;
36+
this.from = from;
37+
this.to = to;
38+
}
39+
40+
/** @internal */
41+
public getCypher(env: CypherEnvironment): string {
42+
return `${this.value.getCypher(env)}[${this.from}..${this.to}]`;
43+
}
44+
}
45+
46+
/** Adds a list range operator (`[ .. ]`) to an expression
47+
* @example
48+
* ```cypher
49+
* collect(var)[1..2]
50+
* ```
51+
*/
52+
export function listRange(expr: Expr, from: number, to: number): ListRange {
53+
return new ListRange(expr, from, to);
54+
}

src/references/PropertyRef.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ describe("Property", () => {
9696
expect(queryResult.cypher).toMatchInlineSnapshot(`"var0.myProperty[5]"`);
9797
});
9898

99+
test("List range access on property", () => {
100+
const variable = new Cypher.Variable();
101+
const property = variable.property("myProperty").range(1, -1);
102+
103+
const testClause = new TestClause(property);
104+
105+
const queryResult = testClause.build();
106+
expect(queryResult.cypher).toMatchInlineSnapshot(`"var0.myProperty[1..-1]"`);
107+
});
108+
99109
describe("Expression", () => {
100110
test("Serialize expression with []", () => {
101111
const variable = new Cypher.Variable();

src/references/PropertyRef.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
*/
1919

2020
import type { CypherEnvironment } from "../Environment";
21-
import { ListIndex } from "../expressions/list/ListIndex";
21+
import { listIndex, type ListIndex } from "../expressions/list/ListIndex";
22+
import type { ListRange } from "../expressions/list/ListRange";
23+
import { listRange } from "../expressions/list/ListRange";
2224
import type { CypherCompilable, Expr } from "../types";
2325
import { escapeProperty } from "../utils/escape";
2426
import type { Variable } from "./Variable";
@@ -48,9 +50,14 @@ export class PropertyRef implements CypherCompilable {
4850
return new PropertyRef(this._variable, ...this.propertyPath, prop);
4951
}
5052

51-
/* Access individual elements via the ListIndex class, using the square bracket notation */
53+
/** Access individual elements in the list */
5254
public index(index: number): ListIndex {
53-
return new ListIndex(this, index);
55+
return listIndex(this, index);
56+
}
57+
58+
/** Adds a list range operator (`[ .. ]`) */
59+
public range(from: number, to: number): ListRange {
60+
return listRange(this, from, to);
5461
}
5562

5663
/** @internal */

0 commit comments

Comments
 (0)