Skip to content

Commit

Permalink
feat: Add hasLabels(LabelExpression labels) for nodes. (#1146)
Browse files Browse the repository at this point in the history
Closes #1141.
  • Loading branch information
michael-simons authored Dec 9, 2024
1 parent 117adf2 commit a1e7d01
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
:artifactId: neo4j-cypher-dsl

// This will be next version and also the one that will be put into the manual for the main branch
:neo4j-cypher-dsl-version: 2024.2.1-SNAPSHOT
:neo4j-cypher-dsl-version: 2024.3.0-SNAPSHOT
// This is the latest released version, used only in the readme
:neo4j-cypher-dsl-version-latest: 2024.2.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public final Condition hasLabels(String... labelsToQuery) {
labelsToQuery);
}

@NotNull
@Override
public final Condition hasLabels(LabelExpression labels) {
return new HasLabelExpressionCondition(this.getSymbolicName()
.orElseThrow(() -> new IllegalStateException("Cannot query a node without a symbolic name.")),
labels);
}

@NotNull
@Override
public final Condition isEqualTo(Node otherNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2019-2024 "Neo4j,"
* Neo4j Sweden AB [https://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
*
* https://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.
*/
package org.neo4j.cypherdsl.core;

import static org.apiguardian.api.API.Status.INTERNAL;

import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.ast.Visitor;

/**
* A condition checking for the presence of label expressions on nodes.
*
* @author Michael J. Simons
* @since 2024.3.0
*/
@API(status = INTERNAL, since = "2024.3.0")
record HasLabelExpressionCondition(SymbolicName nodeName, LabelExpression labelExpression) implements Condition {

@Override
public void accept(Visitor visitor) {

visitor.enter(this);
this.nodeName.accept(visitor);
this.labelExpression.accept(visitor);
visitor.leave(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,18 @@ public interface Node extends PatternElement, PropertyContainer, ExposesProperti
* A condition that checks for the presence of labels on a node.
*
* @param labelsToQuery A list of labels to query
* @return A condition that checks whether this node has all of the labels to query
* @return A condition that checks whether this node has all the labels to query
*/
@NotNull @Contract(pure = true)
Condition hasLabels(String... labelsToQuery);

/**
* A condition that checks for the presence of a label expression on a node
* @since 2024.3.0
*/
@NotNull @Contract(pure = true)
Condition hasLabels(LabelExpression labels);

/**
* Creates a new condition whether this node is equal to {@literal otherNode}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

class LabelExpressionTest {

static final LabelExpression SIMPLE_OR = new LabelExpression("Person").or(new LabelExpression("Organization"));
static final LabelExpression OR_AND_NOT = SIMPLE_OR.and(new LabelExpression("Sanctioned").negate());

@Test // GH-1077
void labelExpressionsShouldWork1() {

var node = Cypher.node(new LabelExpression("Person").or(new LabelExpression("Organization"))).named("n");
var node = Cypher.node(SIMPLE_OR).named("n");
var cypher = Cypher.match(node).returning(node).build().getCypher();

assertThat(cypher).isEqualTo("MATCH (n:`Person`|`Organization`) RETURN n");
Expand All @@ -36,10 +40,93 @@ void labelExpressionsShouldWork1() {
@Test // GH-1077
void labelExpressionsShouldWork2() {

var node = Cypher.node(new LabelExpression("Person").or(new LabelExpression("Organization"))
.and(new LabelExpression("Sanctioned").negate())).named("n");
var node = Cypher.node(OR_AND_NOT).named("n");
var cypher = Cypher.match(node).returning(node).build().getCypher();

assertThat(cypher).isEqualTo("MATCH (n:(`Person`|`Organization`)&!`Sanctioned`) RETURN n");
}

@Nested
class AsConditions {

@Test // GH-1077
void labelExpressionsShouldWork1() {

var node = Cypher.anyNode("n");
var cypher = Cypher.match(node).where(node.hasLabels(SIMPLE_OR)).returning(node).build().getCypher();

assertThat(cypher).isEqualTo("MATCH (n) WHERE n:`Person`|`Organization` RETURN n");
}

@Test // GH-1077
void labelExpressionsShouldWork2() {

var node = Cypher.anyNode("n");
var cypher = Cypher.match(node).where(node.hasLabels(OR_AND_NOT)).returning(node).build().getCypher();

assertThat(cypher).isEqualTo("MATCH (n) WHERE n:(`Person`|`Organization`)&!`Sanctioned` RETURN n");
}

@Test // GH-1141
void labelExpressionsInPredicates() {

var movieOrFilm = new LabelExpression("Movie").or(new LabelExpression("Film"));

String statement;
Node a = Cypher.node("Person").withProperties("name", Cypher.literalOf("Keanu Reeves")).named("a");
Node b = Cypher.anyNode("b");

statement = Cypher.match(a)
.returning(
Cypher.listBasedOn(a.relationshipBetween(b))
.where(b.hasLabels(movieOrFilm).and(b.property("released").isNotNull()))
.returning(b.property("released"))
.as("years"))
.build().getCypher();
assertThat(statement)
.isEqualTo(
"MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE (b:`Movie`|`Film` AND b.released IS NOT NULL) | b.released] AS years");

statement = Cypher.match(a)
.returning(
Cypher.listBasedOn(a.relationshipBetween(b))
.where(
b.hasLabels(movieOrFilm)
.and(b.property("released").isNotNull())
.or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix")))
.or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix 2"))))
.returning(b.property("released"))
.as("years"))
.build().getCypher();
assertThat(statement)
.isEqualTo(
"MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE ((b:`Movie`|`Film` AND b.released IS NOT NULL) OR b.title = 'The Matrix' OR b.title = 'The Matrix 2') | b.released] AS years");

statement = Cypher.match(a)
.returning(
Cypher.listBasedOn(a.relationshipBetween(b))
.where(b.hasLabels(movieOrFilm))
.and(b.property("released").isNotNull())
.or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix")))
.or(b.property("title").isEqualTo(Cypher.literalOf("The Matrix 2")))
.returning(b.property("released"))
.as("years"))
.build().getCypher();

assertThat(statement)
.isEqualTo(
"MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE ((b:`Movie`|`Film` AND b.released IS NOT NULL) OR b.title = 'The Matrix' OR b.title = 'The Matrix 2') | b.released] AS years");

statement = Cypher.match(a)
.returning(
Cypher.listBasedOn(a.relationshipBetween(b))
.where(b.hasLabels(movieOrFilm))
.returning(b.property("released"))
.as("years"))
.build().getCypher();
assertThat(statement)
.isEqualTo(
"MATCH (a:`Person` {name: 'Keanu Reeves'}) RETURN [(a)--(b) WHERE b:`Movie`|`Film` | b.released] AS years");
}
}
}

0 comments on commit a1e7d01

Please sign in to comment.