Skip to content

#3571 fix: accessing internal properties #3572

Merged
robfrank merged 2 commits intomainfrom
fix/3571-internal-properties-in-function-results
Mar 3, 2026
Merged

#3571 fix: accessing internal properties #3572
robfrank merged 2 commits intomainfrom
fix/3571-internal-properties-in-function-results

Conversation

@robfrank
Copy link
Collaborator

@robfrank robfrank commented Mar 3, 2026

accessing internal properties (@Rid, @type, etc.) on functionon results

In SQLASTBuilder.visitModifier(), property-access modifiers always created a plain Identifier node, even for record attribute tokens like @Rid. This caused expressions like inE().@Rid to return null because the execution path used document.get("@Rid") instead of the record attribute lookup.

Fixed by extracting a buildSuffixForIdentifier() helper that recognizes both unquoted record attribute tokens (RID_ATTR, TYPE_ATTR, etc.) and backtick-quoted identifiers whose value matches a known record attribute name. Both visitModifier and visitIdentifierChain now use this helper.

…ion results

In SQLASTBuilder.visitModifier(), property-access modifiers always created
a plain Identifier node, even for record attribute tokens like @Rid. This
caused expressions like inE().@Rid to return null because the execution
path used document.get("@Rid") instead of the record attribute lookup.

Fixed by extracting a buildSuffixForIdentifier() helper that recognizes
both unquoted record attribute tokens (RID_ATTR, TYPE_ATTR, etc.) and
backtick-quoted identifiers whose value matches a known record attribute
name. Both visitModifier and visitIdentifierChain now use this helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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 the SQL parser that prevented correct retrieval of internal record properties (like @Rid or @type) when accessed as a suffix on the result of a function call. The fix involves refactoring the AST builder to properly identify these special attributes, whether they are unquoted or backtick-quoted, ensuring that SQL queries involving such expressions now return the expected values.

Highlights

  • SQL Parsing Fix: Resolved an issue where accessing internal properties (e.g., @Rid, @type) on function results in SQL queries (e.g., inE().@Rid) incorrectly returned null.
  • Record Attribute Recognition: Enhanced the SQL AST builder to correctly recognize both unquoted and backtick-quoted internal record attributes.
  • Code Refactoring: Introduced new helper methods, buildSuffixForIdentifier and isRecordAttributeName, to centralize and improve the logic for parsing record attributes.
  • Improved Test Coverage: Added a dedicated test file to validate the fix for various scenarios involving internal property access on function results.

🧠 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
  • engine/src/main/java/com/arcadedb/query/sql/antlr/SQLASTBuilder.java
    • Imported com.arcadedb.schema.Property for attribute name checks.
    • Refactored the logic for creating SuffixIdentifier in visitIdentifierChain to use a new helper method.
    • Added a private helper method buildSuffixForIdentifier to correctly create SuffixIdentifier instances for both standard identifiers and internal record attributes (like @Rid, @type), handling both unquoted and backtick-quoted forms.
    • Introduced a private static helper method isRecordAttributeName to check if a given string corresponds to a known internal record attribute.
    • Modified visitModifier to utilize buildSuffixForIdentifier when processing property access modifiers, ensuring proper recognition of internal record attributes.
  • engine/src/test/java/com/arcadedb/query/sql/Issue3571InternalPropertyOnFunctionResultTest.java
    • Added a new test class to specifically verify the fix for accessing internal properties on function results.
    • Included test cases for unquoted and backtick-quoted @Rid access on inE() results, both for list and indexed access.
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.

@mergify
Copy link
Contributor

mergify bot commented Mar 3, 2026

🧪 CI Insights

Here's what we observed from your CI run for 474698e.

🟢 All jobs passed!

But CI Insights is watching 👀

@claude
Copy link

claude bot commented Mar 3, 2026

Code Review

Good fix for a clear and well-described bug. The approach is sound. Here are my observations:

Summary

The PR correctly identifies that visitModifier() was creating a plain Identifier node for record attribute tokens, causing inE().@rid to return null. Extracting buildSuffixForIdentifier() and applying it in both visitModifier and visitIdentifierChain is a clean refactoring.


Issues / Observations

1. Inconsistency: PROPS_ATTR not checked in the unquoted token path

buildSuffixForIdentifier checks for RID_ATTR, TYPE_ATTR, IN_ATTR, OUT_ATTR, and THIS in the token path, but not PROPS_ATTR (@props). The same gap exists in visitIdentifierChain's handling of the first identifier (lines 3106-3108 in the current code). Meanwhile, isRecordAttributeName does cover PROPERTY_TYPES_PROPERTY (@props).

