Visitar URL original
fix(#10442): Gremlin, GraphQL and MongoDB QL no longer answer nothing over a time-series type by robfrank · Pull Request #10468 · ArcadeData/arcadedb · GitHub
Skip to content

fix(#10442): Gremlin, GraphQL and MongoDB QL no longer answer nothing over a time-series type - #10468

Merged
robfrank merged 4 commits into
mainfrom
fix/10442-timeseries-gremlin-graphql-mongodb
Oct 20, 2026
Merged

robfrank merged 4 commits into
mainfrom
fix/10442-timeseries-gremlin-graphql-mongodb

Conversation

@robfrank

@robfrank robfrank commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #10442

#10440 found that Cypher silently matched nothing over a TIMESERIES type. This checks the other three engines the docs list for time-series data, and makes each one either serve the samples or fail with an error naming the type:

Engine Before After
Gremlin g.V().hasLabel('Ev').count() = 0, g.V().hasLabel('Ev') = no vertex fails: "Gremlin cannot match the label 'Ev', a TIMESERIES type ..." (same choice as Cypher: samples are neither vertices nor edges)
GraphQL, field returning the type already served the samples (reads with SQL SELECT) unchanged, now pinned by a test
GraphQL, @relationship(type: "Ev") empty list fails: "GraphQL @relationship cannot follow the type 'Ev', a TIMESERIES type ..."
MongoDB QL, unfiltered find() / mongo query language no document (bucket scan of a type with no bucket), while count said 3 the samples, read through SQL
MongoDB QL, filtered count 0 (rows without @rid skipped) the matching samples
MongoDB QL, filtered/sorted find, unfiltered count, countDocuments (aggregate) already served the samples unchanged, pinned by a test

Changes

  • gremlin/.../TimeSeriesLabelGuard.java (new), called first in ArcadeTraversalStrategy.apply() (before the rewrites take the label out of the has() step). It checks every has() label predicate asking FOR a label (eq, within, inside and/or) and every edge label of out/in/both(E) hops. TinkerPop applies the strategy to each child traversal too, so where(), filter() etc. are covered. Negations (neq, without, the body of not()) are answered, as Cypher answers WHERE NOT n:Ev. Cost: one schema hash lookup per label at compile time.
  • graphql/.../GraphQLResultSet.java: a @relationship naming a time-series type fails. GraphQLSchema gets a package-private getDatabase() for this.
  • mongodbw/.../MongoDBCollectionWrapper.java: the unfiltered scan is skipped for a time-series type (falls to the SQL path). A filtered count uses the new MongoFilter.scanMatchingRows(), which visits rows without identity. scanMatches() keeps its RID contract and delegates to it.

Test plan

  • GremlinTimeSeriesLabelIssue10442Test (19 cases, runs in gremlin-it): 15 fail with the guard call removed, all pass with it
  • GraphQLTimeSeriesIssue10442Test (3): the relationship case failed before the fix
  • MongoDBTimeSeriesIssue10442Test (5, real wire client): 4 failed before the fix
  • Regression: gremlin-it 654 tests, graphql 260, mongodbw 354, all green (MongoDBAuthorizationTest hit one client connect timeout in the full run and passed when rerun alone)

Coverage

Invariant: no query language answers an empty result over a TIMESERIES type that it cannot read. Each read either serves the samples or fails naming the type.

Entry point Status
Gremlin hasLabel eq / within / and-or, start or mid traversal, child traversals fixed + tested
Gremlin hop edge labels (out('Ev'), outE('Ev'), folded outE().hasLabel('Ev')) fixed + tested
Gremlin hasLabel('bucket:...') n/a: a time-series type has no bucket (checked in test, then dropped)
Gremlin addV('Ev') / addE('Ev') already fail ("is not a vertex"/"is not a edge")
GraphQL query field over the type, with/without arguments already served, tested
GraphQL @relationship naming the type fixed + tested
GraphQL @sql/@cypher/@gremlin directives delegated to those engines (#10440 / this PR)
Mongo find unfiltered / skip / limit, mongo query language fixed + tested
Mongo filtered/sorted find, raw count (all forms), countDocuments, estimatedDocumentCount count-filtered fixed, rest already served, all tested

Known gaps

Known limit, documented in the guard's Javadoc and pinned by a test: a pattern predicate on the label (TextP.startingWith('E')) names no type and is answered, not refused.

Adversarial pass

Not run: the session had no subagent tool to spawn the independent reviewer. The review cycles below stood in for it.

Review cycles

Cycle Head Changes Bot outcome
1 e059a7a initial fix LGTM pending CI. Applied: comment on unchecked predicates, negation under nested not(). Answered: remote ArcadeGraph verified (see Coverage)
2 c2a66e0 isNegated walks every ancestor not() (parity), tests for not(and(...)), not(where(...)), not(not(...)) non-blocking. Applied: Javadoc on pattern predicates, test clears a leftover database directory
3 9c2859d Javadoc + test setup non-blocking. Applied: tests pinning the TextP limit and the addE('Ev') refusal
4 1e6dc94 tests no blocking issues, nothing actionable left (see Deferred items)

Deferred items (skipped, with rationale)

  • "GraphQL requireNotTimeSeriesType runs a schema lookup per row; resolve it once per field or at SDL time" (cycles 1, 2, 3, 4): it is one hash lookup per resolved @relationship field. An SDL-time check cannot handle a type: given as a variable, and with no parent rows an empty answer is correct.
  • "Mongo update/delete on a time-series collection silently does nothing; fail or pin it with a test" (cycles 2, 4): tracked in mongodbw: a filtered update/delete over a TIMESERIES type silently matches nothing (#10442) #10464. A test pinning the silent no-op would lock in the defect.
  • "Share the instanceof TimeSeriesType check between count and find in a helper" (cycles 3, 4): there is only one inline check. count needs no type check, because scanMatchingRows handles rows both with and without identity.
  • "checkName should check instanceof String; document other negating steps; comment on when the graph is not an ArcadeGraph" (cycle 4): optional nits that do not change behavior.
  • "Confirm the server Gremlin endpoint hits the guard" (cycle 2): the strategy is registered statically on ArcadeGraph (ArcadeGraph.java L115), and the server's Gremlin endpoints run on ArcadeGraph.

Final state

clean-approval after 4 cycles. Merge is the developer's.

🤖 Generated with Claude Code

… over a time-series type

Gremlin refuses a hasLabel() or hop edge label naming a TIMESERIES type, a GraphQL
@relationship naming one fails, and the MongoDB find()/filtered count read the samples
through SQL instead of the empty bucket scan.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 689ca772-9196-464a-93cf-51b1a745620a
📥 Commits

Reviewing files that changed from the base of the PR and between b4a6cb6 and 1e6dc94.

📒 Files selected for processing: 9
  • graphql/src/main/java/com/arcadedb/graphql/schema/GraphQLResultSet.java
  • graphql/src/main/java/com/arcadedb/graphql/schema/GraphQLSchema.java
  • graphql/src/test/java/com/arcadedb/graphql/GraphQLTimeSeriesIssue10442Test.java
  • gremlin/src/main/java/com/arcadedb/gremlin/ArcadeTraversalStrategy.java
  • gremlin/src/main/java/com/arcadedb/gremlin/TimeSeriesLabelGuard.java
  • gremlin/src/test/java/com/arcadedb/gremlin/GremlinTimeSeriesLabelIssue10442Test.java
  • mongodbw/src/main/java/com/arcadedb/mongo/MongoDBCollectionWrapper.java
  • mongodbw/src/main/java/com/arcadedb/mongo/MongoFilter.java
  • mongodbw/src/test/java/com/arcadedb/mongo/MongoDBTimeSeriesIssue10442Test.java
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review of #10468

Overall this is a clean, well-scoped fix. The "serve the samples or fail naming the type" invariant is easy to reason about, the error messages are actionable, and the tests pin the before/after behavior across all three engines. I read the diff only and did not run the tests. Notes below, none blocking.

Gremlin (TimeSeriesLabelGuard)

  • Running the guard first in apply(), before the rewrites strip the label from has(), is the right place. Skipping the body of not() and only refusing eq/within (recursing through and/or) matches the Cypher semantics well.
  • The guard only runs when traversal.getGraph() is an ArcadeGraph. The PR description already lists the remote ArcadeGraph case as unverified. If the remote schema doesn't expose TimeSeriesType, that path silently returns the old empty result. A follow-up issue would be good so the gap doesn't get lost.
  • Some predicate shapes are still silent. A non-eq/within positive predicate such as P.test(...) or a custom BiPredicate is not checked. That is probably fine, but a one-line comment saying so would help the next reader.
  • The check does a schema lookup per label on every strategy application, including each child traversal. That is cheap, but a has() with a very large within() list means N lookups per compile. This is fine for realistic use.
  • getParent() instanceof NotStep only covers a direct not() parent. where(not(...)) goes through NotStep as well, so it is covered. A test for not(and(hasLabel('Ev'), ...)) would pin the nested case.

GraphQL

  • requireNotTimeSeriesType does a schema lookup on every @relationship evaluation, i.e. per row, in the result-set hot path. Resolving the type once per field, or caching the check, would avoid repeating it per row. The lookup is a hash get, so the impact is small, but the check is per row without needing to be.
  • getDatabase() is package-private, so the surface stays small.

MongoDB

Tests

  • Coverage is good, and I like that the PR records which cases fail without the fix. The parameterized Gremlin test is a nice way to cover the matrix.
  • Per CLAUDE.md, a test using a hardcoded DB path under ./target is fine. Please confirm that the Mongo test, which uses a real wire client, takes its port from the fixture rather than a fixed one (HardcodedTestServerPortsTest will flag it otherwise).
  • The PR notes one MongoDBAuthorizationTest connect timeout in the full run. It passed on rerun, but keep an eye on it in CI.

Style

  • Conventions match (final params, imports instead of FQNs, assertThat(...) style). No System.out left behind.

LGTM pending CI.

@codacy-production

codacy-production Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 31 complexity

Metric Results
Complexity 31

View in Codacy

🟢 Coverage 94.92% diff coverage · -4.93% coverage variation

Metric Results
Coverage variation ✅ -4.93% coverage variation
Diff coverage ✅ 94.92% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (b4a6cb6) 244964 212592 86.79%
Head commit (1e6dc94) 277721 (+32757) 227328 (+14736) 81.85% (-4.93%)

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 (#10468) 59 56 94.92%

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%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…ent the unchecked predicates

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review of #10468 (TIMESERIES handling in Gremlin, GraphQL, MongoDB QL)

Overall this is a solid, well-scoped change. I read the diff but did not build it or run the tests.

What looks good

Points to consider

  1. GraphQL, GraphQLResultSet: requireNotTimeSeriesType runs inside evaluateDirectives, which is evaluated per row. It costs one schema lookup per row per @relationship field. It is cheap, but it could be checked once per field (for example at schema build time, or cached on the field). That would also fail an empty result set consistently. Today, if no Station rows exist, the misconfiguration is never reported.
  2. Gremlin guard scope: the guard only fires when traversal.getGraph() is an ArcadeGraph. Please confirm that the SQL/gremlin() and remote (server) paths always hit that branch. The tests cover graph.gremlin(query) and graph.traversal(), but none go through the server's Gremlin endpoint.
  3. Gremlin predicates not covered: Compare.eq and Contains.within are handled, but a TextP or a custom predicate that matches the label (for example startingWith('E')) still silently returns nothing. This is probably acceptable, but it deserves a line in the Javadoc, which currently says only that "any other predicate ... names no label it asks for".
  4. MongoDB, MongoDBCollectionWrapper: the getTypeOrNull(collectionName) instanceof TimeSeriesType check is now on the hot path of every unfiltered find. It is a hash lookup, so this is fine. MongoFilter.scanMatches now silently skips rows with no identity, as before, so update/delete paths cannot touch samples. A test that deleteMany or updateMany on a time-series collection is a no-op or an error would pin that.
  5. Minor: the GraphQL test uses ./target/testgraphql-timeseries-10442 and the Gremlin test uses ./target/testgremlin-timeseries-10442. Both clean up in tearDown, but they should also guard against a leftover directory from a crashed run, as the Gremlin setUp doesn't. The GraphQL test already does this with factory.exists().

None of this blocks the merge. Nice work.

…tover test database

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review of #10468 (TIMESERIES types in Gremlin, GraphQL and MongoDB QL)

I read the diff but did not build the PR or run its tests. The approach is consistent across the three engines: each one either serves the samples or fails with an error naming the type. The new tests pin both the new behavior and the paths that already worked.

What looks good

  • TimeSeriesLabelGuard runs first in ArcadeTraversalStrategy.apply(), before the rewrites remove the label from the has() step. The comment explains why.
  • Negations (neq, without, not()) are deliberately answered, which matches Cypher's WHERE NOT n:Ev. The isNegated walk up the parent chain handles nested not() correctly.
  • The cost is one schema hash lookup per label at compile time, which is acceptable.
  • In Mongo, scanMatches is kept as a thin wrapper over the new scanMatchingRows. Callers that write (update/delete) still skip rows with no identity, so a time-series sample can't be targeted by a mutation.

Points to consider

  1. Gremlin guard coverage gaps.
    • TextP.startingWith and custom predicates are not refused. The Javadoc admits this, so it is a documented limit. A test that pins it would stop a later change from regressing it silently.
    • The guard only covers read-side has() and hop labels. addV('Ev') and addE('Ev') are not covered. That may be intended, since writes to a time-series type through Gremlin look out of scope here, but it is worth confirming.
  2. Guard cost on every traversal. check() runs for the root and every child traversal. It is cheap, but ArcadeGraph arcadeGraph is only pattern-matched on the root's graph. Please confirm that child traversals always have getGraph() set when the strategy is applied. If one doesn't, its labels silently skip the guard. A test with where(__.out('Ev')) already seems to cover this, so I'd just double-check it is in the suite.
  3. Mongo queryDocuments adds a schema lookup per unfiltered find. getTypeOrNull(collectionName) is a hash lookup, so this is fine. The check could move into MongoFilter or a small helper, because count() and queryDocuments now both reason about time-series types separately.
  4. scanMatchingRows now passes rows without an identity. count() with skip/limit uses it correctly. If a future caller uses it for anything that needs an RID, it will need to handle the empty identity. Mentioning that in the Javadoc would help. It partly does already.
  5. GraphQL getDatabase() is added to GraphQLSchema only so GraphQLResultSet can reach the schema. That's fine at package-private scope. Alternatively, requireNotTimeSeriesType could run once when the SDL is parsed, if the @relationship type is known then. That would fail fast at schema time instead of on every resolution, although it would require the type to exist when the SDL is submitted.
  6. Tests.
    • They look solid and follow the repo style (assertThat(...), no fixed ports, ports derived through the base fixture).
    • The GraphQL tests use a fixed ./target/... DB path. That matches other graphql tests, so it's OK.
    • Please check that none of them rely on wall-clock timing. I didn't see any.

Style

  • CLAUDE.md conventions are followed: final is used, imports are explicit, and there are no debug System.out calls.
  • The instanceof pattern matching is idiomatic for this codebase.

No blocking issues found. The Gremlin coverage gaps in item 1 are the main thing I'd want settled or documented before merge.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review

Overall this is a well-scoped fix. It follows the Cypher choice from #10440 (fail loudly, or serve the samples through SQL), and the tests cover the before/after cases. I only read the diff and did not run the tests. Notes below, none blocking.

Gremlin (TimeSeriesLabelGuard)

  • The guard runs before the rewrites that remove the label from the has() step, and the cost is a single schema hash lookup per label at compile time. Both are good.
  • checkName calls name.toString() on whatever eq or within holds. A non-String label such as a T token or a number just misses the lookup, which is harmless. An early instanceof String check would make that explicit.
  • isNegated walks parents counting NotStep. Other steps that negate or invert (for example none()-style steps, or where(P.not(...))) are not modelled. The Javadoc already records the pattern-predicate limit, so adding this one to the "known limits" note would be enough.
  • The guard is skipped when the graph is not an ArcadeGraph (the instanceof guard). That is reasonable, but a short comment on when that happens would help.

GraphQL

  • requireNotTimeSeriesType does a schema lookup each time a @relationship field is resolved. If that happens per parent row, resolve it once per field, or cache it in the result set. It is cheap, but it sits on a hot path.
  • Adding the package-private GraphQLSchema.getDatabase() accessor is fine. CommandSemanticException is consistent with the other engines.

MongoDB

  • scanMatches now silently skips rows without an identity. For update and delete on a TIMESERIES collection this means a no-op that reports success, which is the same class of silent-empty answer this PR removes elsewhere. Consider failing explicitly for writes on a time-series collection, or pin the current behaviour with a test.
  • queryDocuments repeats the getTypeOrNull(...) instanceof TimeSeriesType check inline. A small helper would let count and find share it and keep them from drifting.
  • Moving count to scanMatchingRows is a good change. It removes the old inconsistency where the unfiltered count saw 3 and the filtered count saw 0.

Tests

  • Coverage is good, including the negation and nested-not() cases and the "graph labels unaffected" regression guard.
  • There are no tests for Mongo update/delete on a time-series collection (see above).
  • Tests use fixed relative DB paths with manual cleanup. That matches the existing style and does not bind any ports, so it is fine.

Style (CLAUDE.md)

  • The code adheres to the conventions: single-statement if without braces, final parameters, assertj assertThat style, and no new dependencies.

Nice work.

@robfrank
robfrank merged commit 66e6cad into main Oct 20, 2026
30 of 32 checks passed
@robfrank
robfrank deleted the fix/10442-timeseries-gremlin-graphql-mongodb branch October 20, 2026 17:17
@codecov

codecov Bot commented Oct 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.05085% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.68%. Comparing base (b4a6cb6) to head (1e6dc94).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...ava/com/arcadedb/gremlin/TimeSeriesLabelGuard.java 82.60% 3 Missing and 5 partials ⚠️
.../com/arcadedb/graphql/schema/GraphQLResultSet.java 80.00% 0 Missing and 1 partial ⚠️
.../src/main/java/com/arcadedb/mongo/MongoFilter.java 66.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##               main   #10468   +/-   ##
=========================================
  Coverage     78.67%   78.68%           
+ Complexity     4733     4731    -2     
=========================================
  Files          2278     2279    +1     
  Lines        244964   245020   +56     
  Branches      53204    53222   +18     
=========================================
+ Hits         192720   192783   +63     
+ Misses        32413    32412    -1     
+ Partials      19831    19825    -6     

☔ View full report in Codecov by Harness.
📢 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 Nov 5, 2026
… over a time-series type (#10468)

(cherry picked from commit 66e6cad)
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.

Time-series types: check Gremlin, GraphQL and MongoDB QL for the silent empty result Cypher had (#10440)

1 participant