Skip to content

fix(crud): emit count() and math:: aggregates SurrealDB can parse - #81

Merged
albedosehen merged 1 commit into
mainfrom
fix/surrealql-aggregates
Oct 3, 2026
Merged

albedosehen merged 1 commit into
mainfrom
fix/surrealql-aggregates

Conversation

@albedosehen

@albedosehen albedosehen commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

The query builder's aggregate methods rendered SQL-style calls that SurrealDB rejects at parse time, so any query built with them failed before it ran.

Fixed

count(), sum(), avg(), min(), and max() on ReadQL (what client.query(...) returns) and on AggregationQueryBuilder rendered COUNT(*), SUM(f), AVG(f), MIN(f), and MAX(f). Against SurrealDB 3.3.0:

  • COUNT(*) fails with Parse error: Unexpected token `*`, expected an expression.
  • SUM(f) / AVG(f) / MIN(f) / MAX(f) fail with Parse error: Invalid function/constant path.

They now render the SurrealQL forms:

Method Before After
count() / count('*') COUNT(*) as count count() as count
count('f') COUNT(f) as count count(f) as count
sum('f') SUM(f) as sum_f math::sum(f) as sum_f
avg('f') AVG(f) as avg_f math::mean(f) as avg_f
min('f') MIN(f) as min_f math::min(f) as min_f
max('f') MAX(f) as max_f math::max(f) as max_f

Aliases are unchanged. count('*') was the old default and is still accepted; it renders count(). count(field) counts the rows where that field is truthy.

The aggregate calls inside the having(...) examples (JSDoc in read.ts / having.ts, docs/queries.md, docs/examples/index.md, examples/basicCrud.ts, examples/userManagement.ts) are corrected the same way. The immutable Query expression helpers (count, mathSum, sum_, avg, ...) already rendered the SurrealQL forms and are unchanged. Historical changelog entries are left as they were.

Tests

  • Existing tests that asserted the old strings (capabilities_extended.test.ts, the HAVING strings in query.test.ts) are updated.
  • New unit tests in read.test.ts pin the full statement ReadQL sends for count(), count('*'), count(field), the four math:: aggregates, and a nested field; a guard in capabilities_extended.test.ts fails if any SQL-style aggregate reappears.
  • New live test in integration_client.test.ts (run by the integration workflow) executes groupBy().count().sum().avg().min().max() and count(field) and checks the computed values. Run against the unfixed code it fails with the Unexpected token `*` parse error; with the fix it passes.

Verification

  • deno fmt --check, deno lint, deno check mod.ts, deno task check: clean.
  • Unit tests, selected as test.yml does: 232 passed (1901 steps), 0 failed. Baseline on main: 232 passed (1893 steps).
  • deno test -A src/test/integration_client.test.ts against SurrealDB 3.3.0: 1 passed (27 steps), 0 failed.
  • Every statement the new unit tests pin was also executed directly against SurrealDB 3.3.0 under GROUP BY, plus count() / math::sum() under GROUP ALL.

Not in this change

Found while verifying, left for a separate change because each alters behaviour beyond the aggregate spelling:

  • SurrealQL has no HAVING clause. having(...) appends HAVING ..., which SurrealDB 3.3.0 rejects (Unexpected token `an identifier`, expected Eof) whatever the condition. The equivalent is a filter over a grouped subquery: SELECT * FROM (SELECT ... GROUP BY ...) WHERE count > 5.
  • Aggregates without groupBy() emit no GROUP clause, so they run per row: count() returns one { count: 1 } per record and math::sum(f) errors on a scalar. GROUP ALL is the whole-table form.
  • Grouped rows have no id, and the default no-mapper path calls id.toString(), so an aggregate query without .map(...) fails with Record mapping failed!. The new integration test passes a mapper for this reason.

count(), sum(), avg(), min() and max() on ReadQL and AggregationQueryBuilder
rendered COUNT(*), SUM(f), AVG(f), MIN(f) and MAX(f). SurrealQL has none of
these: COUNT(*) is a parse error on the `*`, and SUM/AVG/MIN/MAX are not
function names, so every query that used one of these methods failed before
it ran.

A row count now renders count(), a field count count(field), and the others
math::sum(f), math::mean(f), math::min(f) and math::max(f). Aliases are
unchanged, and count('*') is still accepted and renders count().

The aggregate calls in the having(...) examples (JSDoc, docs/queries.md,
docs/examples/index.md, examples/) are corrected the same way. Unit tests pin
the new rendering, and a live test in integration_client executes the
aggregates against SurrealDB.
@albedosehen
albedosehen merged commit e639c60 into main Oct 3, 2026
13 checks passed
@albedosehen albedosehen mentioned this pull request Oct 3, 2026
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.

1 participant