Skip to content

feat(schema)!: broadcast semantics for mixed select and context-dependent orderBy expressions - #60

Merged
kudima03 merged 4 commits into
feat/grouped-select-single-valuefrom
docs/select-broadcast-semantics
Sep 24, 2026
Merged

kudima03 merged 4 commits into
feat/grouped-select-single-valuefrom
docs/select-broadcast-semantics

Conversation

@kudima03

@kudima03 kudima03 commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #52 together with #59. Stacked on #59: this PR targets feat/grouped-select-single-value. Once #59 merges, it retargets to main.

1. Broadcast semantics for a mixed select without groupBy (docs only)

#52 flagged select: [field, sum(...)] without groupBy as a query whose result is undefined. This PR doesn't reject it; it gives it a defined meaning, using the broadcast model that each* operators already follow:

  • At least one array-returning item (a field or an each* column): N rows, where N comes from from + joins + where. Every single-value item is repeated on each row. If N = 0, the result is empty.
  • Only single-value items: one row (samples 06 and 09).
  • Aggregates see all N rows: they're computed before distinct and pagination.

New sample 22_select_broadcast.json: order fields plus sum(total_amount) as grand_total and eachDivide(total_amount, sum(total_amount)) as share_of_total.

2. orderBy keys follow the query context (breaking)

orderBy sorts result rows, so its key has to produce one value per result row. Before this PR it only accepted fields, which allowed ambiguous grouped sorts (a field has no single value per group) and gave no way to sort by a computed value.

Now every item is one shape, orderByItem = { expression, direction }, and the allowed kind of expression follows the context. This is the same split as where / having and as select with and without groupBy:

Query A result row is expression must be Enforced by
without groupBy a row arrayReturning: field, each* (single-value is allowed but has no effect) no restriction
with groupBy a group singleValueReturning: aggregate, arithmetic over aggregates root dependentSchemas

In LINQ terms: items.OrderByDescending(i => i.UnitPrice * i.Quantity) and .GroupBy(o => o.UserId).OrderByDescending(g => g.Sum(o => o.Total) / g.Count()).

  • A field is simply an array-returning expression; the { field } form is gone.
  • There are no alias references. To sort by a computed select column, you repeat the expression, which keeps validation strict.
  • To sort groups by a key, wrap it in an aggregate (min_string(users.name)) until Add order-sensitive aggregates firstElement / lastElement with per-aggregate orderBy #61 adds firstElement.
  • Sample 12 sorts by sum(total_amount) desc, then min_string(users.name) asc.
  • New sample 23_order_by_computed.json: order items sorted by eachMultiply(unit_price, quantity) desc, then id.

Verification

All 23 samples validate. Scenario checks for orderBy:

Scenario Result
grouped, aggregate valid
grouped, divide(sum, count) valid
grouped, field invalid
grouped, eachMultiply invalid
ungrouped, field valid
ungrouped, eachMultiply valid
ungrouped, aggregate (constant, has no effect) valid
ungrouped, scalar (constant, has no effect) valid
old { field } form invalid
invalid direction invalid

Breaking change

orderBy items use { "expression": X } in place of { "field": X }, in every query. Grouped queries must sort by a single-value expression. The release needs a major bump.

Follow-up

Order-sensitive aggregates (firstElement / lastElement, each with its own orderBy) are tracked in a separate issue. firstElement(key) would also let groups be sorted by keys of any type, including uuid and boolean.

🤖 Generated with Claude Code

@kudima03
kudima03 force-pushed the docs/select-broadcast-semantics branch from a613f19 to e196dd5 Compare September 24, 2026 13:24
@kudima03 kudima03 changed the title docs(spec): define broadcast semantics for mixed select feat(schema)!: broadcast semantics for mixed select and expression-based grouped orderBy Sep 24, 2026
@kudima03
kudima03 changed the base branch from main to feat/grouped-select-single-value September 24, 2026 13:24
kudima03 and others added 2 commits September 24, 2026 13:45
Without groupBy, a select that mixes array-returning items (fields,
each* columns) with single-value items (aggregates, scalars) now has a
defined result: N rows, with each single-value item repeated on every
row. A select of only single-value items yields one row. Aggregates
are computed over all N rows before distinct and pagination.

Add sample 22 (grand total and share-of-total next to order fields)
and document the rule in README, CLAUDE.md and CHANGELOG.

Closes #52

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add expressionOrderByItem ({ expression: singleValueReturning,
direction }). With groupBy, orderBy items must be expressionOrderByItem
(root dependentSchemas); without groupBy they must be orderByItem
(root anyOf). A group has no single value for a field, so sorting
groups by a field is rejected; sort by an aggregate instead, and wrap
group keys in an aggregate such as min_string.

Migrate sample 12 to sort by sum(total_amount) desc, then
min_string(users.name) asc, and document the rule in README,
CLAUDE.md and CHANGELOG.

BREAKING CHANGE: grouped queries that sort by a field no longer
validate; use an aggregate expression instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kudima03
kudima03 force-pushed the docs/select-broadcast-semantics branch from e196dd5 to 1583d6e Compare September 24, 2026 13:45
Replace the { field } / { expression } split with a single orderByItem
{ expression, direction }. The allowed expression kind follows the
query context, the same split as where/having and grouped select:
array-returning without groupBy (fields and each* computations),
single-value with groupBy (aggregates and arithmetic over them).
Drop expressionOrderByItem.

This makes sorting by computed values possible in both contexts. Add
sample 23 (order items sorted by eachMultiply(unit_price, quantity))
and update README, CLAUDE.md and CHANGELOG.

BREAKING CHANGE: orderBy items use { "expression": X } in place of
{ "field": X }.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kudima03 kudima03 changed the title feat(schema)!: broadcast semantics for mixed select and expression-based grouped orderBy feat(schema)!: broadcast semantics for mixed select and context-dependent orderBy expressions Sep 24, 2026
Drop the root anyOf that rejected single-value orderBy expressions in
ungrouped queries. Such keys are constants and leave the order
unchanged, like OrderBy(r => 1) in LINQ, so rejecting them added schema
complexity without catching an ambiguous query. Grouped queries still
require single-value keys via dependentSchemas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kudima03
kudima03 added this pull request to stack #62 September 24, 2026 14:54
@kudima03
kudima03 merged commit fb8efc2 into main Sep 24, 2026
1 check passed
@kudima03
kudima03 deleted the docs/select-broadcast-semantics branch September 24, 2026 14:55
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.

Prevent mixing aggregate and non-aggregate select without groupBy at schema level

1 participant