Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
6eebc58
feat(joins): join on a to-many relationship attribute
abnegate Oct 13, 2026
5f27c00
test(joins): cover relationship joins and the aggregate join read rule
abnegate Oct 13, 2026
03b3814
feat(arithmetic): add exact rounding and decimal reading for integer …
abnegate Oct 13, 2026
374ae2e
feat(adapter): declare engine arithmetic and resolve fractional operands
abnegate Oct 13, 2026
f5c1f2a
feat(database): add placeholder replacement and grant check toggles
abnegate Oct 13, 2026
5da2986
feat(relationships): prepare nested documents before a write
abnegate Oct 13, 2026
46bcd86
test(relationships): cover the nested pre-pass in the staging pre-checks
abnegate Oct 13, 2026
c9c921b
test(relationships): cover nested preparation on every relationship a…
abnegate Oct 13, 2026
c0d3a33
refactor(relationships): narrow the nested pre-pass array conversion
abnegate Oct 13, 2026
e5cbc14
test(relationships): type the nested preparation test helpers
abnegate Oct 13, 2026
b0c9598
chore(merge): integrate ws-db-810-arithmetic
abnegate Oct 13, 2026
08d3597
chore(merge): integrate ws-db-810-nested
abnegate Oct 13, 2026
af967e0
chore(merge): integrate ws-db-810-joins
abnegate Oct 13, 2026
3f94ca7
test(adapter): prove hasFeature answers spatial support behind every …
abnegate Oct 13, 2026
35ca85b
test(e2e): tie the spatial feature to creating a point attribute
abnegate Oct 13, 2026
56c359b
feat(documents): read documents and aggregate rows through read()
abnegate Oct 13, 2026
e88ca1a
feat(cursors): resolve a list cursor from a document id
abnegate Oct 13, 2026
9db7e45
feat(validator): accept fractional operands on integers behind a flag
abnegate Oct 13, 2026
0500eb5
feat(documents): round fractions on integer attributes as the engine did
abnegate Oct 13, 2026
3683419
test(documents): cover the fractional-integer mode on every adapter
abnegate Oct 13, 2026
f4bcb32
chore(merge): integrate ws-db-810-cursors
abnegate Oct 13, 2026
b937fdd
chore(merge): integrate ws-db-810-fractions
abnegate Oct 13, 2026
1a6ba5b
chore(merge): integrate ws-db-810-features
abnegate Oct 13, 2026
2b2528b
test(joins): expect a one-to-many parent-side join to read its children
abnegate Oct 13, 2026
ac616c6
test(joins): narrow joined and aggregated values before comparing them
abnegate Oct 13, 2026
d1b836e
fix(documents): type the fraction resolution for the static analyser
abnegate Oct 13, 2026
e7fec2d
test(documents): narrow cursor and read values before comparing them
abnegate Oct 13, 2026
b95c5ab
refactor(joins): move join helpers out of the documents trait
abnegate Oct 13, 2026
6a24a34
docs(cursors): warn that resolved cursors must not reach end users
abnegate Oct 13, 2026
afa4b49
refactor(cursors): trim helper docblocks and build alias filters expl…
abnegate Oct 13, 2026
3d6cfe9
fix(arithmetic): refuse non-finite and overlong operands and read pad…
abnegate Oct 13, 2026
9f16e5d
refactor(documents): move the fractional-integer mode into a Fraction…
abnegate Oct 13, 2026
ba17673
refactor(arithmetic): name the exactly computed operators exactOperators
abnegate Oct 13, 2026
24c4ebb
fix(mirror): check grants on the source only
abnegate Oct 13, 2026
18e0cfe
refactor(state): pass every snapshot field explicitly
abnegate Oct 13, 2026
5d3b5c4
fix(relationships): read granted permissions in the write's transaction
abnegate Oct 13, 2026
c795eed
fix(documents): refuse related documents in bulk updates while preparing
abnegate Oct 13, 2026
0e1210c
fix(adapter): round fractions on SQLite, Memory and Redis as 7.4.1 st…
abnegate Oct 13, 2026
d8e4cd9
test(e2e): cover fractional counters on INT and the bound refusals pe…
abnegate Oct 13, 2026
e4724eb
docs(arithmetic): say a non-finite operand or limit leaves the operat…
abnegate Oct 13, 2026
6e8f8c7
chore(merge): integrate ws-db-810-fix-grants
abnegate Oct 13, 2026
01e5a29
chore(merge): integrate ws-db-810-fix-fractions
abnegate Oct 13, 2026
132b4e5
chore(merge): integrate ws-db-810-fix-cursors
abnegate Oct 13, 2026
ffaf4cc
docs: describe the 8.1.0 library additions
abnegate Oct 13, 2026
fb945b1
refactor(joins): keep refusing joins on relationship attributes witho…
abnegate Oct 13, 2026
82dafb3
refactor(documents): keep the join helpers where 8.0.3 has them
abnegate Oct 13, 2026
d3ac1cc
refactor(documents): keep the 8.0.3 aggregation checks in fetch() and…
abnegate Oct 13, 2026
6d85c8d
refactor(documents): keep the 8.0.3 counter path while fractions are …
abnegate Oct 13, 2026
779040a
refactor(validator): read operator limit bounds where 8.0.3 reads them
abnegate Oct 13, 2026
7938425
feat(hook): replace unique() placeholders with a nested hook
abnegate Oct 13, 2026
99bb61d
fix(state): keep snapshot constructor compatible with 8.0.3
abnegate Oct 13, 2026
2d1584c
docs(arithmetic): mark decimal and fractional internal
abnegate Oct 13, 2026
7d8181d
test(relationships): pin the nested hook in writes and pre-checks
abnegate Oct 13, 2026
9fb30a0
test(relationships): cover the nested hook on every adapter
abnegate Oct 13, 2026
84dd9ad
chore(merge): integrate ws-db-810-hook
abnegate Oct 13, 2026
6351646
chore(merge): integrate ws-db-810-hooktests
abnegate Oct 13, 2026
bd8e9cb
chore(merge): integrate ws-db-810-scope
abnegate Oct 13, 2026
676bc21
test(relationships): drop the unused marker closure and narrow the nu…
abnegate Oct 13, 2026
19e1402
docs: describe 8.1.0 as the nested hook, grants and fractions only
abnegate Oct 13, 2026
cd31054
fix(mirror): register a nested hook only in the role the mirror missed
abnegate Oct 13, 2026
c9c0966
docs: describe the nested hook without appwrite's placeholder
abnegate Oct 13, 2026
484beb2
fix(relationships): judge a hook-assigned related id new in the grant…
abnegate Oct 13, 2026
8585e2e
feat(joins): join on the parent side of a one-to-many relationship
abnegate Oct 13, 2026
11c2b25
docs: describe joins on a one-to-many relationship in 8.1.0
abnegate Oct 13, 2026
58eaf7f
chore(merge): integrate ws-db-810-o2m
abnegate Oct 13, 2026
9da71b0
chore(merge): integrate ws-db-810-gaplock
abnegate Oct 13, 2026
49b55f7
fix(joins): check cursor() pages of a one-to-many join against the re…
abnegate Oct 13, 2026
6d552fb
docs: say other relationship joins behave as in 8.0.3, not that they …
abnegate Oct 13, 2026
2c0212c
docs: list the one-to-many join under changed in 8.1.0
abnegate Oct 13, 2026
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
70 changes: 70 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,75 @@
# Changelog

