diff --git a/.changeset/plenty-lobsters-boil.md b/.changeset/plenty-lobsters-boil.md new file mode 100644 index 00000000..5a78a0b5 --- /dev/null +++ b/.changeset/plenty-lobsters-boil.md @@ -0,0 +1,8 @@ +--- +"@neo4j/cypher-builder": patch +--- + +Add support for missing fulltext procedures: + +- `db.index.fulltext.awaitEventuallyConsistentIndexRefresh` +- `db.index.fulltext.listAvailableAnalyzers` diff --git a/.changeset/red-guests-hunt.md b/.changeset/red-guests-hunt.md new file mode 100644 index 00000000..c5eb54b4 --- /dev/null +++ b/.changeset/red-guests-hunt.md @@ -0,0 +1,11 @@ +--- +"@neo4j/cypher-builder": patch +--- + +Add support for missing db procedures: + +- `db.ping` +- `db.propertyKeys` +- `db.relationshipTypes` +- `db.resampleIndex` +- `db.resampleOutdatedIndexes` diff --git a/package.json b/package.json index 60964739..59f0483b 100644 --- a/package.json +++ b/package.json @@ -58,5 +58,6 @@ "ts-jest": "^29.1.1", "typedoc": "^0.26.0", "typescript": "^5.3.2" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/clauses/Call.test.ts b/src/clauses/Call.test.ts index fe8232e8..dc083cdf 100644 --- a/src/clauses/Call.test.ts +++ b/src/clauses/Call.test.ts @@ -22,11 +22,11 @@ import Cypher from ".."; describe("CypherBuilder Call", () => { test("Wraps query inside Call", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); - const createQuery = new Cypher.Create(movieNode).set([movieNode.property("id"), idParam]).return(movieNode); + const createQuery = new Cypher.Create(new Cypher.Pattern(movieNode, { labels: ["Movie"] })) + .set([movieNode.property("id"), idParam]) + .return(movieNode); const queryResult = new Cypher.Call(createQuery).build(); expect(queryResult.cypher).toMatchInlineSnapshot(` "CALL { @@ -45,11 +45,11 @@ describe("CypherBuilder Call", () => { test("Nested Call", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); - const createQuery = new Cypher.Create(movieNode).set([movieNode.property("id"), idParam]).return(movieNode); + const createQuery = new Cypher.Create(new Cypher.Pattern(movieNode, { labels: ["Movie"] })) + .set([movieNode.property("id"), idParam]) + .return(movieNode); const nestedCall = new Cypher.Call(createQuery); const call = new Cypher.Call(nestedCall); const queryResult = call.build(); @@ -72,9 +72,9 @@ describe("CypherBuilder Call", () => { }); test("CALL with import with", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), "movie"]); @@ -98,9 +98,12 @@ describe("CypherBuilder Call", () => { }); test("CALL with import with *", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node).return([node.property("title"), "movie"]); + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })).return([ + node.property("title"), + "movie", + ]); const clause = new Cypher.Call(matchClause).importWith("*"); const queryResult = clause.build(); @@ -116,9 +119,12 @@ describe("CypherBuilder Call", () => { }); test("CALL with import with * and extra fields", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node).return([node.property("title"), "movie"]); + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })).return([ + node.property("title"), + "movie", + ]); const clause = new Cypher.Call(matchClause).importWith(node, "*"); const queryResult = clause.build(); @@ -134,9 +140,9 @@ describe("CypherBuilder Call", () => { }); test("CALL with import with without parameters", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), "movie"]); @@ -159,9 +165,9 @@ describe("CypherBuilder Call", () => { }); test("CALL with import with multiple parameters", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), "movie"]); @@ -185,9 +191,9 @@ describe("CypherBuilder Call", () => { }); test("CALL with import with fails if import with is already set", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), "movie"]); @@ -198,9 +204,9 @@ describe("CypherBuilder Call", () => { }); test("CALL with external with", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), "movie"]); @@ -224,9 +230,9 @@ describe("CypherBuilder Call", () => { }); test("CALL with external with, set and remove", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return(node); @@ -257,9 +263,9 @@ WITH *" }); test("CALL with external with clause", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), "movie"]); @@ -283,10 +289,10 @@ WITH *" }); test("CALL with unwind", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); const movie = new Cypher.Variable(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), movie]); @@ -311,10 +317,10 @@ WITH *" }); test("CALL with unwind passed as a clause", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); const movie = new Cypher.Variable(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(new Cypher.Param("aa"), new Cypher.Param("bb"))) .return([node.property("title"), movie]); @@ -341,9 +347,9 @@ WITH *" }); test("CALL with delete", () => { - const node = new Cypher.Node({ labels: ["Movie"] }); + const node = new Cypher.Node(); - const matchClause = new Cypher.Match(node) + const matchClause = new Cypher.Match(new Cypher.Pattern(node, { labels: ["Movie"] })) .where(Cypher.eq(node.property("title"), new Cypher.Param("bb"))) .return(node); @@ -368,12 +374,12 @@ DELETE this0" test("Call returns a variable", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const variable = new Cypher.Variable(); - const createQuery = new Cypher.Create(movieNode).set([movieNode.property("id"), idParam]).return(variable); + const createQuery = new Cypher.Create(new Cypher.Pattern(movieNode, { labels: ["Movie"] })) + .set([movieNode.property("id"), idParam]) + .return(variable); const queryResult = new Cypher.Call(createQuery).return(variable).build(); expect(queryResult.cypher).toMatchInlineSnapshot(` "CALL { @@ -415,7 +421,7 @@ CALL { test("Call in transaction of rows", () => { const query = Cypher.concat( - new Cypher.Match(node), + new Cypher.Match(new Cypher.Pattern(node)), new Cypher.Call(subquery).inTransactions({ ofRows: 10, }) @@ -433,7 +439,7 @@ CALL { test("Call in transaction on error fail", () => { const query = Cypher.concat( - new Cypher.Match(node), + new Cypher.Match(new Cypher.Pattern(node)), new Cypher.Call(subquery).inTransactions({ onError: "fail", }) @@ -450,7 +456,7 @@ CALL { }); test("Call in transaction on error break", () => { const query = Cypher.concat( - new Cypher.Match(node), + new Cypher.Match(new Cypher.Pattern(node)), new Cypher.Call(subquery).inTransactions({ onError: "break", }) @@ -468,7 +474,7 @@ CALL { test("Call in transaction on error continue", () => { const query = Cypher.concat( - new Cypher.Match(node), + new Cypher.Match(new Cypher.Pattern(node)), new Cypher.Call(subquery).inTransactions({ onError: "continue", }) @@ -489,7 +495,7 @@ CALL { const deleteSubquery = new Cypher.With(node).detachDelete(node); const query = Cypher.concat( - new Cypher.Match(node), + new Cypher.Match(new Cypher.Pattern(node)), new Cypher.Call(deleteSubquery).inTransactions({ ofRows: 10, onError: "fail", diff --git a/src/clauses/Create.test.ts b/src/clauses/Create.test.ts index 1ff2547c..4127bea2 100644 --- a/src/clauses/Create.test.ts +++ b/src/clauses/Create.test.ts @@ -22,14 +22,15 @@ import Cypher from ".."; describe("CypherBuilder Create", () => { test("Create Node", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set( @@ -62,15 +63,18 @@ describe("CypherBuilder Create", () => { const testParam = new Cypher.Param(null); const nullStringParam = new Cypher.Param("null"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const properties = { id: idParam, }; - const createQuery = new Cypher.Create(new Cypher.Pattern(movieNode).withProperties(properties)) + const createQuery = new Cypher.Create( + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties, + }) + ) .set([movieNode.property("test"), testParam], [movieNode.property("nullStr"), nullStringParam]) .return(movieNode); @@ -95,15 +99,22 @@ describe("CypherBuilder Create", () => { const testParam = new Cypher.Param(null); const nullStringParam = new Cypher.Param("null"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const properties = { id: idParam, }; const path = new Cypher.Path(); - const createQuery = new Cypher.Create(new Cypher.Pattern(movieNode).withProperties(properties)) + const createQuery = new Cypher.Create( + new Cypher.Pattern( + movieNode, + + { + labels: ["Movie"], + properties, + } + ) + ) .assignToPath(path) .set([movieNode.property("test"), testParam], [movieNode.property("nullStr"), nullStringParam]) .return(movieNode); @@ -126,14 +137,15 @@ describe("CypherBuilder Create", () => { test("Create Node with empty set", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set() @@ -155,14 +167,15 @@ describe("CypherBuilder Create", () => { test("Create with delete", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set( @@ -192,14 +205,15 @@ DELETE this0" test("Create with detach delete", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set( @@ -229,14 +243,15 @@ DETACH DELETE this0" test("Create with noDetach delete", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set( @@ -266,14 +281,15 @@ NODETACH DELETE this0" test("Create with remove", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set( @@ -308,9 +324,12 @@ REMOVE this0.title" }); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set( @@ -342,16 +361,17 @@ RETURN this0" test("Chained create with existing create Clause", () => { const idParam = new Cypher.Param("my-id"); - const movieNode = new Cypher.Node({ - labels: ["Movie"], - }); + const movieNode = new Cypher.Node(); const secondCreate = new Cypher.Create(new Cypher.Node({ labels: ["Actor"] })); const createQuery = new Cypher.Create( - new Cypher.Pattern(movieNode).withProperties({ - test: new Cypher.Param("test-value"), - id: idParam, + new Cypher.Pattern(movieNode, { + labels: ["Movie"], + properties: { + test: new Cypher.Param("test-value"), + id: idParam, + }, }) ) .set( diff --git a/src/clauses/Foreach.test.ts b/src/clauses/Foreach.test.ts index 60dafb40..1aee7aa2 100644 --- a/src/clauses/Foreach.test.ts +++ b/src/clauses/Foreach.test.ts @@ -24,8 +24,11 @@ describe("Foreach", () => { const list = new Cypher.Literal([1, 2, 3]); const variable = new Cypher.Variable(); - const movieNode = new Cypher.Node({ labels: ["Movie"] }); - const createMovie = new Cypher.Create(movieNode).set([movieNode.property("id"), variable]); + const movieNode = new Cypher.Node(); + const createMovie = new Cypher.Create(new Cypher.Pattern(movieNode, { labels: ["Movie"] })).set([ + movieNode.property("id"), + variable, + ]); const foreachClause = new Cypher.Foreach(variable, list, createMovie).with("*"); @@ -46,8 +49,8 @@ describe("Foreach", () => { const list = new Cypher.Literal([1, 2, 3]); const variable = new Cypher.Variable(); - const movieNode = new Cypher.Node({ labels: ["Movie"] }); - const createMovie = new Cypher.Create(movieNode); + const movieNode = new Cypher.Node(); + const createMovie = new Cypher.Create(new Cypher.Pattern(movieNode, { labels: ["Movie"] })); const foreachClause = new Cypher.Foreach(variable, list, createMovie) .remove(movieNode.property("title")) @@ -74,8 +77,8 @@ WITH *" const list = new Cypher.Literal([1, 2, 3]); const variable = new Cypher.Variable(); - const movieNode = new Cypher.Node({ labels: ["Movie"] }); - const createMovie = new Cypher.Create(movieNode); + const movieNode = new Cypher.Node(); + const createMovie = new Cypher.Create(new Cypher.Pattern(movieNode, { labels: ["Movie"] })); const foreachClause = new Cypher.Foreach(variable, list, createMovie).detachDelete(movieNode).with("*"); diff --git a/src/namespaces/db/db.test.ts b/src/namespaces/db/db.test.ts index 6c074781..9386f56a 100644 --- a/src/namespaces/db/db.test.ts +++ b/src/namespaces/db/db.test.ts @@ -21,231 +21,51 @@ import Cypher from "../../index"; import { TestClause } from "../../utils/TestClause"; describe("db procedures", () => { - describe("db.index.fulltext.queryNodes", () => { - test("Simple fulltext", () => { - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const fulltextProcedure = Cypher.db.index.fulltext - .queryNodes("my-text-index", new Cypher.Param("This is a lovely phrase")) - .yield(["node", targetNode]); - - const { cypher, params } = fulltextProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.fulltext.queryNodes(\\"my-text-index\\", $param0) YIELD node AS this0"` - ); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - } - `); - }); + test("db.awaitIndex", () => { + const procedure = Cypher.db.awaitIndex("name", 123); - test("Fulltext with where and return", () => { - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const fulltextProcedure = Cypher.db.index.fulltext - .queryNodes("my-text-index", new Cypher.Param("This is a lovely phrase")) - .yield(["node", targetNode]) - .where(Cypher.eq(targetNode.property("title"), new Cypher.Param("The Matrix"))) - .return(targetNode); - - const { cypher, params } = fulltextProcedure.build(); - - expect(cypher).toMatchInlineSnapshot(` - "CALL db.index.fulltext.queryNodes(\\"my-text-index\\", $param0) YIELD node AS this0 - WHERE this0.title = $param1 - RETURN this0" - `); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - "param1": "The Matrix", - } - `); - }); + const { cypher } = procedure.build(); - test("Fulltext with options", () => { - const fulltextProcedure = Cypher.db.index.fulltext.queryNodes( - "my-text-index", - new Cypher.Param("This is a lovely phrase"), - { - skip: 5, - analyser: new Cypher.Param("whitespace"), - } - ); - - const { cypher, params } = fulltextProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.fulltext.queryNodes(\\"my-text-index\\", $param0, { skip: 5, analyser: $param1 })"` - ); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - "param1": "whitespace", - } - `); - }); + expect(cypher).toMatchInlineSnapshot(`"CALL db.awaitIndex(\\"name\\", 123)"`); }); - describe("db.index.fulltext.queryRelationships", () => { - test("Simple fulltext", () => { - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const fulltextProcedure = Cypher.db.index.fulltext - .queryRelationships("my-text-index", new Cypher.Param("This is a lovely phrase")) - .yield(["relationship", targetNode]); - - const { cypher, params } = fulltextProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.fulltext.queryRelationships(\\"my-text-index\\", $param0) YIELD relationship AS this0"` - ); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - } - `); - }); + test("db.awaitIndexes", () => { + const procedure = Cypher.db.awaitIndexes(123); - test("Fulltext with options", () => { - const fulltextProcedure = Cypher.db.index.fulltext.queryRelationships( - "my-text-index", - new Cypher.Param("This is a lovely phrase"), - { - skip: 5, - analyser: new Cypher.Param("whitespace"), - } - ); - - const { cypher, params } = fulltextProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.fulltext.queryRelationships(\\"my-text-index\\", $param0, { skip: 5, analyser: $param1 })"` - ); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - "param1": "whitespace", - } - `); - }); - }); - describe("db.index.vector.queryNodes", () => { - test("Simple vector", () => { - const nearestNeighbours = 10; - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const vectorProcedure = Cypher.db.index.vector - .queryNodes("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) - .yield(["node", targetNode]); - - const { cypher, params } = vectorProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.vector.queryNodes(\\"my-vector-index\\", 10, $param0) YIELD node AS this0"` - ); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - } - `); - }); - test("Simple vector", () => { - const nearestNeighbours = 5; - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const vectorProcedure = Cypher.db.index.vector - .queryNodes("my-vector-index", nearestNeighbours, new Cypher.Literal("This is a lovely phrase literal")) - .yield(["node", targetNode]); - - const { cypher, params } = vectorProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.vector.queryNodes(\\"my-vector-index\\", 5, \\"This is a lovely phrase literal\\") YIELD node AS this0"` - ); - expect(params).toMatchInlineSnapshot(`{}`); - }); - test("vector with where and return", () => { - const nearestNeighbours = 15; - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const vectorProcedure = Cypher.db.index.vector - .queryNodes("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) - .yield(["node", targetNode]) - .where(Cypher.eq(targetNode.property("title"), new Cypher.Param("The Matrix"))) - .return(targetNode); - - const { cypher, params } = vectorProcedure.build(); - - expect(cypher).toMatchInlineSnapshot(` - "CALL db.index.vector.queryNodes(\\"my-vector-index\\", 15, $param0) YIELD node AS this0 - WHERE this0.title = $param1 - RETURN this0" - `); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - "param1": "The Matrix", - } - `); - }); + const { cypher } = procedure.build(); + + expect(cypher).toMatchInlineSnapshot(`"CALL db.awaitIndex(123)"`); }); - describe("db.index.vector.queryRelationships", () => { - test("Simple vector", () => { - const nearestNeighbours = 10; - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const vectorProcedure = Cypher.db.index.vector - .queryRelationships("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) - .yield(["relationship", targetNode]); - - const { cypher, params } = vectorProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.vector.queryRelationships(\\"my-vector-index\\", 10, $param0) YIELD relationship AS this0"` - ); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - } - `); - }); - test("Simple vector using literal", () => { - const nearestNeighbours = 10; - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const vectorProcedure = Cypher.db.index.vector - .queryRelationships( - "my-vector-index", - nearestNeighbours, - new Cypher.Literal("This is a lovely phrase literal") - ) - .yield(["relationship", targetNode]); - - const { cypher, params } = vectorProcedure.build(); - - expect(cypher).toMatchInlineSnapshot( - `"CALL db.index.vector.queryRelationships(\\"my-vector-index\\", 10, \\"This is a lovely phrase literal\\") YIELD relationship AS this0"` - ); - expect(params).toMatchInlineSnapshot(`{}`); - }); - test("vector with where and return", () => { - const nearestNeighbours = 5; - const targetNode = new Cypher.Node({ labels: ["Movie"] }); - const vectorProcedure = Cypher.db.index.vector - .queryRelationships("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) - .yield(["relationship", targetNode]) - .where(Cypher.eq(targetNode.property("title"), new Cypher.Param("The Matrix"))) - .return(targetNode); - - const { cypher, params } = vectorProcedure.build(); - - expect(cypher).toMatchInlineSnapshot(` - "CALL db.index.vector.queryRelationships(\\"my-vector-index\\", 5, $param0) YIELD relationship AS this0 - WHERE this0.title = $param1 - RETURN this0" - `); - expect(params).toMatchInlineSnapshot(` - { - "param0": "This is a lovely phrase", - "param1": "The Matrix", - } - `); - }); + + test.each(["createLabel", "createProperty", "createRelationshipType"] as const)( + "%s with string parameter", + (procedureName) => { + const procedure = Cypher.db[procedureName]("param"); + + const { cypher } = procedure.build(); + + expect(cypher).toEqual(`CALL db.${procedureName}("param")`); + } + ); + + test.each(["createLabel", "createProperty", "createRelationshipType"] as const)( + "%s with Expr parameter", + (procedureName) => { + const procedure = Cypher.db[procedureName](new Cypher.Literal("param")); + + const { cypher } = procedure.build(); + + expect(cypher).toEqual(`CALL db.${procedureName}("param")`); + } + ); + + test("db.info", () => { + const dbInfo = Cypher.db.info().yield("id", "creationDate"); + + const { cypher } = dbInfo.build(); + + expect(cypher).toMatchInlineSnapshot(`"CALL db.info() YIELD id, creationDate"`); }); + describe("db.labels", () => { test("db.labels without yield", () => { const dbLabels = Cypher.db.labels(); @@ -278,35 +98,40 @@ describe("db procedures", () => { }); }); - test("db.info", () => { - const dbInfo = Cypher.db.info().yield("id", "creationDate"); + test("db.ping", () => { + const procedure = Cypher.db.ping().yield("success"); + const { cypher } = procedure.build(); - const { cypher } = dbInfo.build(); + expect(cypher).toMatchInlineSnapshot(`"CALL db.ping() YIELD success"`); + }); - expect(cypher).toMatchInlineSnapshot(`"CALL db.info() YIELD id, creationDate"`); + test("db.propertyKeys", () => { + const procedure = Cypher.db.propertyKeys().yield("propertyKey"); + const { cypher } = procedure.build(); + + expect(cypher).toMatchInlineSnapshot(`"CALL db.propertyKeys() YIELD propertyKey"`); }); - test.each(["createLabel", "createProperty", "createRelationshipType"] as const)( - "%s with string parameter", - (procedureName) => { - const procedure = Cypher.db[procedureName]("param"); + test("db.relationshipTypes", () => { + const procedure = Cypher.db.relationshipTypes().yield("relationshipType"); + const { cypher } = procedure.build(); - const { cypher } = procedure.build(); + expect(cypher).toMatchInlineSnapshot(`"CALL db.relationshipTypes() YIELD relationshipType"`); + }); - expect(cypher).toEqual(`CALL db.${procedureName}("param")`); - } - ); + test("db.resampleIndex", () => { + const procedure = Cypher.db.resampleIndex().yield("indexName"); + const { cypher } = procedure.build(); - test.each(["createLabel", "createProperty", "createRelationshipType"] as const)( - "%s with Expr parameter", - (procedureName) => { - const procedure = Cypher.db[procedureName](new Cypher.Literal("param")); + expect(cypher).toMatchInlineSnapshot(`"CALL db.resampleIndex() YIELD indexName"`); + }); - const { cypher } = procedure.build(); + test("db.resampleOutdatedIndexes", () => { + const procedure = Cypher.db.resampleOutdatedIndexes(); + const { cypher } = procedure.build(); - expect(cypher).toEqual(`CALL db.${procedureName}("param")`); - } - ); + expect(cypher).toMatchInlineSnapshot(`"CALL db.resampleOutdatedIndexes()"`); + }); }); describe("db functions", () => { diff --git a/src/namespaces/db/db.ts b/src/namespaces/db/db.ts index 6f76d703..cbb520ea 100644 --- a/src/namespaces/db/db.ts +++ b/src/namespaces/db/db.ts @@ -18,8 +18,7 @@ */ import { CypherFunction } from "../../expressions/functions/CypherFunctions"; -import type { VoidCypherProcedure } from "../../procedures/CypherProcedure"; -import { CypherProcedure } from "../../procedures/CypherProcedure"; +import { CypherProcedure, VoidCypherProcedure } from "../../procedures/CypherProcedure"; import type { Expr } from "../../types"; import { normalizeExpr } from "../../utils/normalize-variable"; @@ -29,13 +28,7 @@ export * as schema from "./schema"; const DB_NAMESPACE = "db"; -/** Returns all labels in the database - * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/5/reference/procedures/#procedure_db_labels) - * @group Procedures - */ -export function labels(): CypherProcedure<"label"> { - return new CypherProcedure("labels", [], DB_NAMESPACE); -} +// FUNCTIONS /** * @see [Neo4j Documentation](https://neo4j.com/docs/cypher-manual/current/functions/database/#functions-database-nameFromElementId) @@ -46,12 +39,25 @@ export function nameFromElementId(dbName: Expr | string): CypherFunction { return new CypherFunction("nameFromElementId", [dbNameExpr], DB_NAMESPACE); } +// PROCEDURES + /** - * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_info) - * @group Procedures + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_awaitindex) + * @group Functions */ -export function info(): CypherProcedure<"id" | "creationDate"> { - return new CypherProcedure("info", [], DB_NAMESPACE); +export function awaitIndex(indexName: Expr | string, timeOutSeconds: Expr | number): VoidCypherProcedure { + const indexNameExpr = normalizeExpr(indexName); + const timeOutSecondsExpr = normalizeExpr(timeOutSeconds); + return new VoidCypherProcedure("awaitIndex", [indexNameExpr, timeOutSecondsExpr], DB_NAMESPACE); +} + +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_awaitindexes) + * @group Functions + */ +export function awaitIndexes(timeOutSeconds: Expr | number): VoidCypherProcedure { + const timeOutSecondsExpr = normalizeExpr(timeOutSeconds); + return new VoidCypherProcedure("awaitIndex", [timeOutSecondsExpr], DB_NAMESPACE); } /** @@ -60,7 +66,7 @@ export function info(): CypherProcedure<"id" | "creationDate"> { */ export function createLabel(newLabel: Expr | string): VoidCypherProcedure { const newLabelExpr = normalizeExpr(newLabel); - return new CypherProcedure("createLabel", [newLabelExpr], DB_NAMESPACE); + return new VoidCypherProcedure("createLabel", [newLabelExpr], DB_NAMESPACE); } /** @@ -69,7 +75,7 @@ export function createLabel(newLabel: Expr | string): VoidCypherProcedure { */ export function createProperty(newProperty: Expr | string): VoidCypherProcedure { const newPropertyExpr = normalizeExpr(newProperty); - return new CypherProcedure("createProperty", [newPropertyExpr], DB_NAMESPACE); + return new VoidCypherProcedure("createProperty", [newPropertyExpr], DB_NAMESPACE); } /** @@ -78,5 +84,60 @@ export function createProperty(newProperty: Expr | string): VoidCypherProcedure */ export function createRelationshipType(newRelationshipType: Expr | string): VoidCypherProcedure { const newRelationshipTypeExpr = normalizeExpr(newRelationshipType); - return new CypherProcedure("createRelationshipType", [newRelationshipTypeExpr], DB_NAMESPACE); + return new VoidCypherProcedure("createRelationshipType", [newRelationshipTypeExpr], DB_NAMESPACE); +} + +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_info) + * @group Procedures + */ +export function info(): CypherProcedure<"id" | "creationDate"> { + return new CypherProcedure("info", [], DB_NAMESPACE); +} + +/** Returns all labels in the database + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/5/reference/procedures/#procedure_db_labels) + * @group Procedures + */ +export function labels(): CypherProcedure<"label"> { + return new CypherProcedure("labels", [], DB_NAMESPACE); +} + +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_ping) + * @group Procedures + */ +export function ping(): CypherProcedure<"success"> { + return new CypherProcedure("ping", [], DB_NAMESPACE); +} + +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_propertykeys) + * @group Procedures + */ +export function propertyKeys(): CypherProcedure<"propertyKey"> { + return new CypherProcedure("propertyKeys", [], DB_NAMESPACE); +} + +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_relationshiptypes) + * @group Procedures + */ +export function relationshipTypes(): CypherProcedure<"relationshipType"> { + return new CypherProcedure("relationshipTypes", [], DB_NAMESPACE); +} + +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_resampleindex) + * @group Procedures + */ +export function resampleIndex(): CypherProcedure<"indexName"> { + return new CypherProcedure("resampleIndex", [], DB_NAMESPACE); +} +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_resampleoutdatedindexes) + * @group Procedures + */ +export function resampleOutdatedIndexes(): VoidCypherProcedure { + return new CypherProcedure("resampleOutdatedIndexes", [], DB_NAMESPACE); } diff --git a/src/namespaces/db/index/fulltext.test.ts b/src/namespaces/db/index/fulltext.test.ts new file mode 100644 index 00000000..e70539d0 --- /dev/null +++ b/src/namespaces/db/index/fulltext.test.ts @@ -0,0 +1,149 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Cypher from "../../../index"; + +describe("db.index.fulltext", () => { + describe("db.index.fulltext.queryNodes", () => { + test("Simple fulltext", () => { + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const fulltextProcedure = Cypher.db.index.fulltext + .queryNodes("my-text-index", new Cypher.Param("This is a lovely phrase")) + .yield(["node", targetNode]); + + const { cypher, params } = fulltextProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.fulltext.queryNodes(\\"my-text-index\\", $param0) YIELD node AS this0"` + ); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + } + `); + }); + + test("Fulltext with where and return", () => { + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const fulltextProcedure = Cypher.db.index.fulltext + .queryNodes("my-text-index", new Cypher.Param("This is a lovely phrase")) + .yield(["node", targetNode]) + .where(Cypher.eq(targetNode.property("title"), new Cypher.Param("The Matrix"))) + .return(targetNode); + + const { cypher, params } = fulltextProcedure.build(); + + expect(cypher).toMatchInlineSnapshot(` + "CALL db.index.fulltext.queryNodes(\\"my-text-index\\", $param0) YIELD node AS this0 + WHERE this0.title = $param1 + RETURN this0" + `); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + "param1": "The Matrix", + } + `); + }); + + test("Fulltext with options", () => { + const fulltextProcedure = Cypher.db.index.fulltext.queryNodes( + "my-text-index", + new Cypher.Param("This is a lovely phrase"), + { + skip: 5, + analyser: new Cypher.Param("whitespace"), + } + ); + + const { cypher, params } = fulltextProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.fulltext.queryNodes(\\"my-text-index\\", $param0, { skip: 5, analyser: $param1 })"` + ); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + "param1": "whitespace", + } + `); + }); + }); + + describe("db.index.fulltext.queryRelationships", () => { + test("Simple fulltext", () => { + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const fulltextProcedure = Cypher.db.index.fulltext + .queryRelationships("my-text-index", new Cypher.Param("This is a lovely phrase")) + .yield(["relationship", targetNode]); + + const { cypher, params } = fulltextProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.fulltext.queryRelationships(\\"my-text-index\\", $param0) YIELD relationship AS this0"` + ); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + } + `); + }); + + test("Fulltext with options", () => { + const fulltextProcedure = Cypher.db.index.fulltext.queryRelationships( + "my-text-index", + new Cypher.Param("This is a lovely phrase"), + { + skip: 5, + analyser: new Cypher.Param("whitespace"), + } + ); + + const { cypher, params } = fulltextProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.fulltext.queryRelationships(\\"my-text-index\\", $param0, { skip: 5, analyser: $param1 })"` + ); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + "param1": "whitespace", + } + `); + }); + }); + + test("db.index.fulltext.awaitEventuallyConsistentIndexRefresh", () => { + const procedure = Cypher.db.index.fulltext.awaitEventuallyConsistentIndexRefresh(); + const { cypher } = procedure.build(); + + expect(cypher).toMatchInlineSnapshot(`"CALL db.index.fulltext.awaitEventuallyConsistentIndexRefresh()"`); + }); + + test("db.index.fulltext.listAvailableAnalyzers", () => { + const procedure = Cypher.db.index.fulltext + .listAvailableAnalyzers() + .yield("analyzer", "description", "stopwords"); + const { cypher } = procedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.fulltext.queryRelationships() YIELD analyzer, description, stopwords"` + ); + }); +}); diff --git a/src/namespaces/db/index/fulltext.ts b/src/namespaces/db/index/fulltext.ts index c783c95a..cd58661f 100644 --- a/src/namespaces/db/index/fulltext.ts +++ b/src/namespaces/db/index/fulltext.ts @@ -18,7 +18,7 @@ */ import type { Literal, Param, Variable } from "../../../Cypher"; -import { CypherProcedure } from "../../../procedures/CypherProcedure"; +import { CypherProcedure, VoidCypherProcedure } from "../../../procedures/CypherProcedure"; import type { Expr } from "../../../types"; import type { InputArgument } from "../../../utils/normalize-variable"; import { normalizeMap, normalizeVariable } from "../../../utils/normalize-variable"; @@ -27,7 +27,7 @@ type FulltextPhrase = string | Literal | Param | Variable; const FULLTEXT_NAMESPACE = "db.index.fulltext"; -/** Returns all labels in the database +/** * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_index_fulltext_querynodes) * @group Procedures */ @@ -41,7 +41,7 @@ export function queryNodes( return new CypherProcedure("queryNodes", procedureArgs, FULLTEXT_NAMESPACE); } -/** Returns all labels in the database +/** * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_index_fulltext_queryrelationships) * @group Procedures */ @@ -55,6 +55,22 @@ export function queryRelationships( return new CypherProcedure("queryRelationships", procedureArgs, FULLTEXT_NAMESPACE); } +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_index_fulltext_awaiteventuallyconsistentindexrefresh) + * @group Procedures + */ +export function awaitEventuallyConsistentIndexRefresh(): VoidCypherProcedure { + return new VoidCypherProcedure("awaitEventuallyConsistentIndexRefresh", [], FULLTEXT_NAMESPACE); +} + +/** + * @see [Neo4j Documentation](https://neo4j.com/docs/operations-manual/current/reference/procedures/#procedure_db_index_fulltext_listavailableanalyzers) + * @group Procedures + */ +export function listAvailableAnalyzers(): CypherProcedure<"analyzer" | "description" | "stopwords"> { + return new CypherProcedure("queryRelationships", [], FULLTEXT_NAMESPACE); +} + function getFulltextArguments( indexName: string | Literal, queryString: FulltextPhrase, diff --git a/src/namespaces/db/index/vector.test.ts b/src/namespaces/db/index/vector.test.ts new file mode 100644 index 00000000..b3b4d62d --- /dev/null +++ b/src/namespaces/db/index/vector.test.ts @@ -0,0 +1,139 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Cypher from "../../../index"; + +describe("db.index.vector.queryNodes", () => { + test("Simple vector", () => { + const nearestNeighbours = 10; + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const vectorProcedure = Cypher.db.index.vector + .queryNodes("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) + .yield(["node", targetNode]); + + const { cypher, params } = vectorProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.vector.queryNodes(\\"my-vector-index\\", 10, $param0) YIELD node AS this0"` + ); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + } + `); + }); + test("Simple vector", () => { + const nearestNeighbours = 5; + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const vectorProcedure = Cypher.db.index.vector + .queryNodes("my-vector-index", nearestNeighbours, new Cypher.Literal("This is a lovely phrase literal")) + .yield(["node", targetNode]); + + const { cypher, params } = vectorProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.vector.queryNodes(\\"my-vector-index\\", 5, \\"This is a lovely phrase literal\\") YIELD node AS this0"` + ); + expect(params).toMatchInlineSnapshot(`{}`); + }); + test("vector with where and return", () => { + const nearestNeighbours = 15; + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const vectorProcedure = Cypher.db.index.vector + .queryNodes("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) + .yield(["node", targetNode]) + .where(Cypher.eq(targetNode.property("title"), new Cypher.Param("The Matrix"))) + .return(targetNode); + + const { cypher, params } = vectorProcedure.build(); + + expect(cypher).toMatchInlineSnapshot(` + "CALL db.index.vector.queryNodes(\\"my-vector-index\\", 15, $param0) YIELD node AS this0 + WHERE this0.title = $param1 + RETURN this0" + `); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + "param1": "The Matrix", + } + `); + }); +}); +describe("db.index.vector.queryRelationships", () => { + test("Simple vector", () => { + const nearestNeighbours = 10; + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const vectorProcedure = Cypher.db.index.vector + .queryRelationships("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) + .yield(["relationship", targetNode]); + + const { cypher, params } = vectorProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.vector.queryRelationships(\\"my-vector-index\\", 10, $param0) YIELD relationship AS this0"` + ); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + } + `); + }); + test("Simple vector using literal", () => { + const nearestNeighbours = 10; + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const vectorProcedure = Cypher.db.index.vector + .queryRelationships( + "my-vector-index", + nearestNeighbours, + new Cypher.Literal("This is a lovely phrase literal") + ) + .yield(["relationship", targetNode]); + + const { cypher, params } = vectorProcedure.build(); + + expect(cypher).toMatchInlineSnapshot( + `"CALL db.index.vector.queryRelationships(\\"my-vector-index\\", 10, \\"This is a lovely phrase literal\\") YIELD relationship AS this0"` + ); + expect(params).toMatchInlineSnapshot(`{}`); + }); + test("vector with where and return", () => { + const nearestNeighbours = 5; + const targetNode = new Cypher.Node({ labels: ["Movie"] }); + const vectorProcedure = Cypher.db.index.vector + .queryRelationships("my-vector-index", nearestNeighbours, new Cypher.Param("This is a lovely phrase")) + .yield(["relationship", targetNode]) + .where(Cypher.eq(targetNode.property("title"), new Cypher.Param("The Matrix"))) + .return(targetNode); + + const { cypher, params } = vectorProcedure.build(); + + expect(cypher).toMatchInlineSnapshot(` + "CALL db.index.vector.queryRelationships(\\"my-vector-index\\", 5, $param0) YIELD relationship AS this0 + WHERE this0.title = $param1 + RETURN this0" + `); + expect(params).toMatchInlineSnapshot(` + { + "param0": "This is a lovely phrase", + "param1": "The Matrix", + } + `); + }); +});