Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ Versioning follows Semantic Versioning with preview suffix `major.minor.patch-pr

---

## [Unreleased]

### ⚠️ Breaking Changes

- **`having` now requires `groupBy`**: Queries that filter with `having` must also group their rows with `groupBy`. Previously the schema accepted `having` on its own, even though it has no meaning without groups. (#40)
- **`groupBy` can no longer be empty**: `groupBy` must list at least one field. To skip grouping, leave the clause out.

---

## [0.1.0-preview.0.5.0] - 2026-05-25

Introduces sort direction control for `orderBy`. Previously `orderBy` accepted bare field references, making sort direction implementation-defined. Queries that used bare fields in `orderBy` must be migrated to the new `orderByItem` wrapper.
Expand Down
6 changes: 6 additions & 0 deletions PureQL-Specification.json
Original file line number Diff line number Diff line change
Expand Up @@ -3238,6 +3238,7 @@
},
"groupBy": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/field"
}
Expand All @@ -3259,6 +3260,11 @@
"from",
"select"
],
"dependentRequired": {
"having": [
"groupBy"
]
},
"title": "PureQL specification",
"type": "object"
}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ PureQL is a JSON-based declarative query language for relational data. Queries a
| `select` | yes | Array of expressions to return |
| `where` | no | Boolean filter applied before grouping |
| `joins` | no | Array of join clauses |
| `groupBy` | no | Fields to group rows by |
| `having` | no | Boolean filter applied after grouping |
| `groupBy` | no | Fields to group rows by (at least one) |
| `having` | no | Boolean filter applied after grouping; requires `groupBy` |
| `orderBy` | no | Fields to order results by |
| `pagination` | no | `skip` and `take` for paging |
| `distinct` | no | When `true`, deduplicate result rows (default: `false`) |
Expand Down Expand Up @@ -104,7 +104,7 @@ Each item in `select` is a value-returning expression (field, scalar, aggregate,
They accept different shapes because they evaluate in different scopes:

- **`where`** is evaluated per row. It accepts either a **boolean-returning** expression (single boolean) or a **boolean-array-returning** expression (per-row boolean column). Use the `each*` family for per-row predicates against fields.
- **`having`** is evaluated per group, after `groupBy`. It accepts only a **boolean-returning** expression. Operands must reduce to a single value per group — typically aggregates compared with `greaterThan` / `equal` / etc. Per-row `each*` operators do **not** fit in `having`.
- **`having`** is evaluated per group, after `groupBy`, and requires a non-empty `groupBy` to be present. It accepts only a **boolean-returning** expression. Operands must reduce to a single value per group — typically aggregates compared with `greaterThan` / `equal` / etc. Per-row `each*` operators do **not** fit in `having`.

`where` example using per-row predicates:

Expand Down
Loading