## 8.1.0

8.1.0 adds opt-in and additive APIs for work that callers had to do themselves. With no `Hook\Nested` hook registered
and with `setCheckGrants()` and `setRoundFractions()` off, which is the default, a `Database` behaves exactly as in
8.0.3, except that it runs a join on the parent side of a one-to-many relationship attribute, which 8.0.3 refused
(listed under Changed). See [Upgrading from 8.0 to 8.1](UPGRADE.md#upgrading-from-80-to-81).

### Added

- `Utopia\Database\Hook\Nested`, a hook with `prepare(Document $collection, Document $document): Document`. Register
it with `Database::addHook()` and remove it with `removeHook()`. It is given a copy of each related document that
`createDocument()`, `createDocuments()`, `updateDocument()`, `upsertDocument()`, `upsertDocuments()`,
`authorizeCreate()` or `authorizeUpdate()` is given as a document, at every depth the write relates. A parent is
prepared before its children, and hooks run in the order they were registered. The write checks grants on, and
relates, the document the hook returns. The hook is not given the top-level document, a related document given as
an ID, or anything from `updateDocuments()`. A `Mirror` runs it on itself, for the pre-checks, and on its source.
Its destination receives the IDs the source wrote.
- `Database::setCheckGrants(bool $check): static`, `isCheckingGrants(): bool` and
`withCheckGrants(bool $check, callable $callback): mixed`, off by default. While on and while authorization is
enabled, a related document given as a document to `createDocument()`, `createDocuments()`, `updateDocument()`,
`upsertDocument()`, `upsertDocuments()`, `authorizeCreate()` or `authorizeUpdate()` may add permissions only for
roles the caller holds. The check judges the document the `Hook\Nested` hooks return. A related document whose
`$id` a `Hook\Nested` hook assigned is judged new: none of its permissions are read or locked, and it needs create
permission. An unheld role throws `Exception\Authorization` (`Permissions must be one of: (<roles>)`) and malformed
permissions throw `Exception\Structure`, before anything is written.
- While grants are checked, `updateDocuments()` refuses a related document given as a document with
`Exception\Relationship` (`Invalid relationship value. A bulk update takes related documents by ID while grants are
checked.`). Related documents given by ID still link.
- `Database::setRoundFractions(bool $round): static`, `isRoundingFractions(): bool` and
`withRoundFractions(bool $round, callable $callback): mixed`, off by default. While on, a fractional change, amount
or limit on a single-valued integer or big integer attribute is stored as the adapter's engine stored it on 7.4.1,
instead of being refused. This covers `increaseDocumentAttribute()`, `decreaseDocumentAttribute()` and the numeric
operators of `updateDocument()`, `updateDocuments()`, `upsertDocument()` and `upsertDocuments()`. Fractional items
added to an integer array are cut toward zero. What the engine refused is still refused.
- `Adapter::arithmetic(): Arithmetic` declares how the adapter's engine stored a fractional result in an integer
column on 7.4.1. The base answer is MariaDB's and MySQL's. `Postgres`, `Mongo`, `SQLite`, `Memory` and `Redis`
override it. `Pool` asks the pooled adapter once per pool, and `ReadWritePool` answers it wherever reads go, without
opening the sticky window.
- `Utopia\Database\Arithmetic`, a `final readonly` class:
`new Arithmetic(Rounding $counter, Rounding $operators, Rounding $division, Rounding $power, bool
$refusesCounterFractions = false, array $exactOperators = [], array $refusedWithoutLimit = [], bool
$truncatesModulus = false)`, and `rounding(OperatorType $method, bool $whole): Rounding`.
- `Utopia\Database\Arithmetic\Rounding`, an enum with the cases `HalfEven`, `HalfAwayFromZero` and `TowardZero`, and
the methods `apply(int|string $base, float $fraction): int|string`, `round(float $value): int|string` and
`ratio(int|string $numerator, int|string $denominator): int|string`.
- `Validator\Operator` and `Validator\Structure` (and so `Validator\PartialStructure`) take a trailing
`bool $fractions = false`. When it is set, a numeric operator on an integer attribute may carry a finite fractional
amount or limit within the attribute's range, for a caller that resolves it into a whole one before writing.
- `Database::resolveCursor(string $collection, string $id, CursorDirection $direction, array $queries = []):
Document` returns the cursor that a list with `$queries` pages from when it names a document by ID. The document is
read with authorization skipped, and only the cursor fields are returned: `$id`, `$sequence`, `$collection` and
the values the list orders by, including values from joined rows. Pass the result only to `cursorAfter()` or
`cursorBefore()`. It returns an empty `Document` when no document has the ID.
- `Database::read(string $collection, array $queries = [], PermissionType $forPermission = PermissionType::Read):
array` returns a list of `Document`. For aggregate or `groupBy()` queries these are the rows of `aggregate()`;
otherwise they are what `find()` returns. Aggregate queries under a permission other than read throw
`Exception\Query`. `find()` still refuses aggregate queries.
- `ParsedQuery::isAggregation(): bool` tells whether the queries aggregate or group.
- The `State\Snapshot` constructor takes two more trailing arguments, `bool $checkGrants = false` and
`bool $roundFractions = false`. `Database::snapshot()` fills them in.

### Changed

- A join condition whose left column is the parent side of a one-to-many relationship of the main collection, such
as `Query::on('orders', '$id')`, compares the main `$id` with the relationship's two-way key in the joined
collection. The condition keeps its operator. `getDocument()`, `find()`, `findOne()`, `read()`, `aggregate()`,
`count()`, `sum()` and `resolveCursor()` run it; 8.0.3 refused it with `Cannot join on virtual relationship
attribute`. The joined collection is read under the same permission and tenant rules as any join. The child side of
a one-to-many and every other relationship type join, or are refused, as before.

## 8.0.3

### Added
Expand Down
Loading
Loading