If the grammar's identifier rule includes PROPS_ATTR as an alternative (it defines the token in the lexer), then .@props as an unquoted suffix would not be caught by the token check and would fall through to isRecordAttributeName. Whether that fallback actually catches it depends on how visit(idCtx) resolves an PROPS_ATTR token. Worth checking to ensure @props behaves consistently with @rid.

Similarly, @cat has no grammar token (CAT_ATTR is absent from the lexer), so it relies entirely on the isRecordAttributeName string-matching fallback. That seems intentional, but a comment explaining this would help future readers.

2. First-identifier handling in visitIdentifierChain not fully aligned

At line 3106-3108, the record-attribute check on the first identifier still uses the raw token checks but not the isRecordAttributeName fallback for backtick-quoted variants like `@rid`. So `@rid`.someProp at the start of a chain would not get RecordAttribute treatment, while .`@rid` in the middle (via buildSuffixForIdentifier) would. This asymmetry may or may not matter in practice, but it's worth documenting or aligning.

3. Test data accumulates across runs

Each test inserts vertices/edges without cleanup. IF NOT EXISTS prevents DDL failures on reruns, but data accumulates — on a second run, SELECT inE().@rid FROM V3571a would return a vertex row with two inbound edges instead of one. The tests only assert isNotEmpty(), which still passes, but it reduces determinism. Consider adding a @BeforeEach cleanup or asserting exact counts.

4. Test coverage: only @rid is exercised

All four test cases exercise @rid. It would strengthen confidence to add at least one case for @type and @cat (since they rely on the isRecordAttributeName string-matching path rather than the grammar token path), confirming the fallback path works correctly too.


Minor Notes

  • Using Property.RID_PROPERTY etc. instead of magic strings is the right call.
  • equalsIgnoreCase is appropriate for attribute name matching.
  • The Javadoc on buildSuffixForIdentifier and isRecordAttributeName is clear and helpful.
  • Test naming is clear and consistent with the issue number.

Overall this is a focused, well-targeted fix. Addressing the PROPS_ATTR token-path gap and expanding test coverage to non-@rid attributes would make it more complete.

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 correctly fixes an issue where internal properties like @rid were not accessible on function results. The solution involves a new helper method, buildSuffixForIdentifier, to properly recognize and handle these properties in the SQL parser. This is a solid fix, and the addition of a comprehensive test suite is commendable. I have one suggestion to refactor the new helper method for improved readability and to reduce code duplication.

