Skip to content

Commit

Permalink
feat: Allow calling of raw Cypher strings in sub queries. (#961)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-simons committed Apr 9, 2024
1 parent b4b1ef9 commit 3b59c67
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.adoc
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: 2023.9.6-SNAPSHOT
:neo4j-cypher-dsl-version: 2023.10.0-SNAPSHOT
// This is the latest released version, used only in the readme
:neo4j-cypher-dsl-version-latest: 2023.9.5

Expand Down
Expand Up @@ -456,7 +456,7 @@ protected final void addUpdatingClause(UpdatingClause updatingClause) {

@NotNull
@Override
public StatementBuilder.OngoingReadingWithoutWhere call(Statement statement, IdentifiableElement... imports) {
public BuildableSubquery call(Statement statement, IdentifiableElement... imports) {

this.closeCurrentOngoingMatch();
this.closeCurrentOngoingUpdate();
Expand All @@ -466,6 +466,18 @@ public StatementBuilder.OngoingReadingWithoutWhere call(Statement statement, Ide
return this;
}

@NotNull
@Override
public BuildableSubquery callRawCypher(String rawCypher, Object... args) {

this.closeCurrentOngoingMatch();
this.closeCurrentOngoingUpdate();

this.currentSinglePartElements.add(Subquery.raw(rawCypher, args));

return this;
}

@NotNull
@Override
public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) {
Expand Down Expand Up @@ -951,13 +963,21 @@ public OngoingUnwind unwind(Expression expression) {

@NotNull
@Override
public OngoingReadingWithoutWhere call(Statement statement, IdentifiableElement... imports) {
public BuildableSubquery call(Statement statement, IdentifiableElement... imports) {

return DefaultStatementBuilder.this
.addWith(buildWith())
.call(statement, imports);
}

@Override
public @NotNull BuildableSubquery callRawCypher(String rawCypher, Object... args) {

return DefaultStatementBuilder.this
.addWith(buildWith())
.callRawCypher(rawCypher, args);
}

@NotNull
@Override
public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) {
Expand Down Expand Up @@ -1663,10 +1683,15 @@ public StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere withDistinct(

@NotNull
@Override
public StatementBuilder.OngoingReadingWithoutWhere call(Statement statement, IdentifiableElement... imports) {
public BuildableSubquery call(Statement statement, IdentifiableElement... imports) {
return new DefaultStatementBuilder(this.buildCall()).call(statement, imports);
}

@Override
public @NotNull BuildableSubquery callRawCypher(String rawCypher, Object... args) {
return new DefaultStatementBuilder(this.buildCall()).callRawCypher(rawCypher, args);
}

@NotNull
@Override
public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) {
Expand Down Expand Up @@ -1784,12 +1809,19 @@ public OrderableOngoingReadingAndWithWithoutWhere withDistinct(Collection<Identi

@NotNull
@Override
public StatementBuilder.OngoingReadingWithoutWhere call(Statement statement, IdentifiableElement... imports) {
public BuildableSubquery call(Statement statement, IdentifiableElement... imports) {

DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall());
return DefaultStatementBuilder.this.call(statement, imports);
}

@Override
public @NotNull BuildableSubquery callRawCypher(String rawCypher, Object... args) {

DefaultStatementBuilder.this.currentSinglePartElements.add(this.buildCall());
return DefaultStatementBuilder.this.callRawCypher(rawCypher, args);
}

@NotNull
@Override
public BuildableSubquery callInTransactions(Statement statement, Integer rows, IdentifiableElement... imports) {
Expand Down
Expand Up @@ -63,7 +63,7 @@ interface BuildableSubquery extends OngoingReadingWithoutWhere, BuildableStateme
* @return An ongoing reading
*/
@NotNull @CheckReturnValue
default StatementBuilder.OngoingReadingWithoutWhere call(Statement statement) {
default BuildableSubquery call(Statement statement) {
return call(statement, new IdentifiableElement[0]);
}

Expand All @@ -82,7 +82,7 @@ default StatementBuilder.OngoingReadingWithoutWhere call(Statement statement) {
* @since 2021.3.0
*/
@NotNull @CheckReturnValue
default StatementBuilder.OngoingReadingWithoutWhere call(Statement statement, String... imports) {
default BuildableSubquery call(Statement statement, String... imports) {
return call(statement, Arrays.stream(imports).map(SymbolicName::of).toArray(SymbolicName[]::new));
}

Expand All @@ -103,7 +103,7 @@ default StatementBuilder.OngoingReadingWithoutWhere call(Statement statement, St
* @since 2021.3.0
*/
@NotNull @CheckReturnValue
StatementBuilder.OngoingReadingWithoutWhere call(Statement statement, IdentifiableElement... imports);
BuildableSubquery call(Statement statement, IdentifiableElement... imports);

/**
* Starts building a new sub-query from a {@code CALL ... IN TRANSACTIONS} clause
Expand All @@ -115,6 +115,17 @@ default BuildableSubquery callInTransactions(Statement statement) {
return callInTransactions(statement, null, new IdentifiableElement[0]);
}

/**
* Starts building a new sub-query from a raw Cypher string that might also have arguments as supported through {@link Cypher#raw(String, Object...)}.
* Use this method as your own risk and be aware that no checks are done on the Cypher.
* @param rawCypher the raw Cypher statement to call
* @param args optional args that replace placeholders in the {@code rawCypher}
* @return Ongoing sub-query definition
* @since 2023.10.0
*/
@NotNull @CheckReturnValue
BuildableSubquery callRawCypher(String rawCypher, Object... args);

/**
* Creates a subquery running in its own transactions. The statement won't be checked for a {@literal RETURN} or
* {@literal YIELD} clause to accommodate for {@literal CREATE} or other clauses that are void and work in a subquery
Expand Down
Expand Up @@ -37,8 +37,13 @@
@Neo4jVersion(minimum = "4.0.0")
public final class Subquery extends AbstractClause implements Clause {

private ImportingWith importingWith;
private final ImportingWith importingWith;
private final Statement statement;
private final RawLiteral rawStatement;

static Subquery raw(String format, Object... mixedArgs) {
return new Subquery(RawLiteral.create(format, mixedArgs));
}

/**
* The {@code statement} can either be a unit sub-query, used to modify the graph. Those won't impact the amount of
Expand All @@ -56,14 +61,25 @@ static Subquery call(Statement statement, IdentifiableElement... imports) {
private Subquery(ImportingWith importingWith, Statement statement) {
this.importingWith = importingWith;
this.statement = statement;
this.rawStatement = null;
}

private Subquery(RawLiteral rawStatement) {
this.rawStatement = rawStatement;
this.importingWith = null;
this.statement = null;
}

@Override
public void accept(Visitor visitor) {

visitor.enter(this);
this.importingWith.accept(visitor);
statement.accept(visitor);
if (this.rawStatement != null) {
this.rawStatement.accept(visitor);
} else {
this.importingWith.accept(visitor);
this.statement.accept(visitor);
}
visitor.leave(this);
}

Expand All @@ -77,6 +93,6 @@ InTransactions inTransactionsOf(Integer rows) {
*/
@API(status = INTERNAL)
public boolean doesReturnOrYield() {
return statement.doesReturnOrYield();
return statement != null && statement.doesReturnOrYield();
}
}
Expand Up @@ -704,6 +704,25 @@ void returningRawShouldWork() {
}""");
}

@Test
void veryRawCallShouldWork() {

SymbolicName msg = Cypher.name("message");
String statement = Cypher.unwind(Cypher.parameter("events")).as(msg)
.with(
msg.property("value").as("event"),
msg.property("header").as("header"),
msg.property("key").as("key"),
msg.property("value").as("value")
)
.callRawCypher("WITH key, value CREATE (e:Event {key: key, value: value})")
.build()
.getCypher();

assertThat(statement)
.isEqualTo("UNWIND $events AS message WITH message.value AS event, message.header AS header, message.key AS key, message.value AS value CALL {WITH key, value CREATE (e:Event {key: key, value: value})}");
}

@Test // GH-190
void mixedWithShouldMakeSense() {

Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Expand Up @@ -627,6 +627,10 @@
<!-- 2023.5.0 and higher, move up an interface, not to be implemented, has not been removed -->
<exclude>org.neo4j.cypherdsl.core.StatementBuilder$ExposesSetAndRemove#set(org.neo4j.cypherdsl.core.Node,java.util.Collection)</exclude>
<exclude>org.neo4j.cypherdsl.core.StatementBuilder$ExposesSetAndRemove#set(org.neo4j.cypherdsl.core.Node,java.lang.String[])</exclude>
<!-- 2023.10.0 and higher, changed to a broader return type, only we implement that interface -->
<exclude>org.neo4j.cypherdsl.core.ExposesSubqueryCall#call(org.neo4j.cypherdsl.core.Statement)</exclude>
<exclude>org.neo4j.cypherdsl.core.ExposesSubqueryCall#call(org.neo4j.cypherdsl.core.Statement,java.lang.String[])</exclude>
<exclude>org.neo4j.cypherdsl.core.ExposesSubqueryCall#call(org.neo4j.cypherdsl.core.Statement,org.neo4j.cypherdsl.core.IdentifiableElement[])</exclude>
</excludes>
</parameter>
</configuration>
Expand Down

0 comments on commit 3b59c67

Please sign in to comment.