Skip to content

Graph DSL silently accepts an unrecognized DIRECTION token, and misreports a missing IN <collection> #237

Description

@emanzx

Version / build tested against

v0.5.0 (live repro against the released image). Code re-verified unchanged at origin/main @ 461c3adb — neither graph_parse/helpers.rs nor graph_parse/variants.rs was touched between the tag and that head.

Deployment mode

Origin — single node (local)

Engine(s) involved

Graph (overlay)

Summary

The graph DSL does not validate two of GRAPH TRAVERSE's arguments, in opposite directions. An unrecognized DIRECTION token is accepted silently and falls through to out, so a traversal returns a confidently wrong edge set with no error. Conversely, omitting the IN <collection> clause that 0.5.0 made mandatory aborts the whole graph parse, so the statement falls through to the general SQL parser and the user is told Expected: an SQL statement, found: GRAPH rather than which clause is missing. Both are argument-handling defects in the same module, and the first produces silently-wrong results.

Steps to reproduce

CREATE COLLECTION g (id TEXT PRIMARY KEY);
GRAPH INSERT EDGE IN g FROM 'a' TO 'b' TYPE 'knows' PROPERTIES '{}';

-- One directed edge a -> b. Traversing FROM 'b', only an inbound walk
-- can reach 'a'. That makes the direction argument observable.

GRAPH TRAVERSE IN g FROM 'b' DEPTH 1 DIRECTION IN;
-- reaches 'a'   -- correct

GRAPH TRAVERSE IN g FROM 'b' DEPTH 1 DIRECTION INBOUND;
-- does NOT reach 'a', no error   <- WRONG, silently treated as `out`

GRAPH TRAVERSE IN g FROM 'b' DEPTH 1 DIRECTION BANANA;
-- does NOT reach 'a', no error   <- WRONG, arbitrary token accepted

-- Second symptom: omit the required IN clause.
GRAPH TRAVERSE FROM 'b' DEPTH 1;
-- ERROR 42601: parse error: sql parser error:
--   Expected: an SQL statement, found: GRAPH at Line: 1, Column: 1

Expected behavior

  1. A DIRECTION token outside in / out / both is rejected with a parse error naming the offending value. A traversal never silently substitutes a direction the caller did not ask for.
  2. Omitting a required clause reports the missing clause — e.g. GRAPH TRAVERSE requires IN <collection> — rather than a message stating the input is not an SQL statement at all.

Actual behavior

1. Unrecognized DIRECTION is silently coerced to out.

graph_parse/helpers.rs#L104-L114:

pub(super) fn direction_after(toks: &[Tok<'_>]) -> GraphDirection {
    match word_after(toks, "DIRECTION")
        .as_deref()
        .map(str::to_ascii_uppercase)
        .as_deref()
    {
        Some("IN") => GraphDirection::In,
        Some("BOTH") => GraphDirection::Both,
        _ => GraphDirection::Out,
    }
}

The _ arm absorbs every unrecognized token along with the absent-clause case, and the function returns GraphDirection rather than Option/Result, so a caller cannot distinguish "defaulted" from "garbage". The return is used unvalidated by parse_traverse, parse_neighbors and parse_path
(graph_parse/variants.rs#L52-L90).

Defaulting an absent clause to out is reasonable and is not what this reports; accepting DIRECTION BANANA as out is. The measured table from the repro above:

clause reaches a
DIRECTION IN yes — correct
DIRECTION INBOUND no — silently out
DIRECTION BANANA no — silently out
(clause omitted) no — documented default

2. A missing required IN <collection> is reported as "not an SQL statement".

graph_parse/variants.rs#L52-L57:

pub(super) fn parse_traverse(toks: &[Tok<'_>]) -> Option<NodedbStatement> {
    let collection = quoted_after(toks, "IN")?;

The ? returns None, try_parse therefore reports no graph statement, and the input falls through to the general SQL parser, which cannot know GRAPH was ever valid. Since IN <collection> became mandatory in 0.5.0, this is the error every caller upgrading from 0.4.0 meets first, and it points away from the actual cause. parse_neighbors and parse_path share the shape.

Same class as two previously-closed reports, in a different handler and still present at 461c3adb: #89 (DSL handlers text-parsing their arguments and silently coercing) and #213 (CREATE VECTOR INDEX ignoring unrecognized option tokens).

What actually happened? (check all that are true)

  • Acknowledged/committed data was lost, corrupted, or silently wrong
  • The server crashed, hung, or failed to start
  • A security or isolation boundary was crossed
  • Core functionality is broken with no acceptable workaround
  • A workaround exists (rewrite the query, avoid one path, etc.)

Proposed severity

SEV-3 — Medium: feature wrong, but operational and a workaround exists

Valid tokens behave correctly and the workaround is to spell the token in the server's own vocabulary, which is why this is proposed as SEV-3 rather than SEV-2. The counter-argument is on the record for triage: the failure mode is silently-wrong rows rather than an error, which the severity dropdown lists under SEV-2.

Reproducibility

Always — every attempt

Last known-good version / commit (if a regression)

Not a regression — direction_after behaves identically at v0.4.0. The second symptom is new in v0.5.0 only because IN <collection> became required there.

Environment & logs

Linux x86_64, Docker, released image farhansyah/nodedb:0.5.0, single node, default configuration apart from a loopback NODEDB_HOST. Observed over pgwire. No server-side log line is emitted for either case.

Before submitting

  • I searched existing issues and this is not a duplicate.
  • I reproduced this on a released tag or a current main build (not a stale local branch).
  • This is not a security vulnerability (those go to a private advisory).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sqlParser, planner, SQL semanticsengine:graphGraph overlaypriority:P2Scheduled, not urgentsev:3-mediumFeature wrong, but operational and a workaround existsstatus:needs-triageAwaiting maintainer triage (severity + priority)type:bugA defect — broken, incorrect, or lost data

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions