apache / apache/age

Cypher keyword-named aliases cannot be referenced back as variables

Open
#2,416 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
C
Stars
4.8k
Forks
523
Avg merge
1d 2h
Merged PRs (30d)
9

Description

# Followup: keyword-named aliases cannot be referenced back

After #2415 lands, `safe_keywords` (e.g. `count`, `exists`, `coalesce`,
`match`, `where`, `order`, `limit`, `distinct`, ...) are accepted as
**aliases** in `RETURN`/`WITH`/`YIELD`/`UNWIND ... AS `. However,
once such an alias is bound, it still cannot be **referenced** as a
variable later in the same query.

## Reproducer (against the branch from #2415)

```sql
SELECT * FROM cypher('g', $$ WITH 1 AS count RETURN count $$) AS (a agtype);
-- ERROR: syntax error at end of input

SELECT * FROM cypher('g', $$ UNWIND [1,2,3] AS count RETURN count $$) AS (a agtype);
-- ERROR: syntax error at end of input

SELECT * FROM cypher('g', $$ WITH 5 AS limit RETURN limit + 1 AS x $$) AS (a agtype);
-- ERROR: syntax error at or near "+"
```

The alias *binds* successfully (so projecting any literal under
`AS count` works), but the parser cannot reduce a bare `COUNT` token to
an `expr_var`, so the second mention of `count` fails.

## Root cause

`expr_var: var_name` reads the variable through `var_name`, which still
accepts only `symbolic_name` (i.e. `IDENTIFIER`). #2415 deliberately
broadens only the alias-binding path (`var_name_alias`) because
broadening `var_name` itself reintroduces ~156 shift/reduce conflicts
in bison (keyword tokens collide with their syntactic roles inside
expressions and patterns).

A complete fix likely requires either:

1. A scoped grammar refactor that resolves the conflicts (probably with
precedence directives or a hand-written lookahead helper), or
2. Lexer-level handling that re-tags an already-bound alias name as
`IDENTIFIER` for the rest of the query (rejected by Apache AGE
before — context-sensitive lexing), or
3. A semantic-analysis pass that rewrites `expr_var` references to
safe_keywords before parsing reaches the conflict (intrusive).

## Workaround today

Use a plain identifier when the alias needs to be referenced:

```sql
SELECT * FROM cypher('g',
$$ WITH 1 AS cnt RETURN cnt $$
) AS (a agtype);
```

or backtick-quote the alias if the keyword form is desired downstream:

```sql
SELECT * FROM cypher('g',
$$ WITH 1 AS `count` RETURN `count` $$
) AS (a agtype);
```

## Related

- #2355 — original report (alias binding, fixed in #2415).
- #2415 — alias-binding fix; this issue tracks the remaining
read-back asymmetry.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.