Skip to content

Expected parameter(s) #2498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zhageLiu opened this issue Mar 14, 2022 · 10 comments
Closed

Expected parameter(s) #2498

zhageLiu opened this issue Mar 14, 2022 · 10 comments
Assignees
Labels
type: bug A general bug

Comments

@zhageLiu
Copy link

this is my code:

Node node = Cypher.node("Node").named("node");
Property name= node.property("name");
Parameter<List<String>> parameters = Cypher.anonParameter( nameList );
Condition in = property.in(parameters );
Collection<Node> all = nodeRepository.findAll(in);

i try to run these, then throw the ClientException:Expected parameter(s)

and i debug these

package org.springframework.data.neo4j.repository.query;
public Collection<T> findAll(Condition condition) {
        return this.neo4jOperations.toExecutableQuery(this.metaData.getType(), QueryFragmentsAndParameters.forCondition(this.metaData, condition, (Pageable)null, (Collection)null)).getResults();
    }
package org.springframework.data.neo4j.core;
public List<T> getResults() {
    Collection<T> all = (Collection)this.createFetchSpec().map(RecordFetchSpec::all).orElse(Collections.emptyList());
    return this.preparedQuery.resultsHaveBeenAggregated() ? (List)all.stream().flatMap((nested) -> {
        return ((Collection)nested).stream();
    }).distinct().collect(Collectors.toList()) : (List)all.stream().collect(Collectors.toList());
}
private Optional<RecordFetchSpec<T>> createFetchSpec() {
******
    if (cypherQuery == null || containsPossibleCircles) {
        if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
            NodesAndRelationshipsByIdStatementProvider nodesAndRelationshipsById = this.createNodesAndRelationshipsByIdStatementProvider(entityMetaData, queryFragments, queryFragmentsAndParameters.getParameters());
            if (nodesAndRelationshipsById.hasRootNodeIds()) {
                return Optional.empty();
            }
            cypherQuery = Neo4jTemplate.renderer.render(nodesAndRelationshipsById.toStatement(entityMetaData));
            finalParameters = nodesAndRelationshipsById.getParameters();
        } else {
           ******
        }
    }
******
}
private NodesAndRelationshipsByIdStatementProvider createNodesAndRelationshipsByIdStatementProvider(Neo4jPersistentEntity<?> entityMetaData, QueryFragments queryFragments, Map<String, Object> parameters) {
    Statement rootNodesStatement = Neo4jTemplate.this.cypherGenerator.prepareMatchOf(entityMetaData, queryFragments.getMatchOn(), queryFragments.getCondition()).returning(new String[]{"__sn__"}).build();
    Map<String, Object> usedParameters = new HashMap(parameters);
    usedParameters.putAll(rootNodesStatement.getParameters());
******(①)******    
Collection<Long> rootNodeIds = new HashSet((Collection)((RunnableSpec)Neo4jTemplate.this.neo4jClient.query(Neo4jTemplate.renderer.render(rootNodesStatement)).bindAll(usedParameters)).fetchAs(Value.class).mappedBy((t, r) -> {
        return r.get("__sn__");
    }).one().map((value) -> {
        return value.asList(Value::asLong);
    }).get());
    if (rootNodeIds.isEmpty()) {
        return NodesAndRelationshipsByIdStatementProvider.EMPTY;
    } else {
        Set<Long> relationshipIds = new HashSet();
        Set<Long> relatedNodeIds = new HashSet();
        queryFragments.getClass();
        Iterator var9 = entityMetaData.getRelationshipsInHierarchy(queryFragments::includeField).iterator();

        while(var9.hasNext()) {
            RelationshipDescription relationshipDescription = (RelationshipDescription)var9.next();
            Statement statement = Neo4jTemplate.this.cypherGenerator.prepareMatchOf(entityMetaData, relationshipDescription, queryFragments.getMatchOn(), queryFragments.getCondition()).returning(Neo4jTemplate.this.cypherGenerator.createReturnStatementForMatch(entityMetaData)).build();
            usedParameters = new HashMap(parameters);
            usedParameters.putAll(statement.getParameters());
            ******②******
((RunnableSpec)Neo4jTemplate.this.neo4jClient.query(Neo4jTemplate.renderer.render(statement)).bindAll(parameters)).fetch().one().ifPresent(this.iterateAndMapNextLevel(relationshipIds, relatedNodeIds, relationshipDescription, PropertyPathWalkStep.empty()));
        }

        return new NodesAndRelationshipsByIdStatementProvider(rootNodeIds, relationshipIds, relatedNodeIds, queryFragments);
    }
}

