Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,17 @@ private boolean matchesProperties(final Vertex vertex, final Result currentResul
expectedValue = evaluator.evaluate((Expression) expectedValue, currentResult, context);
}

// Resolve parameter references (e.g., $id -> actual value from context)
// Resolve ParameterReference objects (e.g., {user_name: $username} -> actual value from context)
if (expectedValue instanceof CypherASTBuilder.ParameterReference) {
final String paramName = ((CypherASTBuilder.ParameterReference) expectedValue).getName();
if (context.getInputParameters() != null) {
final Object paramValue = context.getInputParameters().get(paramName);
if (paramValue != null)
expectedValue = paramValue;
Comment on lines +517 to +518
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If paramValue is null (meaning the parameter was not found or explicitly set to null), expectedValue will remain a CypherASTBuilder.ParameterReference object. This can lead to incorrect comparisons later in the matchesProperties method, as a ParameterReference object will be compared directly against a vertex property value, or potentially cause a ClassCastException if subsequent logic expects a String or null. The expectedValue should be set to null if the parameter resolves to null.

Suggested change
if (paramValue != null)
expectedValue = paramValue;
expectedValue = paramValue;

}
}

// Resolve parameter references stored as strings (legacy "$paramName" format)
if (expectedValue instanceof String) {
final String strValue = (String) expectedValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.arcadedb.query.opencypher.Labels;
import com.arcadedb.query.opencypher.ast.Expression;
import com.arcadedb.query.opencypher.ast.MergeClause;
import com.arcadedb.query.opencypher.parser.CypherASTBuilder;
import com.arcadedb.query.opencypher.ast.NodePattern;
import com.arcadedb.query.opencypher.ast.PathPattern;
import com.arcadedb.query.opencypher.ast.RelationshipPattern;
Expand Down Expand Up @@ -740,6 +741,14 @@ private Map<String, Object> evaluateProperties(final Map<String, Object> propert
if (value instanceof Expression) {
value = evaluator.evaluate((Expression) value, result, context);
}
// Resolve ParameterReference objects (e.g., {user_name: $username} -> actual value from context)
else if (value instanceof CypherASTBuilder.ParameterReference paramRef) {
if (context.getInputParameters() != null) {
final Object paramValue = context.getInputParameters().get(paramRef.getName());
if (paramValue != null)
value = paramValue;
}
}
// Legacy support: If the value looks like a property access (e.g., "BatchEntry.subtype"),
// try to evaluate it against the current result context
else if (value instanceof String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

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

Expand Down Expand Up @@ -269,6 +270,61 @@ void mergeRelationshipWithBackticksInTypeName() {
assertThat(count).isEqualTo(1L);
}

/**
* Regression test: MERGE with a parameter reference in properties should find an existing node.
*/
@Test
void mergeFindsNodeWithParameterReference() {
database.getSchema().getOrCreateVertexType("USER_RIGHTS");

// Create the node with a literal property value
database.transaction(() -> {
database.command("opencypher", "CREATE (n:USER_RIGHTS {user_name: \"random_username_123\"}) RETURN n");
});

// MERGE using a parameter reference - should find the existing node, not create a duplicate
database.transaction(() -> {
final ResultSet result = database.command("opencypher",
"MERGE (n:USER_RIGHTS {user_name: $username}) RETURN n",
Map.of("username", "random_username_123"));
assertThat(result.hasNext()).isTrue();
final Vertex v = (Vertex) result.next().toElement();
assertThat((String) v.get("user_name")).isEqualTo("random_username_123");
});

// Verify only one node was created (MERGE did not duplicate)
final ResultSet verify = database.query("opencypher", "MATCH (n:USER_RIGHTS) RETURN n");
int count = 0;
while (verify.hasNext()) {
verify.next();
count++;
}
assertThat(count).isEqualTo(1);
}

/**
* Regression test: MERGE with a parameter reference should create a node when none exists.
*/
@Test
void mergeCreatesNodeWithParameterReference() {
database.getSchema().getOrCreateVertexType("USER_RIGHTS");

// MERGE using a parameter reference - node does not exist yet, should be created
database.transaction(() -> {
final ResultSet result = database.command("opencypher",
"MERGE (n:USER_RIGHTS {user_name: $username}) RETURN n",
Map.of("username", "new_user_456"));
assertThat(result.hasNext()).isTrue();
final Vertex v = (Vertex) result.next().toElement();
assertThat((String) v.get("user_name")).isEqualTo("new_user_456");
});

// Verify exactly one node was created
final ResultSet verify = database.query("opencypher",
"MATCH (n:USER_RIGHTS {user_name: \"new_user_456\"}) RETURN n");
assertThat(verify.hasNext()).isTrue();
}

/**
* Test for issue #3217: Backticks in node labels should also be treated as escape characters.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,38 @@ void manyParametersInIdQuery() {
}
}

/**
* Regression test for: MATCH (n:LABEL {prop: $param}) returns nothing when using inline
* node-pattern property filters with parameters, while the equivalent literal query works.
* The parser stores the parameter as a ParameterReference object in the properties map,
* and MatchNodeStep.matchesProperties() was not resolving it before comparing.
*/
@Test
void inlineNodePatternParameterMatching() {
final Database database = new DatabaseFactory("./target/testparams4").create();
try {
database.getSchema().getOrCreateVertexType("USER_RIGHTS");

database.transaction(() ->
database.command("opencypher", "CREATE (n:USER_RIGHTS {user_name: \"random_username_123\"}) RETURN n"));

// Inline property filter with a string parameter should return the node
final ResultSet result = database.query("opencypher",
"MATCH (n:USER_RIGHTS {user_name: $username}) RETURN n",
Map.of("username", "random_username_123"));

int count = 0;
while (result.hasNext()) {
result.next();
count++;
}
assertThat(count).isEqualTo(1);
} finally {
database.drop();
FileUtils.deleteRecursively(new File("./target/testparams4"));
}
}

@BeforeEach
@AfterEach
void clean() {
Expand Down
Loading