Skip to content

#3315 fix: resolve argument expressions correctly in CREATE EDGE stat…#3323

Merged
robfrank merged 2 commits intomainfrom
fix/3315-create-edge
Feb 5, 2026
Merged

#3315 fix: resolve argument expressions correctly in CREATE EDGE stat…#3323
robfrank merged 2 commits intomainfrom
fix/3315-create-edge

Conversation

@robfrank
Copy link
Collaborator

@robfrank robfrank commented Feb 4, 2026

…ements

Fixed property extraction from Result collections in SuffixIdentifier to return unwrapped values instead of Result wrappers. Enhanced CreateEdgesStep to handle projection Results by extracting @Rid property when vertex element is unavailable.

This allows CREATE EDGE to properly resolve expressions like:

  • LET $x = SELECT @Rid FROM V; CREATE EDGE E FROM $x.@Rid TO $x.@Rid
  • CREATE EDGE E FROM (SELECT @Rid FROM V).@Rid TO (SELECT @Rid FROM V).@Rid

…ements

Fixed property extraction from Result collections in SuffixIdentifier to return
unwrapped values instead of Result wrappers. Enhanced CreateEdgesStep to handle
projection Results by extracting @Rid property when vertex element is unavailable.

This allows CREATE EDGE to properly resolve expressions like:
- LET $x = SELECT @Rid FROM V; CREATE EDGE E FROM $x.@Rid TO $x.@Rid
- CREATE EDGE E FROM (SELECT @Rid FROM V).@Rid TO (SELECT @Rid FROM V).@Rid

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@robfrank robfrank linked an issue Feb 4, 2026 that may be closed by this pull request
@robfrank robfrank added the sql label Feb 4, 2026
@robfrank robfrank added this to the 26.2.1 milestone Feb 4, 2026
@robfrank robfrank requested a review from gramian February 4, 2026 13:49
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @robfrank, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug in ArcadeDB's SQL execution engine that prevented CREATE EDGE statements from correctly resolving vertex references when using the results of subqueries or LET variables. The fix involves modifying the property extraction logic and enhancing vertex resolution during edge creation, along with adding a new test case to ensure the fix's correctness and prevent future regressions.

Highlights

  • Fixes CREATE EDGE Statement: This PR resolves an issue where CREATE EDGE statements failed to correctly resolve argument expressions when using query result sets, specifically when accessing properties like @rid.
  • SuffixIdentifier Modification: The SuffixIdentifier.java file was modified to ensure that property extraction from Result collections returns unwrapped values instead of Result wrappers.
  • CreateEdgesStep Enhancement: The CreateEdgesStep.java file was enhanced to handle projection Results by extracting the @rid property when a vertex element is unavailable.
  • Comprehensive Testing: A new test case, createEdgeWithPropertyAccessOnResultSet(), was added to QueryTest.java to cover various scenarios, including LET variables and subqueries with property access, ensuring the fix works as expected and prevents regressions.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • docs/3315-create-edge.md
  • engine/src/main/java/com/arcadedb/query/sql/executor/CreateEdgesStep.java
    • Enhanced vertex resolution for projection Results.
    • Added @rid property extraction fallback.
  • engine/src/main/java/com/arcadedb/query/sql/parser/SuffixIdentifier.java
    • Fixed property extraction from Result collections.
    • Added unwrapping logic for Result objects.
  • engine/src/test/java/com/arcadedb/query/sql/QueryTest.java
Activity
  • Analysis of the CREATE EDGE execution path to identify the bug in SuffixIdentifier.java.
  • Creation of a new test case in QueryTest.java to reproduce the bug scenarios.
  • Implementation of the fix in SuffixIdentifier.java and CreateEdgesStep.java.
  • Verification of the fix by running all tests and ensuring no regressions.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@claude
Copy link

claude bot commented Feb 4, 2026

Pull Request Review - Issue #3315 Fix

Summary

This PR fixes a bug where CREATE EDGE statements fail to resolve argument expressions when using query result sets. The fix properly unwraps Result objects when extracting properties from collections.


Strengths

  1. Well-Documented Fix: Excellent documentation in 3315-create-edge.md that thoroughly explains the root cause, execution flow, and impact analysis.

  2. Comprehensive Test Coverage: The test in QueryTest.java:677-767 covers all scenarios from the original issue:

    • LET variable with property access
    • Direct subquery with property access
    • Baseline working case (regression test)
    • Multiple edges from result set
  3. Root Cause Analysis: The fix correctly identifies that SuffixIdentifier was returning wrapped Result objects instead of unwrapped values.

  4. Minimal Scope: Changes are focused and surgical - only modifying the specific methods that need adjustment.


🔍 Code Quality Observations

SuffixIdentifier.java (lines 175-220)

Positive:

Concerns:

  1. Code Duplication: The unwrapping logic is duplicated in two methods (lines 184-194 and 211-221). Consider extracting to a private helper method:
private Object unwrapResultIfNeeded(Object value) {
  if (value instanceof Result r && (identifier \!= null || recordAttribute \!= null)) {
    if (r.isElement()) {
      return r.getElement().get();
    } else if (identifier \!= null && r.hasProperty(identifier.getStringValue())) {
      return r.getProperty(identifier.getStringValue());
    } else if (recordAttribute \!= null) {
      return recordAttribute.evaluate(r, context);
    }
  }
  return value;
}
  1. Potential NPE: At line 187 and 214, r.getElement().get() could throw NoSuchElementException if Optional is empty. Should use orElse(null) or handle the empty case explicitly.

  2. Double Property Access: Lines 188-189 and 215-216 access the same property twice - once in hasProperty() check and once in getProperty(). Could optimize by storing the result.

  3. Edge Case: What happens when recordAttribute is not null but identifier is, and the Result doesn't have the recordAttribute? The fallback at line 193/220 adds the original value, but this might mask issues.

CreateEdgesStep.java (lines 295-312)

Positive:

  • Good defensive programming with proper null checks
  • Clear error messages
  • Maintains backward compatibility

Concerns:

  1. Redundant Null Check: Line 304 checks if (rid \!= null) but if it's null, line 307 throws the same exception. This could be simplified:
if (result.hasProperty("@rid")) {
  final Object rid = result.getProperty("@rid");
  if (rid \!= null) {
    currentFrom = rid;
  } else {
    throw new CommandExecutionException("Invalid vertex for edge creation: " + result);
  }
}

Could become:

if (result.hasProperty("@rid") && result.getProperty("@rid") \!= null) {
  currentFrom = result.getProperty("@rid");
} else {
  throw new CommandExecutionException("Invalid vertex for edge creation: " + result);
}
  1. Property Name Hardcoding: The string "@rid" is used directly. Consider using the constant RID_PROPERTY (line 40 in SuffixIdentifier.java imports it).

⚠️ Potential Issues

  1. Behavioral Change Scope: The unwrapping logic in SuffixIdentifier applies to ALL property/attribute access on Result collections, not just CREATE EDGE. This could have unintended side effects in other SQL commands. Have all related tests been run?

  2. Performance Impact: Every iteration now includes instanceof checks and conditional logic. For large result sets, this could add overhead. Consider benchmarking with large collections.

  3. Incomplete Fix?: The fix handles the case where property extraction returns a Result, but what about nested property access like .property.subproperty? The current implementation may not handle deep nesting correctly.


🔒 Security Considerations

No security concerns identified. The changes don't introduce:

  • SQL injection vulnerabilities
  • Authentication/authorization bypasses
  • Data exposure risks

🧪 Testing Recommendations

  1. Edge Cases to Test:

    • Empty result sets: CREATE EDGE FROM (SELECT @rid FROM V WHERE 1=0).@rid TO ...
    • Null properties: What if @Rid itself is null?
    • Non-existent properties: .nonExistent
    • Nested property access: .prop1.prop2
  2. Performance Testing: Run with large result sets (10k+ vertices) to ensure no performance degradation.

  3. Integration Tests: Verify other SQL commands using SuffixIdentifier still work correctly (UPDATE, DELETE, etc.).


📋 Code Style Compliance

Per CLAUDE.md guidelines:

Good:

  • Uses existing assertion style: assertThat(...).isTrue()
  • No System.out debug statements
  • Proper use of final keyword
  • Comments reference issue numbers