when i run to code ①, the method of bindAll have params with name "usedParameters", and my cql is true ,the field with name "runnableStatement" , have field "parameters" ,and this filde value is my cql Should have parameters, is true.

but when i run to code ②, bindAll() have params with name "parameters", but this param is empty, and the field of "runnableStatement" - "parameters" is empty, so my cql is wrong, finally throw the Exception: Expected parameter(s)

my English is not good, i hope you see what I mean, and I don't understand what's wrong with me, please help me
thank you!

@michael-simons michael-simons self-assigned this Mar 14, 2022
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 14, 2022
michael-simons added a commit that referenced this issue Mar 14, 2022
michael-simons added a commit that referenced this issue Mar 14, 2022
@michael-simons
Copy link
Collaborator

Hi @zhageLiu your english is great, all good, I can make sense of it.

Here's what I think is happening: Judging from Collection<Node> all = nodeRepository.findAll(in); your repository seems to be of the following type:

public interface NodeRepository extends Neo4jRepository<Node, ID>, CypherdslConditionExecutor<Node> {
}

This is wrong and does not work!

The type org.neo4j.cypherdsl.core.Node is from https://github.com/neo4j-contrib/cypher-dsl and an entrypoint to the Cypher-DSL. Repositories however contain domain objects. Domain objects are classes annotated with @org.springframework.data.neo4j.core.schema.Node (Note the difference in package!).

The former is a class, the latter a Java annotation.

A Spring Data repository contains only domain objects.

So given

import java.util.UUID;

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;

@Node
public class DomainModel {

	@Id @GeneratedValue UUID id;

	private final String name;

	public DomainModel(String name) {
		this.name = name;
	}

	public UUID getId() {
		return id;
	}

	public String getName() {
		return name;
	}
}

as domain object (it will have the label DomainModel, but that can be changed with the @Node annotation) and

import java.util.UUID;

import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor;

public interface DomainModelRepository extends Neo4jRepository<Node, UUID>, CypherdslConditionExecutor<DomainModel> {
}

as the repository this code (which basically is your test code) works as expected:

Node node = Cypher.node("DomainModel").named("domainModel");
Property name = node.property("name");
Parameter<List<String>> parameters = Cypher.anonParameter(Arrays.asList("A", "C"));
Condition in = name.in(parameters);
Collection<DomainModel> result = repository.findAll(in, Cypher.sort(name).descending());
Assertions.assertThat(result).hasSize(2)
		.map(DomainModel::getName)
		.containsExactly("C", "A");

Please see this commit 6692b8e for a full example and let me know if this helps.

Please spend some time with our documentation, https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#reference too.

@michael-simons michael-simons added blocked: awaiting feedback and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 14, 2022
@zhageLiu
Copy link
Author

Thanks for your reply.
I apologize for not uploading the code completely, but i think my error is not this.
I re-use the same code test as I did with the error logic, they still throw the same error:Expected parameter(s)

Here is my new test code

domain:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Node("Person")
@Builder(toBuilder = true)
public class Person {
    @Id
    @GeneratedValue
    Long id;
    String name;
    @Relationship(type = "KNOW", direction = Relationship.Direction.INCOMING)
    List<Know> knows;
}

relationship:

@RelationshipProperties
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Know {
    @Id
    @GeneratedValue
    Long id;
    @TargetNode
    Person person;
}

repository:

@Repository
public interface PersonRepository extends Neo4jRepository<Person, Long>, CypherdslConditionExecutor<Person> {

}

testcode:

ArrayList<Know> knows = new ArrayList<>();
knows.add(Know.builder().person(Person.builder().name("b").build()).build());
Person person = Person.builder().name("a").knows(knows).build();
personRepository.save(person);
Node personNode = Cypher.node("Person").named("person");
Property name = personNode.property("name");
Parameter<List<String>> param = Cypher.anonParameter(Arrays.asList("a", "b"));
Condition in = name.in(param);
personRepository.findAll(in);

