Skip to content

Unwrap map() as the only step of a project().by() traversal #2464

Closed

Description

@danielcweber

🤖 This issue was created by Claude (Claude Code).

Is your feature request related to a problem? Please describe.

A by() modulator of project() whose traversal consists of nothing but a map(...) step is serialized as is:

.project('name','globalId')
  .by(__.map(__.values('Name').limit(1)))
  .by(__.map(__.id().limit(1)))

by() of project() only takes the first result of its traversal, for a single incoming traverser. map(t) also emits only the first result of t for that traverser, and filters it out if t yields nothing. Inside by(), __.map(t) and t therefore produce the same value, and the map step only adds length and one more step to evaluate per element.

This shape comes up naturally with helpers like "the first value of a property per element", written as Map(__ => __.Values(x => x.Name).Limit(1)). The map is needed when such a helper is used in the middle of a traversal, so that the limit(1) applies per element instead of to the whole stream. Once the helper's result ends up in a by(), the map is redundant.

Describe the solution you'd like

When serializing a ProjectStep.ByTraversalStep whose traversal consists of a single MapStep, Gremlinq emits the inner traversal of the MapStep directly:

.by(__.map(__.values('Name').limit(1)))   →   .by(__.values('Name').limit(1))

This should work at the step or serialization level, so that it also applies to traversals pushed via ConfigureSteps, not only to calls of the Map(...) query method.

Describe alternatives you've considered

  • Let callers decide: Callers could offer two variants of such helpers, one with map and one without, and pick the right one depending on where the result ends up. That leaks a serialization concern into every caller.
  • Other by() modulators: The same may hold for other by() modulators that take only the first result (e.g. order().by(), group().by(), select().by()). Each would need to be checked separately; project().by() is the case that matters most here.

Additional context

  • Measured on real queries: In a real-world GraphQL-to-Gremlin query of about 1400 lines, this pattern occurs 29 times.
  • Related: The trailing limit(1) inside such traversals is a separate issue (Drop a trailing limit(1) from map() and project().by() traversals #2465).
  • Test: A serialization test comparing project(...).by(__.map(t)) with project(...).by(t) for a few t would cover it.

Activity

  1. danielcweber commented on Sep 30, 2026

    @danielcweber
    ContributorAuthor

    Done in #2475, in the Project(...).By(...) operator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions