Skip to content

Commit

Permalink
Add support for multiple expressions in case
Browse files Browse the repository at this point in the history
  • Loading branch information
angrykoala committed Jul 26, 2024
1 parent 4190597 commit 5fa3f51
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 15 deletions.
12 changes: 12 additions & 0 deletions .changeset/many-months-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@neo4j/cypher-builder": patch
---

Add support for multiple expressions on the simple CASE:

```cypher
matchClause.return(
new Cypher.Case(person.property("eyes"))
.when(new Cypher.Literal("brown"), new Cypher.Literal("hazel"))
.then(new Cypher.Literal(2))
```
1 change: 1 addition & 0 deletions docs/modules/ROOT/content-nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* How-to guides
** xref:how-to/concatenate-clauses.adoc[]
** xref:how-to/customize-cypher.adoc[]
** xref:how-to/conditional-expressions.adoc[]
** xref:how-to/use-change-data-capture.adoc[]
** xref:how-to/type-predicate-expressions.adoc[]
* link:https://github.com/neo4j/cypher-builder/tree/main/examples[Examples]
Expand Down
31 changes: 31 additions & 0 deletions docs/modules/ROOT/pages/how-to/conditional-expressions.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[[conditional-expressions]]
:description: This page describes how to create conditional expressions with CASE.
= Conditional Expressions (CASE)

This page describes how to create link:https://neo4j.com/docs/cypher-manual/current/queries/case/[`CASE`] expressions in Cypher with Cypher Builder.


== Simple CASE

THe simple `CASE` form compares a single expressions against multiple values. It can be constructed with the `Case` class by passing the expression to be compared the constructor:


[source, javascript]
----
const person = new Cypher.Node();
new Cypher.Case(person.property("eyes"))
.when(new Cypher.Literal("blue")).then(new Cypher.Literal(1))
.when(new Cypher.Literal("brown"), new Cypher.Literal("hazel")).then(new Cypher.Literal(2))
.else(new Cypher.Literal(3))
----

The resulting Cypher, note that `END` is added automatically:

[source, cypher]
----
CASE n.eyes
WHEN 'blue' THEN 1
WHEN 'brown', 'hazel' THEN 2
ELSE 3
END
----
42 changes: 36 additions & 6 deletions src/expressions/Case.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,23 @@ describe("Case", () => {
test("case ... then ... else with comparator", () => {
const testParam = new Cypher.Param("Hello");

const caseClause = new Cypher.Case(testParam).when(new Cypher.Literal("Hello")).then(new Cypher.Literal(true));
const caseClause = new Cypher.Case(testParam)
.when(new Cypher.Literal("Hello"))
.then(new Cypher.Literal(true))
.when(new Cypher.Literal("Bye"))
.then(new Cypher.Literal(false));

caseClause.else(new Cypher.Literal(false));

const queryResult = new TestClause(caseClause).build();

expect(queryResult.cypher).toMatchInlineSnapshot(`
"CASE $param0
WHEN \\"Hello\\" THEN true
ELSE false
END"
`);
"CASE $param0
WHEN \\"Hello\\" THEN true
WHEN \\"Bye\\" THEN false
ELSE false
END"
`);

expect(queryResult.params).toMatchInlineSnapshot(`
{
Expand Down Expand Up @@ -78,4 +83,29 @@ describe("Case", () => {
new TestClause(caseClause).build();
}).toThrow("Cannot generate CASE ... WHEN statement without THEN");
});

test("doc example 1", () => {
const person = new Cypher.Node();
const matchClause = new Cypher.Match(new Cypher.Pattern(person, { labels: ["Person"] }));

matchClause.return(
new Cypher.Case(person.property("eyes"))
.when(new Cypher.Literal("blue"))
.then(new Cypher.Literal(1))
.when(new Cypher.Literal("brown"), new Cypher.Literal("hazel"))
.then(new Cypher.Literal(2))
.else(new Cypher.Literal(3))
);

const { cypher } = new TestClause(matchClause).build();

expect(cypher).toMatchInlineSnapshot(`
"MATCH (this0:Person)
RETURN CASE this0.eyes
WHEN \\"blue\\" THEN 1
WHEN \\"brown\\", \\"hazel\\" THEN 2
ELSE 3
END"
`);
});
});
19 changes: 10 additions & 9 deletions src/expressions/Case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
* limitations under the License.
*/

import type { CypherEnvironment } from "../Environment";
import { CypherASTNode } from "../CypherASTNode";
import { padBlock } from "../utils/pad-block";
import { compileCypherIfExists } from "../utils/compile-cypher-if-exists";
import type { CypherEnvironment } from "../Environment";
import type { Expr, Predicate } from "../types";
import { compileCypherIfExists } from "../utils/compile-cypher-if-exists";
import { padBlock } from "../utils/pad-block";

/** Case statement
* @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual/current/syntax/expressions/#query-syntax-case)
Expand All @@ -37,8 +37,9 @@ export class Case<C extends Expr | undefined = undefined> extends CypherASTNode
this.comparator = comparator;
}

public when(expr: C extends Expr ? Expr : Predicate): When<C> {
const whenClause = new When(this, expr);
// public when(expr: C extends Expr ? Expr : Predicate): When<C> {
public when(...exprs: C extends Expr ? Expr[] : [Predicate]): When<C> {
const whenClause = new When(this, exprs);
this.whenClauses.push(whenClause);
return whenClause;
}
Expand All @@ -62,13 +63,13 @@ export class Case<C extends Expr | undefined = undefined> extends CypherASTNode

class When<T extends Expr | undefined> extends CypherASTNode {
protected parent: Case<T>;
private predicate: Expr;
private predicates: Expr[];
private result: Expr | undefined;

constructor(parent: Case<T>, predicate: Expr) {
constructor(parent: Case<T>, predicate: Expr[]) {
super();
this.parent = parent;
this.predicate = predicate;
this.predicates = predicate;
}

public then(expr: Expr): Case<T> {
Expand All @@ -80,7 +81,7 @@ class When<T extends Expr | undefined> extends CypherASTNode {
* @internal
*/
public getCypher(env: CypherEnvironment): string {
const predicateStr = this.predicate.getCypher(env);
const predicateStr = this.predicates.map((p) => p.getCypher(env)).join(", ");
if (!this.result) throw new Error("Cannot generate CASE ... WHEN statement without THEN");
const resultStr = this.result.getCypher(env);

Expand Down

0 comments on commit 5fa3f51

Please sign in to comment.