they still throw the same error: Expected parameter(s): pcdsl01, i guess the error is run to the while(var9.next) in method name "createNodesAndRelationshipsByIdStatementProvider", this loop is find the node's relationship, then used method of bindAll(),
and this method use the param name "parameters", but this param is empty, and this code have a variable name "usedParameters", the "usedParameters" changes up here and the value is my param but not use.

then the cql is generate:

MATCH (person:Person) WHERE person.name IN $pcdsl01 OPTIONAL MATCH (person)<-[__sr__:KNOW]-(__srn__:Person) WITH collect(id(person)) AS __sn__, collect(id(__srn__)) AS __srn__, collect(id(__sr__)) AS __sr__ RETURN __sn__, __srn__, __sr__

when this cql is parsing, the placeholder name "$pcdsl01" is not in the param "parameters", so when run query with this cql, this cql didn't parsing to the ture params like ["a", "b"], but still is pcdsl01, so throw this error.

i'm a beginner for java and neo4j, if I wasn't clear or if I was wrong, please forgive me, thanks

@zhageLiu zhageLiu reopened this Mar 15, 2022
@zhageLiu
Copy link
Author

and my pom is

org.springframework.boot
spring-boot-starter-data-neo4j
2.6.4

@zhageLiu
Copy link
Author

I tested other methods of findAll, like eq, gt, lt... They all throw the same exception.
code:

Property name = personNode.property("name");
Parameter<String> param = Cypher.anonParameter("a");
Condition eq = name.eq(param);
personRepository.findAll(eq);
Property name = personNode.property("name");
Parameter<String> param = Cypher.anonParameter("a");
Condition gt = name.gt(param);
personRepository.findAll(gt);
Property name = personNode.property("name");
Parameter<List<String>> param = Cypher.anonParameter(Arrays.asList("a", "b"));
Condition in = name.in(param);
personRepository.findAll(in);

What they all have in common is this:

  1. Node have relations
  2. run to method named "bindAll", The generated CQL have a placeholder named "$pcdsl01", This placeholder will be taken value as a key from the parameter named "parameters" and param's class is Map. But this param is empty, so when the cql run, it will be throw a Nested exception :Suppressed: org.neo4j.driver.internal.util.ErrorUtil$InternalExceptionCause: null ,then throw this exception: Expected parameter(s)

@zhageLiu
Copy link
Author

hello @michael-simons, did you see my reply? this error confused me, please read and help me

@michael-simons
Copy link
Collaborator

Hi @zhageLiu I can reproduce this with your domain and we are investigating.

@zhageLiu
Copy link
Author

hi @michael-simons thanks for your reply, i hope my analysis can be of some help to your team

@michael-simons
Copy link
Collaborator

That was faster than expected now to debunk.

First of all, big thank you to you for being persistent on this issue, much appreciated.

This will be fixed in the next patches, but here's already an explanation: The CypherdslConditionExecutor is a mixin to the repository, as you have seen in your debugging, it contributes parameters to queries.

Now, with your domain model, we do issue several queries to make sure we don't create a super big one running in circles (your domain has a "self referential" relationship). We must add the parameters to each of the statements.

Well:

usedParameters = new HashMap<>(parameters);
usedParameters.putAll(statement.getParameters());
neo4jClient.query(renderer.render(statement))
.bindAll(parameters)

I wanted to do this (see usedParameters vs parameters): I prepped everything and than used the original ones. Bummer.
But now we have a test for it.

@zhageLiu
Copy link
Author

thank you and your team, and can you tell me this message when the patches release? thanks again

@michael-simons
Copy link
Collaborator

Please check https://calendar.spring.io for the planned dates. This is out of our hands, the Spring Data Team at VMWare lead by @mp911de decides and runs the release. I'm backporting this back to 6.1.x.

@michael-simons michael-simons added this to the 6.1.10 (2021.0.10) milestone Mar 17, 2022
michael-simons added a commit that referenced this issue Mar 17, 2022
michael-simons added a commit that referenced this issue Mar 17, 2022
@michael-simons michael-simons mentioned this issue Jan 4, 2023
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants