-
Notifications
You must be signed in to change notification settings - Fork 0
Implement @Query and @TargetNode annotations for Spring Data FalkorDB #6
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
Conversation
- Changed distributionManagement to use central.sonatype.com - Added GPG signing plugin for artifact signing - Added central-publishing-maven-plugin for Maven Central publishing - Updated publish workflow to use CENTRAL_USERNAME/CENTRAL_TOKEN - Updated release workflow with GPG key import and Maven Central deployment - Removed old Spring Artifactory settings.xml This matches the publishing configuration used by the JFalkorDB project for Maven Central publishing.
Add comprehensive support for custom queries and relationship properties mapping: ## New Annotations: ### @query Annotation - Location: org.springframework.data.falkordb.repository.query.Query - Supports custom Cypher queries in repository methods - Multiple parameter binding options: @param, indexed (-zsh, ), entity properties - Special query types: count=true, exists=true, write=true - Compatible with Spring Data Neo4j @query patterns ### @TargetNode Annotation - Location: org.springframework.data.falkordb.core.schema.TargetNode - Marks target node field in @RelationshipProperties classes - Enables proper relationship entity mapping with properties - Works with @RelationshipId for complete relationship support ### @RelationshipId Annotation - Location: org.springframework.data.falkordb.core.schema.RelationshipId - Marks relationship internal ID field - Complements @TargetNode for full relationship properties support ## Implementation Details: - Enhanced FalkorDBQueryMethod with @query support methods - Created StringBasedFalkorDBQuery for executing custom queries - Added comprehensive examples in test entities (Movie, Person, ActedIn) - Updated TwitterUserRepository with @query examples - Added detailed documentation in ANNOTATIONS.md ## Usage Examples: - Parameter binding: @query("MATCH (u:User) WHERE u.name = RETURN u") - Count queries: @query(value = "...", count = true) - Entity parameters: @query("... WHERE u.id = .__id__ ...") - Relationship properties with @TargetNode for target node mapping Based on Spring Data Neo4j documentation patterns and adapted for FalkorDB.
- Fixed method access issues in FalkorDBQueryMethod by storing method reference - Updated StringBasedFalkorDBQuery to use correct FalkorDBOperations methods - Fixed test compilation error in AnnotationUsageTests - Applied Spring Java formatting to all files All code now compiles successfully and tests pass.
WalkthroughThis pull request introduces support for custom Cypher queries via the Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Repository Client
participant Repo as Repository Method
participant QM as FalkorDBQueryMethod
participant SBQ as StringBasedFalkorDBQuery
participant FO as FalkorDBOperations
participant DB as FalkorDB
Client->>Repo: invoke method with @Query
Repo->>QM: created with reflected method
QM->>QM: hasAnnotatedQuery() checks @Query presence
alt Query Present
Repo->>SBQ: execute(method, args)
SBQ->>QM: getAnnotatedQuery() retrieves Cypher
SBQ->>SBQ: build parameter map from args
SBQ->>QM: isCountQuery() / isExistsQuery() / isWriteQuery()
alt Count Query
SBQ->>FO: query(cypher, params, Long.class)
FO->>DB: execute Cypher
DB-->>FO: return count results
FO-->>SBQ: Long value
SBQ-->>Repo: processed count
else Exists Query
SBQ->>FO: query(cypher, params, Boolean.class)
FO->>DB: execute Cypher
DB-->>FO: return boolean results
FO-->>SBQ: Boolean value
SBQ-->>Repo: processed exists
else Collection Query
SBQ->>FO: query(cypher, params, domain type)
FO->>DB: execute Cypher
DB-->>FO: return records
FO-->>SBQ: collection
SBQ-->>Repo: processed results
end
Repo-->>Client: return typed result
else No Query
SBQ-->>SBQ: throw IllegalStateException
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes The changes span heterogeneous concerns including CI/CD infrastructure migration, new public API annotations with annotation processing logic, query execution implementation with multiple result-type branches, and extensive examples. The StringBasedFalkorDBQuery class contains logic density across count/exists/collection/single-result handling paths. The workflow changes require verification of credential handling and Maven Central integration. Multiple interdependent files demand contextual reasoning about how annotations flow through the query method introspection to execution. Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
||
| - name: Import GPG key | ||
| if: github.event_name == 'release' | ||
| uses: crazy-max/ghaction-import-gpg@v6 |
Check warning
Code scanning / CodeQL
Unpinned tag for a non-immutable Action in workflow Medium
Uses Step
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/main/java/org/springframework/data/falkordb/repository/query/StringBasedFalkorDBQuery.java (1)
61-66: Enable dynamic projection.Use ResultProcessor.withDynamicProjection(parameters) to let interface/class projections work.
- // ResultProcessor processor = - // queryMethod.getResultProcessor().withDynamicProjection(parameters); - ResultProcessor processor = queryMethod.getResultProcessor(); + ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(parameters);src/test/java/org/springframework/data/falkordb/examples/ActedIn.java (1)
40-42: Consider Set for roles and value semantics.If roles shouldn’t contain duplicates, prefer Set and add equals/hashCode to ActedIn for stable relationship property identity.
src/test/java/org/springframework/data/falkordb/examples/MovieRepository.java (1)
62-63: Consider using text blocks for multi-line Cypher queries.While string concatenation works, Java text blocks improve readability for complex queries. Since this is example code, cleaner formatting helps users understand the patterns.
For example, line 62 could be rewritten as:
- @Query("MATCH (m:Movie)-[r:ACTED_IN]-(p:Person {name: $actorName}) " + "RETURN m, collect(r), collect(p)") + @Query(""" + MATCH (m:Movie)-[r:ACTED_IN]-(p:Person {name: $actorName}) + RETURN m, collect(r), collect(p) + """) List<Movie> findMoviesByActorName(@Param("actorName") String actorName);And lines 66-67:
- @Query("MATCH (p1:Person {name: $actorName})-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person) " + "WHERE p1 <> p2 " - + "RETURN DISTINCT p2") + @Query(""" + MATCH (p1:Person {name: $actorName})-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person) + WHERE p1 <> p2 + RETURN DISTINCT p2 + """) List<Person> findCoActors(@Param("actorName") String actorName);Also applies to: 66-68
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
.github/workflows/publish.yml(1 hunks).github/workflows/release.yml(3 hunks)ANNOTATIONS.md(1 hunks)pom.xml(2 hunks)settings.xml(0 hunks)src/main/java/org/springframework/data/falkordb/core/schema/RelationshipId.java(1 hunks)src/main/java/org/springframework/data/falkordb/core/schema/TargetNode.java(1 hunks)src/main/java/org/springframework/data/falkordb/repository/query/FalkorDBQueryMethod.java(4 hunks)src/main/java/org/springframework/data/falkordb/repository/query/Query.java(1 hunks)src/main/java/org/springframework/data/falkordb/repository/query/StringBasedFalkorDBQuery.java(1 hunks)src/test/java/org/springframework/data/falkordb/examples/ActedIn.java(1 hunks)src/test/java/org/springframework/data/falkordb/examples/AnnotationUsageTests.java(1 hunks)src/test/java/org/springframework/data/falkordb/examples/Movie.java(1 hunks)src/test/java/org/springframework/data/falkordb/examples/MovieRepository.java(1 hunks)src/test/java/org/springframework/data/falkordb/integration/TwitterUserRepository.java(2 hunks)
💤 Files with no reviewable changes (1)
- settings.xml
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/org/springframework/data/falkordb/examples/AnnotationUsageTests.java (1)
src/test/java/org/springframework/data/falkordb/examples/ActedIn.java (1)
RelationshipProperties(31-88)
🪛 GitHub Check: CodeQL
.github/workflows/release.yml
[warning] 57-57: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Release' step Uses Step uses 'crazy-max/ghaction-import-gpg' with ref 'v6', not a pinned commit hash
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Tests (Java 17)
- GitHub Check: Tests (Java 21)
🔇 Additional comments (23)
src/main/java/org/springframework/data/falkordb/core/schema/RelationshipId.java (1)
66-73: LGTM! Well-structured annotation with comprehensive documentation.The annotation definition follows Spring Data conventions with appropriate retention policy, target, and API stability marking. The Javadoc clearly explains the purpose and provides practical examples.
src/main/java/org/springframework/data/falkordb/core/schema/TargetNode.java (1)
79-86: LGTM! Excellent documentation and consistent design.The annotation is well-designed with comprehensive Javadoc that includes a complete example showing how @TargetNode integrates with @RelationshipProperties, @relationship, and other annotations. This will be very helpful for users.
.github/workflows/publish.yml (2)
45-47: LGTM! Maven Central credentials properly configured.The Java setup correctly configures Maven Central server credentials using GitHub secrets. This aligns with the pom.xml changes for central publishing.
51-51: Skipping GPG signing for SNAPSHOT builds is compliant with Maven Central.GPG signing is not required for snapshot builds. The
-Dgpg.skip=trueflag for SNAPSHOTs aligns with Maven Central's requirements, where GPG signing is required for releases but not for SNAPSHOTs. No changes needed..github/workflows/release.yml (2)
45-53: LGTM! Version extraction from git tags is correctly implemented.The version extraction logic properly strips the
refs/tags/vprefix and exports it to GITHUB_OUTPUT for use in subsequent steps.
75-84: LGTM! Maven Central deployment correctly configured.The release deployment step properly activates the release profile, which triggers GPG signing and central publishing as configured in pom.xml. The credentials are correctly sourced from secrets.
ANNOTATIONS.md (1)
1-230: LGTM! Comprehensive and well-structured documentation.The documentation thoroughly covers all three new annotations with practical examples, parameter binding techniques, query types, and best practices. The migration notes from Spring Data Neo4j will be particularly helpful for users transitioning to FalkorDB.
The examples align well with the actual annotation implementations and test cases in the codebase.
src/test/java/org/springframework/data/falkordb/examples/AnnotationUsageTests.java (4)
46-66: LGTM! Clear demonstration of @TargetNode annotation usage.The test effectively demonstrates how to construct relationship entities with the @TargetNode annotation, showing the linkage between Person and ActedIn relationship properties.
68-92: LGTM! Good example of relationship properties with multiple actors.The test demonstrates a practical scenario with multiple relationship entities, showing how Movies aggregate ActedIn relationships with different actors and roles.
94-136: Documentation test appropriately structured.This test method effectively documents various @query annotation patterns through commented examples. While it doesn't execute queries, it serves as inline documentation reference which is valuable for developers.
138-164: Clear explanation of annotation interactions.The test effectively documents how @RelationshipProperties, @TargetNode, and @RelationshipId work together, providing context for their combined usage.
src/main/java/org/springframework/data/falkordb/repository/query/Query.java (3)
61-73: LGTM! Proper use of @AliasFor for query string aliases.The value() and cypher() methods are correctly configured as aliases using Spring's @AliasFor annotation. This allows users to use either attribute name interchangeably.
81-109: LGTM! Well-documented boolean flags for query types.The count(), exists(), and write() flags are clearly documented with their purposes and usage implications. This provides users with clear guidance on when to use each flag.
27-60: LGTM! Excellent Javadoc with comprehensive examples.The annotation's Javadoc thoroughly documents:
- Purpose and usage context
- All three parameter binding styles (index, named, entity property)
- Practical examples for each pattern
This will be very helpful for developers using the annotation.
pom.xml (3)
70-79: LGTM! Maven Central distribution management correctly configured.The distribution management section properly configures both release and snapshot repositories to point to Maven Central endpoints. The server IDs match those referenced in the CI workflows.
804-817: LGTM! GPG signing properly configured for releases.The maven-gpg-plugin is correctly configured to sign artifacts during the verify phase, which is required for Maven Central publishing.
818-828: LGTM! Central publishing plugin properly configured.The central-publishing-maven-plugin configuration is correct:
publishingServerIdmatches the server-id in distribution management and workflowsautoPublish=trueenables automatic publishing after validationwaitUntil=publishedensures the workflow waits for publication to completeThis aligns with Maven Central publishing best practices.
src/test/java/org/springframework/data/falkordb/examples/Movie.java (1)
43-48: Relationship modeling looks correct.Incoming ACTED_IN with properties (ActedIn) and DIRECTED to Person align with the examples.
src/main/java/org/springframework/data/falkordb/repository/query/FalkorDBQueryMethod.java (1)
61-114: Annotation parsing and flags LGTM.Query string fallback (value → cypher) and count/exists/write flags are correctly exposed.
src/test/java/org/springframework/data/falkordb/examples/MovieRepository.java (4)
16-31: LGTM! Well-structured example repository.The interface declaration and imports are clean and appropriate for demonstrating @query annotation patterns.
49-59: LGTM! Excellent demonstration of query type attributes.The examples correctly showcase count, exists, and write operations with appropriate @query attributes and return types.
70-72: LGTM! Clean multi-parameter query example.The range query correctly demonstrates multiple parameter binding with clear, readable Cypher syntax.
46-47: Code is correct—no changes needed.The Movie entity at
src/test/java/org/springframework/data/falkordb/examples/Movie.javaline 36 confirms thattitleis the@Idfield (@Id private String title;). The query correctly uses$movie.__id__to bind to this identifier and matches on{title: $movie.__id__}.
| - name: Import GPG key | ||
| if: github.event_name == 'release' | ||
| uses: crazy-max/ghaction-import-gpg@v6 | ||
| with: | ||
| gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} | ||
| passphrase: ${{ secrets.GPG_PASSPHRASE }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security: Pin the GitHub Action to a commit hash.
The crazy-max/ghaction-import-gpg@v6 action uses a mutable tag reference. For supply chain security, pin to a specific commit SHA.
Apply this change to pin to a commit:
- - name: Import GPG key
- if: github.event_name == 'release'
- uses: crazy-max/ghaction-import-gpg@v6
- with:
- gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
- passphrase: ${{ secrets.GPG_PASSPHRASE }}
+ - name: Import GPG key
+ if: github.event_name == 'release'
+ uses: crazy-max/ghaction-import-gpg@cb9bde4e2ea896cc8e1166c0d262ffa2577c7e64 # v6.2.0
+ with:
+ gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+ passphrase: ${{ secrets.GPG_PASSPHRASE }}Based on learnings: Supply chain security best practices recommend pinning third-party actions to commit SHAs rather than mutable tags.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Import GPG key | |
| if: github.event_name == 'release' | |
| uses: crazy-max/ghaction-import-gpg@v6 | |
| with: | |
| gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} | |
| passphrase: ${{ secrets.GPG_PASSPHRASE }} | |
| - name: Import GPG key | |
| if: github.event_name == 'release' | |
| uses: crazy-max/ghaction-import-gpg@cb9bde4e2ea896cc8e1166c0d262ffa2577c7e64 # v6.2.0 | |
| with: | |
| gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} | |
| passphrase: ${{ secrets.GPG_PASSPHRASE }} |
🧰 Tools
🪛 GitHub Check: CodeQL
[warning] 57-57: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Release' step Uses Step uses 'crazy-max/ghaction-import-gpg' with ref 'v6', not a pinned commit hash
🤖 Prompt for AI Agents
.github/workflows/release.yml around lines 55 to 60: the workflow uses the
mutable tag crazy-max/ghaction-import-gpg@v6 which is a supply-chain risk;
replace the tag with a specific commit SHA from the action's repository (e.g.
crazy-max/ghaction-import-gpg@<commit-sha>) by locating a vetted commit in the
action's repo, update the uses line to that full SHA, and commit the change so
the workflow is pinned to an immutable reference.
| if (queryMethod.isCountQuery()) { | ||
| // For count queries, execute the query and return the count | ||
| List<Long> results = operations.query(query, parameterMap, Long.class); | ||
| Long count = results.isEmpty() ? 0L : results.get(0); | ||
| return processor.processResult(count); | ||
| } | ||
|
|
||
| if (queryMethod.isExistsQuery()) { | ||
| // For exists queries, execute the query and return the boolean result | ||
| List<Boolean> results = operations.query(query, parameterMap, Boolean.class); | ||
| Boolean exists = results.isEmpty() ? false : results.get(0); | ||
| return processor.processResult(exists); | ||
| } | ||
|
|
||
| if (queryMethod.isCollectionQuery()) { | ||
| return processor.processResult(operations.query(query, parameterMap, returnedType.getDomainType())); | ||
| } | ||
|
|
||
| // Single result query | ||
| Optional<?> result = operations.queryForObject(query, parameterMap, returnedType.getDomainType()); | ||
| return processor.processResult(result.orElse(null)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Write queries flagged via @query(write = true) are ignored.
There’s no branch for write operations; methods annotated as write will execute the read path, likely failing or returning unexpected results.
- Implement an early branch for writes and delegate to the appropriate FalkorDBOperations write API (e.g., update/execute) returning update count or honoring void return.
Proposed diff (adjust the operations method name to match your API):
ReturnedType returnedType = processor.getReturnedType();
+ if (queryMethod.isWriteQuery()) {
+ // Choose the correct operations method (e.g., update/executeWrite) and return update count if method expects it.
+ long updates = operations.update(query, parameterMap);
+ return processor.processResult(updates);
+ }Run this to verify the available ops and adapt the call:
🏁 Script executed:
#!/bin/bash
rg -nP 'interface\s+FalkorDBOperations\b|class\s+.*FalkorDBOperations' -A40 -B2
rg -nP '@Query\([^)]*\bwrite\s*=\s*true\b' -C2Length of output: 9681
I need to gather more information. The previous output was incomplete. Let me run targeted searches to verify the review's claims.
🏁 Script executed:
#!/bin/bash
# Get full FalkorDBOperations interface definition
fd -t f 'FalkorDBOperations\.java' | xargs cat | head -200
# Search for isWriteQuery method
rg -nP '\bisWriteQuery\b' -C2
# Search for @Query with write parameter
rg -nP '@Query\(' -C3Length of output: 22544
Now let me inspect the StringBasedFalkorDBQuery file to see the actual implementation:
🏁 Script executed:
#!/bin/bash
# View the full StringBasedFalkorDBQuery.java file
fd -t f 'StringBasedFalkorDBQuery\.java' | xargs catLength of output: 3960
Let me check FalkorDBTemplate for write operation support:
🏁 Script executed:
#!/bin/bash
# Check FalkorDBTemplate for write/update/execute methods
fd -t f 'FalkorDBTemplate\.java' | xargs cat | grep -n -A5 -B2 'public.*\(update\|execute\|write\)'
# Also search for any mention of write in the file
fd -t f 'FalkorDBTemplate\.java' | xargs cat | grep -n -i 'write'Length of output: 54
Based on my verification, the review comment identifies a real issue that requires correction. Here is the rewritten review:
Implement handling for write queries annotated with @query(write = true).
Currently, StringBasedFalkorDBQuery.execute() has no branch for write operations. Methods annotated with @Query(write = true) will execute the read path via queryForObject(), failing or returning incorrect results.
Required changes:
- Add an
update()method toFalkorDBOperationsinterface to support write queries with appropriate return type (e.g.,longfor update count). - Implement this method in
FalkorDBTemplate. - Add an early branch in
StringBasedFalkorDBQuery.execute()to detect and delegate write queries:
if (queryMethod.isWriteQuery()) {
// Choose the correct operations method and handle return type appropriately
long updates = operations.update(query, parameterMap);
return processor.processResult(updates);
}Place this branch after the count/exists checks and before the collection/default read path.
🤖 Prompt for AI Agents
In
src/main/java/org/springframework/data/falkordb/repository/query/StringBasedFalkorDBQuery.java
around lines 67 to 88, add handling for write queries annotated with
@Query(write = true): detect queryMethod.isWriteQuery() after the count/exists
checks and before the collection/single-result branches, call a new
operations.update(query, parameterMap) to perform the write, capture the
returned update count, and pass that count into processor.processResult(...). To
support this, add an update(...) method to the FalkorDBOperations interface with
an appropriate numeric return type (e.g., long), implement that method in
FalkorDBTemplate delegating to the underlying client, and ensure the execute()
branch uses the correct numeric type and null-safety when returning the
processed result.
| private Map<String, Object> createParameterMap(Object[] parameters) { | ||
| Map<String, Object> parameterMap = new HashMap<>(); | ||
|
|
||
| // Add indexed parameters ($0, $1, ...) | ||
| if (parameters != null) { | ||
| for (int i = 0; i < parameters.length; i++) { | ||
| parameterMap.put(String.valueOf(i), parameters[i]); | ||
| } | ||
| } | ||
|
|
||
| // Add named parameters if available | ||
| // This would typically involve analyzing @Param annotations | ||
| // For now, we'll keep it simple with indexed parameters | ||
|
|
||
| return parameterMap; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Named parameters not bound; $username placeholders will fail. Also index mapping must ignore special parameters.
Currently only numeric keys "0","1",... are created from raw method indices. This breaks:
- Named placeholders (e.g., $username).
- Methods with Pageable/Sort: numeric indices must be computed over bindable parameters, not raw args.
Implement binding via QueryMethod parameters: add both the bindable index ("0","1",...) and the (optional) @Param/compiled name.
Apply this diff:
- private Map<String, Object> createParameterMap(Object[] parameters) {
- Map<String, Object> parameterMap = new HashMap<>();
-
- // Add indexed parameters ($0, $1, ...)
- if (parameters != null) {
- for (int i = 0; i < parameters.length; i++) {
- parameterMap.put(String.valueOf(i), parameters[i]);
- }
- }
-
- // Add named parameters if available
- // This would typically involve analyzing @Param annotations
- // For now, we'll keep it simple with indexed parameters
-
- return parameterMap;
- }
+ private Map<String, Object> createParameterMap(Object[] parameters) {
+ Map<String, Object> parameterMap = new HashMap<>();
+ if (parameters == null) {
+ return parameterMap;
+ }
+
+ var paramsInfo = this.queryMethod.getParameters();
+ int bindIndex = 0;
+ for (org.springframework.data.repository.query.Parameter p : paramsInfo.getBindableParameters()) {
+ Object value = parameters[p.getIndex()];
+ // numeric placeholders $0,$1,... over bindable params only
+ parameterMap.put(String.valueOf(bindIndex), value);
+ // named placeholders $name (from @Param or -parameters)
+ String name = p.getName();
+ if (org.springframework.util.StringUtils.hasText(name)) {
+ parameterMap.put(name, value);
+ }
+ bindIndex++;
+ }
+ return parameterMap;
+ }Add imports outside this hunk:
import org.springframework.util.StringUtils;
import org.springframework.data.repository.query.Parameter;Based on learnings (Spring 6.2+: compile with -parameters to retain names).
🤖 Prompt for AI Agents
In
src/main/java/org/springframework/data/falkordb/repository/query/StringBasedFalkorDBQuery.java
around lines 99 to 114, the current createParameterMap only adds raw numeric
keys ("0","1",...) from the method args which breaks named placeholders (e.g.
$username) and misaligns indices when special parameters (Pageable/Sort) are
present; update the method to iterate the QueryMethod/Parameters metadata
instead of raw args, skip non-bindable parameters (e.g. Pageable, Sort),
maintain a bindableIndex counter to populate numeric keys ("0","1",...) based on
bindable parameters only, and for each bindable parameter add both the numeric
bindable index key and a named key using @Param value if present or the compiled
parameter name (using Parameter#getName and StringUtils.hasText); also add the
requested imports: org.springframework.util.StringUtils and
org.springframework.data.repository.query.Parameter.
| @Query("MATCH (m:Movie {title: $title})-[r:ACTED_IN]-(p:Person) RETURN m, collect(r), collect(p)") | ||
| Optional<Movie> findMovieWithActors(@Param("title") String title); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the undirected relationship pattern matches your domain model.
The query uses an undirected relationship pattern -[r:ACTED_IN]- which matches relationships in both directions. If your domain model has a specific direction (e.g., (Person)-[:ACTED_IN]->(Movie)), consider using a directed pattern for consistency and clarity.
🏁 Script executed:
#!/bin/bash
# Verify the ACTED_IN relationship direction in entity definitions and other queries
# Check ActedIn relationship entity definition
echo "=== Checking ActedIn relationship direction in entity ==="
fd -e java ActedIn.java --exec cat {}
echo -e "\n=== Checking relationship direction in other queries ==="
rg -n --type=java -C2 'ACTED_IN'Length of output: 8146
Use directed relationship pattern to match domain model definition.
The domain model defines @Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING) on the Movie entity, indicating the relationship flows from Person to Movie: (Person)-[:ACTED_IN]->(Movie). However, lines 42, 46, and 62 use undirected patterns -[r:ACTED_IN]- which match relationships in both directions. Line 66's co-actors query correctly uses the directed pattern. Ensure consistency by updating lines 42, 46, and 62 to use directed relationships:
- Line 42:
(p:Person)-[:ACTED_IN]->(m:Movie {title: $title}) - Line 46:
(p:Person)-[:ACTED_IN]->(m:Movie {title: $movie.__id__}) - Line 62:
(p:Person {name: $actorName})-[:ACTED_IN]->(m:Movie)
🤖 Prompt for AI Agents
In src/test/java/org/springframework/data/falkordb/examples/MovieRepository.java
around lines 42, 46 and 62 the Cypher patterns use an undirected relationship
`-[r:ACTED_IN]-` which contradicts the domain model that declares the
relationship as incoming to Movie (Person)-[:ACTED_IN]->(Movie); update each
pattern accordingly: line 42 use `(p:Person)-[:ACTED_IN]->(m:Movie {title:
$title})`, line 46 use `(p:Person)-[:ACTED_IN]->(m:Movie {title:
$movie.__id__})`, and line 62 use `(p:Person {name:
$actorName})-[:ACTED_IN]->(m:Movie)` so the queries match the directed
relationship defined on the Movie entity.
| // Custom query methods using @Query annotation | ||
| @Query("MATCH (u:User)-[:FOLLOWS]->(f:User) WHERE u.username = $username RETURN f") | ||
| List<TwitterUser> findFollowing(@Param("username") String username); | ||
|
|
||
| @Query("MATCH (f:User)-[:FOLLOWS]->(u:User) WHERE u.username = $username RETURN f") | ||
| List<TwitterUser> findFollowers(@Param("username") String username); | ||
|
|
||
| @Query("MATCH (u:User) WHERE u.followerCount > $0 AND u.verified = $1 RETURN u ORDER BY u.followerCount DESC") | ||
| List<TwitterUser> findTopVerifiedUsers(Integer minFollowers, Boolean verified); | ||
|
|
||
| @Query(value = "MATCH (u:User)-[:FOLLOWS]->() WHERE u.username = $username RETURN count(*)", count = true) | ||
| Long countFollowing(@Param("username") String username); | ||
|
|
||
| @Query(value = "MATCH ()-[:FOLLOWS]->(u:User) WHERE u.username = $username RETURN count(*)", count = true) | ||
| Long countFollowers(@Param("username") String username); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Named placeholder usage will not bind with current executor.
Your queries use $username but StringBasedFalkorDBQuery only binds numeric indices. Until the binder is fixed, these methods will fail at runtime.
Options:
- Prefer fixing the binder (recommended; see suggested refactor in StringBasedFalkorDBQuery).
- Temporary workaround: switch to numeric placeholders and drop @param:
- @Query("MATCH (u:User)-[:FOLLOWS]->(f:User) WHERE u.username = $username RETURN f")
- List<TwitterUser> findFollowing(@Param("username") String username);
+ @Query("MATCH (u:User)-[:FOLLOWS]->(f:User) WHERE u.username = $0 RETURN f")
+ List<TwitterUser> findFollowing(String username);
- @Query("MATCH (f:User)-[:FOLLOWS]->(u:User) WHERE u.username = $username RETURN f")
- List<TwitterUser> findFollowers(@Param("username") String username);
+ @Query("MATCH (f:User)-[:FOLLOWS]->(u:User) WHERE u.username = $0 RETURN f")
+ List<TwitterUser> findFollowers(String username);
- @Query(value = "MATCH (u:User)-[:FOLLOWS]->() WHERE u.username = $username RETURN count(*)", count = true)
- Long countFollowing(@Param("username") String username);
+ @Query(value = "MATCH (u:User)-[:FOLLOWS]->() WHERE u.username = $0 RETURN count(*)", count = true)
+ Long countFollowing(String username);
- @Query(value = "MATCH ()-[:FOLLOWS]->(u:User) WHERE u.username = $username RETURN count(*)", count = true)
- Long countFollowers(@Param("username") String username);
+ @Query(value = "MATCH ()-[:FOLLOWS]->(u:User) WHERE u.username = $0 RETURN count(*)", count = true)
+ Long countFollowers(String username);Verification aid:
Based on learnings (parameter names via -parameters or @param).
🏁 Script executed:
#!/bin/bash
# Show all named placeholders used in @Query
rg -nP '@Query\([^)]*?\$[A-Za-z_]\w*' -C2
# Show methods with Pageable/Sort which need bindable indexing
rg -nP '\b(Pageable|Sort)\b' -n -C2 --type=javaLength of output: 22877
Now let me search the codebase directly to examine the StringBasedFalkorDBQuery implementation:
🏁 Script executed:
#!/bin/bash
# Locate StringBasedFalkorDBQuery and examine parameter binding logic
fd -t f "StringBasedFalkorDBQuery.java"
if [ $? -eq 0 ]; then
echo "=== Found StringBasedFalkorDBQuery ==="
cat "$(fd -t f StringBasedFalkorDBQuery.java)"
fi
# Also check for any test files related to parameter binding
fd -t f ".*Test.*" | xargs grep -l "StringBasedFalkorDBQuery\|@Query.*\$" | head -5Length of output: 4176
Let me verify whether the example code with named parameters is actually tested or just documentation:
🏁 Script executed:
#!/bin/bash
# Search for actual test invocations of MovieRepository named parameter methods
rg -n "findMovieWithActors|findActorsInMovie|existsByTitle|updateMovieTimestamp|findCoActors" --type=java -C3
# Check if TwitterUserRepository is being tested
rg -n "TwitterUserRepository\|findFollowing\|findFollowers\|countFollowing" --type=java -C2Length of output: 6881
Let me verify the TwitterUserRepository file matches the review comment lines:
🏁 Script executed:
#!/bin/bash
# Get the exact content of TwitterUserRepository.java lines 44-59
sed -n '44,59p' src/test/java/org/springframework/data/falkordb/integration/TwitterUserRepository.javaLength of output: 888
Named parameter binding is not implemented in StringBasedFalkorDBQuery; these methods will fail at runtime.
The review comment is correct. StringBasedFalkorDBQuery's createParameterMap() method only creates indexed parameters (e.g., $0, $1), and the code explicitly states "For now, we'll keep it simple with indexed parameters" with a note that named parameter extraction "would typically involve analyzing @param annotations" but is not yet implemented.
Lines 46, 49, 55, and 58 use named placeholders ($username) with @Param annotations—these will fail to bind. Line 52 correctly uses numeric indices ($0, $1) and will work.
Your suggested workarounds are valid: either fix StringBasedFalkorDBQuery.createParameterMap() to extract and process @Param annotations, or convert the four affected methods to numeric placeholder syntax. The diff provided is correct for the temporary workaround approach.
🤖 Prompt for AI Agents
In
src/test/java/org/springframework/data/falkordb/integration/TwitterUserRepository.java
around lines 44-59, named parameter placeholders like $username used with @Param
will fail because StringBasedFalkorDBQuery only supports indexed parameters ($0,
$1); either update createParameterMap() to extract @Param names and map them to
values, or change these repository methods to use numeric placeholders matching
parameter order (e.g., replace $username with $0) and remove or keep @Param
annotations as needed so the runtime can bind parameters correctly.
This PR implements @query and @TargetNode annotations for Spring Data FalkorDB based on Spring Data Neo4j patterns.
New Annotations:
@query Annotation
@TargetNode Annotation
@RelationshipId Annotation
Features:
Status:
Summary by CodeRabbit
New Features
Documentation
Chores