Comment on lines +3397 to +3414
private SuffixIdentifier buildSuffixForIdentifier(final SQLParser.IdentifierContext idCtx) {
// Unquoted record attribute tokens (@rid, @type, @in, @out, @this)
if (idCtx.RID_ATTR() != null || idCtx.TYPE_ATTR() != null ||
idCtx.IN_ATTR() != null || idCtx.OUT_ATTR() != null ||
idCtx.THIS() != null) {
final RecordAttribute attr = new RecordAttribute(-1);
attr.setName(idCtx.getText());
return new SuffixIdentifier(attr);
}
// Quoted identifiers (e.g. `@rid`) or plain identifiers whose value matches a record attribute
final Identifier id = (Identifier) visit(idCtx);
if (isRecordAttributeName(id.getStringValue())) {
final RecordAttribute attr = new RecordAttribute(-1);
attr.setName(id.getStringValue());
return new SuffixIdentifier(attr);
}
return new SuffixIdentifier(id);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This method can be refactored to improve readability and eliminate code duplication. By determining the record attribute name first and then creating the RecordAttribute object in a single place, the logic becomes clearer and more maintainable.

  private SuffixIdentifier buildSuffixForIdentifier(final SQLParser.IdentifierContext idCtx) {
    String recordAttributeName = null;
    Identifier identifier = null;

    // Unquoted record attribute tokens (@rid, @type, @in, @out, @this)
    if (idCtx.RID_ATTR() != null || idCtx.TYPE_ATTR() != null ||
        idCtx.IN_ATTR() != null || idCtx.OUT_ATTR() != null ||
        idCtx.THIS() != null) {
      recordAttributeName = idCtx.getText();
    } else {
      // Quoted identifiers (e.g. `@rid`) or plain identifiers whose value matches a record attribute
      identifier = (Identifier) visit(idCtx);
      if (isRecordAttributeName(identifier.getStringValue())) {
        recordAttributeName = identifier.getStringValue();
      }
    }

    if (recordAttributeName != null) {
      final RecordAttribute attr = new RecordAttribute(-1);
      attr.setName(recordAttributeName);
      return new SuffixIdentifier(attr);
    }

    return new SuffixIdentifier(identifier);
  }

…erage

- Use buildSuffixForIdentifier for the first identifier in
  visitIdentifierChain too, so backtick-quoted `@rid` at chain start
  is handled consistently with the modifier position.
- Add @type and @cat test cases (the latter exercises the
  isRecordAttributeName string-matching fallback since @cat has no
  dedicated grammar token).
- Add DELETE cleanup before INSERT in each test for deterministic
  assertions (exact count instead of isNotEmpty).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Mar 3, 2026

Code Review

This is a clean, well-scoped fix for issue #3571. Here is my feedback.

What the fix does well

  • Root cause correctly identified: visitModifier() was always creating a plain Identifier node even when encountering record-attribute tokens like @rid, so .@rid on a function result fell through to document.get("@rid") and returned null. The fix routes through buildSuffixForIdentifier() which creates a RecordAttribute node instead.
  • Good extraction of the helper: buildSuffixForIdentifier() removes the duplicated attribute-detection block that existed in both visitModifier and visitIdentifierChain, keeping things DRY.
  • Test coverage is solid: Six scenarios covering unquoted/quoted variants, @rid, @type, and @cat, including both list access and indexed [0] access. The test class follows project conventions (TestHelper, assertThat, IF NOT EXISTS, etc.).

Issues / suggestions

1. equalsIgnoreCase in isRecordAttributeName — is case-insensitivity intentional?

The method uses equalsIgnoreCase, which means a backtick-quoted @RID would also be treated as a record attribute. The unquoted path (RID_ATTR, TYPE_ATTR, etc.) is driven by the lexer, which presumably already handles case. For the backtick-quoted path the original string is preserved, so equalsIgnoreCase could silently accept @RID as a record attribute. If that is intentional (for user-friendliness), it should be noted; if not, a plain equals is safer and more consistent with the token path.

2. Namespace-qualified function check still duplicates attribute detection

In visitIdentifierChain, the early-return guard for namespace-qualified calls still lists the grammar tokens explicitly:

firstIdCtx.RID_ATTR() == null && firstIdCtx.TYPE_ATTR() == null
    && firstIdCtx.IN_ATTR() == null && firstIdCtx.OUT_ATTR() == null && firstIdCtx.THIS() == null

This does not cover backtick-quoted variants. Since buildSuffixForIdentifier now centralises that logic, a small helper isRecordAttributeContext(IdentifierContext) that combines the token checks with the string check could make this guard consistent with the rest of the fix. In practice the scenario is unlikely, but the inconsistency is worth noting.

3. @props is included in isRecordAttributeName but has no test

PROPERTY_TYPES_PROPERTY (@props) is listed in the method but there is no test exercising it via dot notation on a function result. Not a blocker, but a test would confirm the code path works end-to-end.

4. Minor: @this as a suffix attribute

@this is recognised by isRecordAttributeName, and it also has a dedicated grammar token (THIS()), so the unquoted path already handles it. The backtick-quoted variant is now also handled. Worth confirming that RecordAttribute for @this in a suffix position (e.g. inE()[0].@this) produces the expected result (the record itself) rather than null.


Summary

The fix is correct and production-ready as-is. Points 1 and 2 are the most substantive; the others are minor gaps in coverage and consistency. Overall a well-handled bug fix with good test coverage.

@codacy-production
Copy link

codacy-production bot commented Mar 3, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-10.32% 96.30%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (687b972) 103053 77052 74.77%
Head commit (474698e) 133641 (+30588) 86135 (+9083) 64.45% (-10.32%)

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 (#3572) 27 26 96.30%

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

@robfrank robfrank merged commit 41c4254 into main Mar 3, 2026
21 of 23 checks passed
@robfrank robfrank linked an issue Mar 3, 2026 that may be closed by this pull request
@codecov
Copy link

codecov bot commented Mar 3, 2026

Codecov Report

❌ Patch coverage is 74.07407% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.30%. Comparing base (687b972) to head (474698e).
⚠️ Report is 40 commits behind head on main.

Files with missing lines Patch % Lines
...va/com/arcadedb/query/sql/antlr/SQLASTBuilder.java 74.07% 1 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3572      +/-   ##
==========================================
- Coverage   65.79%   65.30%   -0.49%     
==========================================
  Files        1506     1506              
  Lines      103053   103092      +39     
  Branches    21366    21381      +15     
==========================================
- Hits        67801    67329     -472     
- Misses      26030    26566     +536     
+ Partials     9222     9197      -25     

☔ 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.

robfrank added a commit that referenced this pull request Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL: accessing internal properties in a map

1 participant