-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add isType, isNotType and TYPES for type predicate expressions
- Loading branch information
1 parent
3889edd
commit f97c229
Showing
6 changed files
with
379 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
--- | ||
"@neo4j/cypher-builder": minor | ||
--- | ||
|
||
Add support for type predicate expressions with the functions `Cypher.isType` and `Cypher.isNotType`: | ||
|
||
```ts | ||
const variable = new Cypher.Variable(); | ||
const unwindClause = new Cypher.Unwind([new Cypher.Literal([42, true, "abc", null]), variable]).return( | ||
variable, | ||
Cypher.isType(variable, Cypher.TYPE.INTEGER) | ||
); | ||
``` | ||
|
||
```cypher | ||
UNWIND [42, true, \\"abc\\", NULL] AS var0 | ||
RETURN var0, var0 IS :: INTEGER | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
/* | ||
* 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 ".."; | ||
|
||
describe("IsType", () => { | ||
test("UNWIND return isType", () => { | ||
const variable = new Cypher.Variable(); | ||
const unwindClause = new Cypher.Unwind([new Cypher.Literal([42, true, "abc", null]), variable]).return( | ||
variable, | ||
Cypher.isType(variable, Cypher.TYPE.INTEGER) | ||
); | ||
|
||
const { cypher, params } = unwindClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"UNWIND [42, true, \\"abc\\", NULL] AS var0 | ||
RETURN var0, var0 IS :: INTEGER" | ||
`); | ||
expect(params).toMatchInlineSnapshot(`{}`); | ||
}); | ||
|
||
test.each([ | ||
Cypher.TYPE.ANY, | ||
Cypher.TYPE.BOOLEAN, | ||
Cypher.TYPE.DATE, | ||
Cypher.TYPE.DURATION, | ||
Cypher.TYPE.FLOAT, | ||
Cypher.TYPE.INTEGER, | ||
Cypher.TYPE.LOCAL_DATETIME, | ||
Cypher.TYPE.LOCAL_TIME, | ||
Cypher.TYPE.MAP, | ||
Cypher.TYPE.NODE, | ||
Cypher.TYPE.NOTHING, | ||
Cypher.TYPE.NULL, | ||
Cypher.TYPE.PATH, | ||
Cypher.TYPE.POINT, | ||
Cypher.TYPE.PROPERTY_VALUE, | ||
Cypher.TYPE.RELATIONSHIP, | ||
Cypher.TYPE.STRING, | ||
Cypher.TYPE.ZONED_DATETIME, | ||
Cypher.TYPE.ZONED_TIME, | ||
] as const)("isType '%s'", (type) => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie).where(Cypher.isType(movie.property("title"), type)).return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toEqual(`MATCH (this0:Movie) | ||
WHERE this0.title IS :: ${type} | ||
RETURN this0`); | ||
}); | ||
|
||
test("isType 'List<STRING>'", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.isType(movie.property("title"), Cypher.TYPE.list(Cypher.TYPE.STRING))) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE this0.title IS :: LIST<STRING> | ||
RETURN this0" | ||
`); | ||
}); | ||
|
||
test("isType 'List<List<STRING>>'", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.isType(movie.property("title"), Cypher.TYPE.list(Cypher.TYPE.list(Cypher.TYPE.STRING)))) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE this0.title IS :: LIST<LIST<STRING>> | ||
RETURN this0" | ||
`); | ||
}); | ||
|
||
test("isType with union type", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.isType(movie.property("title"), Cypher.TYPE.list(Cypher.TYPE.STRING), Cypher.TYPE.STRING)) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE this0.title IS :: LIST<STRING> | STRING | ||
RETURN this0" | ||
`); | ||
}); | ||
|
||
test("isType in NOT", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.not(Cypher.isType(movie.property("title"), Cypher.TYPE.STRING))) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE NOT (this0.title IS :: STRING) | ||
RETURN this0" | ||
`); | ||
}); | ||
|
||
test("isNotType", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.isNotType(movie.property("title"), Cypher.TYPE.STRING)) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE this0.title IS NOT :: STRING | ||
RETURN this0" | ||
`); | ||
}); | ||
|
||
describe("notNull", () => { | ||
test("isType.notNull", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.isType(movie.property("title"), Cypher.TYPE.STRING).notNull()) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE this0.title IS :: STRING NOT NULL | ||
RETURN this0" | ||
`); | ||
}); | ||
|
||
test("isNotType.notNull", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.isNotType(movie.property("title"), Cypher.TYPE.STRING).notNull()) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE this0.title IS NOT :: STRING NOT NULL | ||
RETURN this0" | ||
`); | ||
}); | ||
|
||
test("isType.notNull with union type", () => { | ||
const movie = new Cypher.Node({ labels: ["Movie"] }); | ||
const matchClause = new Cypher.Match(movie) | ||
.where(Cypher.isType(movie.property("title"), Cypher.TYPE.STRING, Cypher.TYPE.BOOLEAN).notNull()) | ||
.return(movie); | ||
|
||
const { cypher } = matchClause.build(); | ||
|
||
expect(cypher).toMatchInlineSnapshot(` | ||
"MATCH (this0:Movie) | ||
WHERE this0.title IS :: STRING NOT NULL | BOOLEAN NOT NULL | ||
RETURN this0" | ||
`); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/* | ||
* 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 type Cypher from ".."; | ||
import { CypherASTNode } from "../CypherASTNode"; | ||
import type { ValueOf } from "../utils/type-helpers"; | ||
|
||
const BaseTypes = { | ||
ANY: "ANY", | ||
BOOLEAN: "BOOLEAN", | ||
DATE: "DATE", | ||
DURATION: "DURATION", | ||
FLOAT: "FLOAT", | ||
INTEGER: "INTEGER", | ||
LOCAL_DATETIME: "LOCAL DATETIME", | ||
LOCAL_TIME: "LOCAL_TIME", | ||
MAP: "MAP", | ||
NODE: "NODE", | ||
NOTHING: "NOTHING", | ||
NULL: "NULL", | ||
PATH: "PATH", | ||
POINT: "POINT", | ||
PROPERTY_VALUE: "PROPERTY VALUE", | ||
RELATIONSHIP: "RELATIONSHIP", | ||
STRING: "STRING", | ||
ZONED_DATETIME: "ZONED DATETIME", | ||
ZONED_TIME: "ZONED TIME", | ||
} as const; | ||
|
||
/** | ||
* Generates a cypher LIST<...> type | ||
* @example | ||
* ```cypher | ||
* LIST<STRING> | ||
* ``` | ||
*/ | ||
function list(type: Type): ListType { | ||
return new ListType(type); | ||
} | ||
|
||
/** | ||
* Types supported by Neo4j | ||
* @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual/current/values-and-types/property-structural-constructed/#types-synonyms) | ||
*/ | ||
export const CypherTypes = { | ||
...BaseTypes, | ||
list, | ||
} as const; | ||
|
||
/** | ||
* Type predicate expression | ||
* @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual/current/values-and-types/type-predicate/) | ||
* @example | ||
* ```cypher | ||
* val IS :: INTEGER | ||
* ``` | ||
*/ | ||
export function isType(expr: Cypher.Expr, ...type: Type[]): IsType { | ||
return new IsType(expr, type); | ||
} | ||
|
||
/** | ||
* Type predicate expression with NOT | ||
* @see [Cypher Documentation](https://neo4j.com/docs/cypher-manual/current/values-and-types/type-predicate/#type-predicate-not) | ||
* @example | ||
* ```cypher | ||
* val IS NOT :: INTEGER | ||
* ``` | ||
*/ | ||
export function isNotType(expr: Cypher.Expr, ...type: Type[]): IsType { | ||
return new IsType(expr, type, true); | ||
} | ||
|
||
class ListType { | ||
private type: Type; | ||
|
||
constructor(type: Type) { | ||
this.type = type; | ||
} | ||
|
||
public getCypher(env: Cypher.Environment): string { | ||
const typeStr = compileType(this.type, env); | ||
|
||
return `LIST<${typeStr}>`; | ||
} | ||
} | ||
|
||
export class IsType extends CypherASTNode { | ||
private expr: Cypher.Expr; | ||
private type: Type[]; | ||
private not: boolean; | ||
private _notNull: boolean = false; | ||
|
||
public constructor(expr: Cypher.Expr, type: Type[], not = false) { | ||
super(); | ||
this.expr = expr; | ||
this.type = type; | ||
this.not = not; | ||
} | ||
|
||
public notNull(): this { | ||
this._notNull = true; | ||
return this; | ||
} | ||
|
||
public getCypher(env: Cypher.Environment): string { | ||
const exprCypher = env.compile(this.expr); | ||
const isStr = this.not ? "IS NOT" : "IS"; | ||
|
||
// Note that all types must be nullable or non nullable | ||
const notNullStr = this._notNull ? " NOT NULL" : ""; | ||
const typesStr = this.type.map((type) => { | ||
const typeStr = compileType(type, env); | ||
|
||
return `${typeStr}${notNullStr}`; | ||
}); | ||
|
||
return `${exprCypher} ${isStr} :: ${Array.from(typesStr).join(" | ")}`; | ||
} | ||
} | ||
|
||
type Type = ValueOf<typeof BaseTypes> | ListType; | ||
|
||
function compileType(type: Type, env: Cypher.Environment): string { | ||
if (type instanceof ListType) { | ||
return env.compile(type); | ||
} else { | ||
return type; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.