Skip to content

Group an aggregating item over a property key by the property, and refuse a key of another shape there, as drivine4j does, at Cypher spec v0.14.0 - #315

Merged
jasperblues merged 1 commit into
mainfrom
agg-key-expr
Oct 31, 2026
Merged

jasperblues merged 1 commit into
mainfrom
agg-key-expr

Conversation

@jasperblues

Copy link
Copy Markdown
Member

Ports drivine4j's #491, merged as liberation-data/drivine4j#496, and syncs the Cypher spec from v0.13.0 to v0.14.0 (liberation-data/drivine-cypher-spec#39).

Fixes #311

A key inside an item that aggregates (Neo4j's 42I18)

_require_explicit_grouping now reads a key inside an aggregating item, beside its aggregates and in a body, only where the key is a variable or a variable's property (_is_plain_key). In a pattern's parts, only a variable counts. So these are refused, as Neo4j refuses them:

_named_in_bodies is gone: _named_outside reads the whole item. The ORDER BY check is unchanged. It still reads a key of any shape, as Neo4j does.

Grouped by the property, not its variable

_aggregates_first also takes an aggregating item that reads a property key. It works the item out over the keys' columns, so r.role + count(*) beside r.role gives one row for each role, not one for each relationship. A NULL key is one group. Two property keys of one variable group by both.

Neo4j has a quirk here, and the spec pins it. With the key r.role:

  • a list comprehension's WHERE reads the key, so the rows are grouped by role;
  • a comprehension's projection, a quantifier's WHERE and a reduce step group by r.

rekeyed follows this: it replaces the key by its column in a comprehension's WHERE and leaves the other bodies as written, so r stays beside.

Reader

An unaliased map projection item is named after its variable, as Neo4j names it. So RETURN p {.name}, count(*) ORDER BY p.name and WITH p {.name}, count(*) AS c RETURN p.name compile.

An order term written as an item, in another case

#310 compared keys with _grouping_key in _require_explicit_grouping and _aggregates_first. An order term that doesn't aggregate never reaches _aggregates_first, so RETURN TOUPPER(p.name) AS u, count(*) AS c ORDER BY toupper(p.name) (a spec v0.14.0 step) failed with "p is not defined".

_project now does what drivine4j's project does: before anything else, it rewrites an order term, or a part of one, written as an item apart from the case of its function names, to that item. _grouping_key now also lowercases function names in a comprehension's, a quantifier's or a reduce's body, as drivine4j's keyOf does.

Tests

  • The spec's new steps in "Implicit grouping key" (queries.yaml) and "Implicit grouping key in a body or an ORDER BY" (refusals.yaml). They run on Neo4j, Postgres and Oracle.
  • Ported from drivine4j:
    • CompilerRefusalTest "an aggregating item reads a key that is a variable or a variable's property…" (unit);
    • CypherQueryContract "an aggregating item over a key that is a property groups by the property…" (integration, async and sync);
    • the CypherReaderTest names.

The golden statements are unchanged.

…fuse a key of another shape there, as drivine4j does, at Cypher spec v0.14.0

Ports drivine4j's #491 (merged as liberation-data/drivine4j#496), with the spec's
liberation-data/drivine-cypher-spec#39.

- _require_explicit_grouping reads a key inside an item that aggregates, beside its aggregates and
  in a body, only where it is a variable or a variable's property (_is_plain_key); in a pattern's
  parts only a variable. toUpper(p.name), x.a.b, x['a'] or x.a + 1 read there is refused (42I18).
  _named_in_bodies goes: _named_outside reads the whole item.
- _aggregates_first takes an item that aggregates and reads a property key, and works it out over
  the keys' columns, so r.role + count(*) beside r.role groups per role. A list comprehension's
  WHERE reads the key by its name; its projection, a quantifier's WHERE and a reduce's step keep the
  key's variable beside, as Neo4j groups by it there.
- The reader names an unaliased map projection after its variable.
- _project reads an order term written as an item, apart from its functions' names' case, as that
  item, so ORDER BY toupper(p.name) after TOUPPER(p.name) AS u compiles; _grouping_key lowers a
  function's name in a comprehension's, a quantifier's or a reduce's body too, as keyOf does.
- drivine4j's CompilerRefusalTest, CypherQueryContract and CypherReaderTest cases are ported.

Fixes #311
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.

Compiled Cypher: an aggregating item over a key that is a variable's property is refused, where Neo4j groups by the property (port of drivine4j#491)

1 participant