⚠️ Issues:

  • Documentation file docs/3315-create-edge.md (217 lines) shouldn't be committed per CLAUDE.md: "don't commit on git, I will do it after a review"
  • Consider if documentation belongs in main docs vs. throwaway analysis docs

🎯 Recommendations

Before Merge:

  1. Refactor: Extract duplicated unwrapping logic in SuffixIdentifier to reduce code duplication
  2. Fix NPE Risk: Use .orElse(null) instead of .get() on Optional
  3. Use Constants: Replace hardcoded "@rid" with RID_PROPERTY
  4. Remove Doc File: The docs/3315-create-edge.md file should not be committed (per project guidelines)
  5. Run Full Test Suite: Verify no regressions in other SQL commands using SuffixIdentifier

Nice to Have:

  1. Add edge case tests for empty results, null properties, nested access
  2. Performance benchmark with large result sets
  3. Consider adding JavaDoc to explain the unwrapping behavior

Overall Assessment

Quality: Good - The fix addresses the root cause correctly and includes solid test coverage.

Risk: Medium - Changes to SuffixIdentifier affect all property access on Result collections, requiring thorough regression testing.

Recommendation: Approve with minor revisions - Address the code duplication and NPE risks, remove the documentation file, then merge.

Great work identifying and fixing this issue! The thorough analysis and testing approach is exemplary.

@mergify
Copy link
Contributor

mergify bot commented Feb 4, 2026

🧪 CI Insights

Here's what we observed from your CI run for 537476a.

🟢 All jobs passed!

But CI Insights is watching 👀

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses the issue of resolving argument expressions in CREATE EDGE statements by unwrapping Result objects. The changes in SuffixIdentifier and CreateEdgesStep are logical, and the new test cases in QueryTest provide good coverage for the fix. I have a couple of suggestions to improve code clarity and reduce duplication.

- Extract duplicate unwrapping logic into private helper method (SuffixIdentifier)
- Fix potential NPE by using orElse(null) instead of get() on Optional
- Simplify CreateEdgesStep code by removing redundant hasProperty check

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@codacy-production
Copy link

codacy-production bot commented Feb 4, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-8.97% 58.33%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (46c5a3f) 100618 66194 65.79%
Head commit (537476a) 130197 (+29579) 73972 (+7778) 56.82% (-8.97%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#3323) 24 14 58.33%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

❌ Patch coverage is 41.66667% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.72%. Comparing base (46c5a3f) to head (537476a).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...om/arcadedb/query/sql/parser/SuffixIdentifier.java 29.41% 9 Missing and 3 partials ⚠️
...m/arcadedb/query/sql/executor/CreateEdgesStep.java 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3323      +/-   ##
==========================================
+ Coverage   56.68%   56.72%   +0.04%     
==========================================
  Files        1368     1368              
  Lines      100618   100633      +15     
  Branches    20486    20492       +6     
==========================================
+ Hits        57032    57082      +50     
+ Misses      34458    34431      -27     
+ Partials     9128     9120       -8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@claude
Copy link

claude bot commented Feb 4, 2026

PR Review: Fix CREATE EDGE Expression Resolution (#3315)

Summary

This PR fixes a bug where CREATE EDGE statements failed to properly resolve argument expressions when using query result sets. The fix addresses issue #3315 by unwrapping Result objects during property extraction in SuffixIdentifier and enhancing vertex resolution in CreateEdgesStep.


Code Quality & Best Practices ✅

Strengths:

  1. TDD Approach: Excellent adherence to the project's TDD guidelines - comprehensive tests written that cover all failure scenarios
  2. Code Style Compliance: Code follows existing patterns (single-line if statements without braces, final keyword usage)
  3. Clear Documentation: Inline comments reference issue SQL: CREATE EDGE fails to resolve argument expressions correctly #3315, making the code maintainable
  4. Refactoring Applied: Second commit shows good response to review feedback with extracted helper method
  5. No System.out: Clean code without debug statements

Minor Observations:

SuffixIdentifier.java (lines 206-226)

  • The unwrapResultIfNeeded method has good defensive programming with early returns
  • Potential Issue: Line 212 uses r.getElement().orElse(null) which is correct, but consider the semantic difference between "Result with no element" vs "Result with null element" - current behavior seems intentional
  • Edge Case: If identifier \!= null and r.getProperty(identifier.getStringValue()) returns a Result object itself, there's no recursive unwrapping. This seems intentional but worth noting for nested projections

CreateEdgesStep.java (lines 295-308)

  • Good two-tier fallback strategy (vertex first, then @Rid property)
  • The comment "for projection results" is helpful
  • Code is clear and follows the existing error handling pattern

Potential Bugs or Issues ⚠️

1. Null Handling in unwrapResultIfNeeded

Line 212: return r.getElement().orElse(null);

While syntactically correct, returning null could propagate NPEs downstream. However, reviewing the caller sites (lines 181, 191), the null is added to a List and eventually processed by asVertex(), which handles null appropriately. Status: Acceptable but worth monitoring

2. Inconsistent Unwrapping Strategy

The method tries three approaches in order:

  1. Get underlying element
  2. Get property by identifier
  3. Evaluate record attribute

Question: What happens if a Result has both an element AND a property matching the identifier? The method returns the element without checking the property. This seems like intended "element-first" semantics, but could surprise users if they expect property access.

Recommendation: Add a comment explaining the precedence order, e.g.:

// Precedence: 1) underlying element, 2) property, 3) record attribute

3. Property Null Check

Line 217: if (prop \!= null)

This prevents returning explicit null property values. If a property explicitly set to null exists, this will fall through to the record attribute evaluation. This might be intentional but could mask bugs.

Status: Likely acceptable for current use case, but document the behavior


Performance Considerations ⚡

Positive:

  1. Minimal Overhead: Unwrapping only occurs during property extraction from collections, which is already O(n)
  2. Early Returns: Good use of guard clauses minimizes unnecessary work
  3. No Recursion: Flat control flow avoids stack overhead

Neutral:

  1. instanceof Checks: Lines 207, 211 add instanceof checks. These are very fast JVM operations and unlikely to cause issues
  2. Additional Method Call: The helper method adds a call frame, but modern JVMs inline small methods effectively

Recommendation:

Performance impact is negligible. No concerns for production use.


Security Concerns 🔒

Reviewed Areas:

  1. No SQL Injection Risk: The fix operates on already-parsed query results, not user input
  2. No Unauthorized Data Access: Unwrapping respects existing Result permissions/visibility
  3. No Resource Leaks: No new resource acquisition (connections, streams, etc.)

Status: No security concerns identified


Test Coverage 🧪

Excellent Coverage:

The test createEdgeWithPropertyAccessOnResultSet() covers:

  1. ✅ LET variable with property access ($x.@rid)
  2. ✅ Direct subquery with property access ((SELECT ...).@rid)
  3. ✅ Baseline working case (regression check)
  4. ✅ Multiple edges from result set
  5. ✅ Proper verification of edge @in/@out properties

Suggestions for Additional Tests:

  1. Nested projections: SELECT property.subproperty FROM ...
  2. Null property values: Ensure explicit nulls are handled correctly
  3. Mixed types: Result sets containing both elements and projections
  4. Error cases: Non-existent properties, invalid RID formats
  5. Edge case: Empty result sets (SELECT @rid FROM V WHERE 1=0)

Test Style:

  • ✅ Follows existing AssertJ patterns (assertThat(...).isEqualTo(...))
  • ✅ Good use of cleanup between scenarios
  • ✅ Clear comments explaining each scenario

Architecture & Design 🏗️

Design Decision: Dual Fix Approach

The PR fixes the issue at two levels:

  1. Root Cause (SuffixIdentifier): Unwraps Results during property extraction
  2. Symptom (CreateEdgesStep): Falls back to @Rid property extraction

Analysis: This defense-in-depth approach is good, but raises a question: Is the CreateEdgesStep change necessary if SuffixIdentifier is fixed?

  • Pro: Provides robustness if SuffixIdentifier unwrapping is incomplete
  • Con: Adds code complexity and masks potential bugs elsewhere

Recommendation: Keep both changes but add a comment in CreateEdgesStep explaining this is a defensive fallback:

// Defensive fallback: extract @rid if vertex unavailable (should be rare after #3315 fix)

Documentation 📚

Excellent:

  • The 217-line docs/3315-create-edge.md provides thorough context
  • Commit messages follow project standards
  • Co-Authored-By tag is present

Suggestions:

  1. Consider adding SQL documentation examples (as noted in the doc file)
  2. Update CHANGELOG.md or release notes to mention the fix

Final Recommendation 🎯

APPROVE with Minor Suggestions

This is a high-quality fix that:

  • Correctly identifies and fixes the root cause
  • Includes comprehensive tests
  • Follows project coding standards
  • Has minimal performance/security impact
  • Demonstrates excellent TDD practices

Before Merge:

  1. ✅ Verify all tests pass (including regression suite) - already noted as done
  2. ⚠️ Consider adding the clarifying comments suggested above
  3. ⚠️ Consider edge case tests for empty result sets and nested projections
  4. ✅ Remove documentation file from commit if not intended for production (or move to docs/internal/)

Minor Polish (Optional):

  • Line 217: Consider if (prop \!= null || r.hasProperty(identifier.getStringValue())) to distinguish null values from missing properties
  • Add JavaDoc to unwrapResultIfNeeded explaining the precedence order

Great work on this fix! The thorough testing and clear problem analysis make this a solid contribution. The defensive programming approach with both SuffixIdentifier and CreateEdgesStep changes provides good robustness. 🚀

@robfrank
Copy link
Collaborator Author

robfrank commented Feb 4, 2026

PR Review Comments Addressed

I've pushed a new commit (537476a) addressing the relevant review feedback:

✅ Addressed

  1. SuffixIdentifier.java - Code Duplication (@gemini-code-assist, @claude[bot])

    • Extracted duplicated unwrapping logic into a private helper method unwrapResultIfNeeded()
    • Reduced code from ~50 lines to ~30 lines while maintaining the same functionality
  2. SuffixIdentifier.java - NPE Risk (@claude[bot])

    • Changed r.getElement().get() to r.getElement().orElse(null) to prevent potential NoSuchElementException
  3. CreateEdgesStep.java - Simplify Code (@gemini-code-assist, @claude[bot])

    • Removed redundant hasProperty("@rid") check since getProperty() returns null when property doesn't exist
    • Simplified from 11 lines to 6 lines

❌ Discarded (with rationale)

  1. Remove docs/3315-create-edge.md

    • This documentation was explicitly requested as part of the bug-fix workflow deliverables
    • It provides valuable analysis, root cause documentation, and test coverage details for future reference
  2. Use RID_PROPERTY constant instead of "@Rid" string

    • While valid for strict coding standards, "@rid" is a widely-used SQL construct throughout the ArcadeDB codebase (100+ usages)
    • Changing this single instance would be inconsistent with the rest of the codebase

All tests pass after the refactoring (verified with QueryTest#createEdgeWithPropertyAccessOnResultSet and CreateEdge* tests).

@robfrank robfrank merged commit 01b09bd into main Feb 5, 2026
24 of 27 checks passed
robfrank added a commit that referenced this pull request Feb 17, 2026
#3323)

* #3315 fix: resolve argument expressions correctly in CREATE EDGE statements

Fixed property extraction from Result collections in SuffixIdentifier to return
unwrapped values instead of Result wrappers. Enhanced CreateEdgesStep to handle
projection Results by extracting @Rid property when vertex element is unavailable.

This allows CREATE EDGE to properly resolve expressions like:
- LET $x = SELECT @Rid FROM V; CREATE EDGE E FROM $x.@Rid TO $x.@Rid
- CREATE EDGE E FROM (SELECT @Rid FROM V).@Rid TO (SELECT @Rid FROM V).@Rid
- Extract duplicate unwrapping logic into private helper method (SuffixIdentifier)
- Fix potential NPE by using orElse(null) instead of get() on Optional
- Simplify CreateEdgesStep code by removing redundant hasProperty check

(cherry picked from commit 01b09bd)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL: CREATE EDGE fails to resolve argument expressions correctly

1 participant