Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4dee763
refactor(api): remove dead 8.x surface for 8.1.2
abnegate Oct 14, 2026
888a20d
fix(pool): honour setHostname() and expose the pooled connections
abnegate Oct 14, 2026
029ebf2
refactor(documents)!: remove read() in favour of find() and aggregate()
abnegate Oct 14, 2026
34b1b9d
docs: document read() removal and pool hostname for 8.1.2
abnegate Oct 14, 2026
7990857
refactor(documents): round fractional integer changes instead of emul…
abnegate Oct 14, 2026
a6a67b0
docs: describe the 8.1.2 integer rounding rule
abnegate Oct 14, 2026
e1cc40c
test: name the rounding tests after the rule they check
abnegate Oct 14, 2026
01c891b
refactor(schema)!: move the update models into their model namespaces
abnegate Oct 14, 2026
66cc4a9
Merge branch 'qlt-fix/db-c1' into qlt-fix/db-cleanup
abnegate Oct 14, 2026
b87d1ae
Merge branch 'qlt-fix/db-c2' into qlt-fix/db-cleanup
abnegate Oct 14, 2026
656bffb
Merge branch 'qlt-fix/db-c4' into qlt-fix/db-cleanup
abnegate Oct 14, 2026
42873d8
Merge branch 'qlt-fix/db-c6' into qlt-fix/db-cleanup
abnegate Oct 14, 2026
8f7b5f9
fix(pool): forget the cached write-pool hostname when a hostname is set
abnegate Oct 14, 2026
cc9bf56
test(hook): cover the column check of the AllowNull constructor
abnegate Oct 14, 2026
e949653
refactor(documents): name the integer rounding modes with an enum
abnegate Oct 14, 2026
9b0ccc4
fix(documents): round every plain decimal text that is_numeric accepts
abnegate Oct 14, 2026
dfd3382
fix(documents): drop an operator change that rounds to nothing
abnegate Oct 14, 2026
3a28e0d
test(documents): cover zero rounding, decimal text and integer arrays
abnegate Oct 14, 2026
2970af0
docs: describe zero rounding, decimal text and the rounding opt-in lo…
abnegate Oct 14, 2026
b3279a1
fix(documents): drop zero-rounded operators from the caller's updates
abnegate Oct 14, 2026
0726028
perf(big-int): add and subtract long integer strings in linear time
abnegate Oct 14, 2026
f9f6f51
fix(document): mark removeAttribute impure so analysis forgets isEmpty
abnegate Oct 14, 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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml tests/u
- `Capability.php` -- behaviour flags an adapter reports through `supports()`
- `Document.php` -- JSON document model (extends ArrayObject)
- `Collection.php` -- the collection definition (a `Document`, built with `Collection::create()`)
- `Attribute.php`, `Index.php`, `Relationship.php` -- `final readonly` schema value objects with a factory per type; `AttributeUpdate`, `CollectionUpdate`, `RelationshipUpdate` are the update models
- `Attribute.php`, `Index.php`, `Relationship.php` -- `final readonly` schema value objects with a factory per type; `Attribute\Update`, `Collection\Update`, `Relationship\Update` are the update models
- `Schema/` -- `Column` and `Index`, the physical schema read back from an engine
- `Mirror.php`, `Mirror/` -- database mirroring/replication, its write `Filter` and `Failure`
- `Query.php` -- query builder extension
Expand Down Expand Up @@ -66,7 +66,7 @@ docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml tests/u

**Hook system:** Pluggable hooks for permissions, relationships, tenancy filtering, and lifecycle events, registered with `Database::addHook()`. A database registers neither `Hook\Permissions` nor `Hook\Relationships` on its own.

**Schema API:** Collection-first methods take value objects: `createCollection(Collection::create(...))`, `createAttribute($collection, Attribute::string(...))`, `createIndex($collection, Index::key(...))`, `createRelationship($collection, Relationship::oneToMany(...))`, and updates take `AttributeUpdate`, `CollectionUpdate`, `RelationshipUpdate`.
**Schema API:** Collection-first methods take value objects: `createCollection(Collection::create(...))`, `createAttribute($collection, Attribute::string(...))`, `createIndex($collection, Index::key(...))`, `createRelationship($collection, Relationship::oneToMany(...))`, and updates take `Attribute\Update`, `Collection\Update`, `Relationship\Update`.

**Adapter model:** Ask `supports(Capability::X)` for behaviour flags and `hasFeature(Feature\X::class)` for optional features (never `instanceof`: `Pool` forwards features without implementing them). Limits come from `limits()`; `Database::profile()` snapshots them for the validators.

Expand Down
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.2

8.1.2 removes public API that nothing outside the library needed, replaces the opt-in emulation of 7.4.1's fractional
arithmetic on integer attributes with one rounding rule that always applies, and moves the update models into the
namespace of the model they update. Every entry under Removed and Changed (breaking) needs a code change; see
[Upgrading from 8.1.1 to 8.1.2](UPGRADE.md#upgrading-from-811-to-812) for each replacement.

### Removed (breaking)

- `Database::read()`, added in 8.1.0. Call `find()` for documents and `aggregate()` for aggregate or `groupBy()`
queries; `Query::groupByType($queries)->isAggregation()` tells which of the two a list is. `find()` still refuses
aggregate and `groupBy()` queries.
- `Database::rawQuery()`. Use `Database::query(new Utopia\Query\Builder\Statement($query, $bindings))`, which runs
the same statement under the same authorization guard. `Feature\RawQuery` and the adapters' `rawQuery()` stay.
- `Database::getRelationshipHook()`. Keep the `Hook\Relationships` given to `addHook()`.
- `Database::getMaxIndexLength()`, `getMaxVarcharLength()`, `getMinDateTime()` and `getMaxDateTime()`. Read
`indexLength`, `varchar`, `minDateTime` and `maxDateTime` from `$database->getAdapter()->limits()`.
- `Database::getCacheWriterTimeout()`.
- `Database::setRoundFractions()`, `isRoundingFractions()` and `withRoundFractions()`, with the `Mirror` overrides.
Integer rounding is always on (listed under Changed).
- `Adapter::arithmetic()` and its overrides in `Postgres`, `Mongo`, `SQLite`, `Memory`, `Redis` and `Pool`;
`ReadWritePool` no longer lists it among the methods it answers wherever reads go.
- `Utopia\Database\Arithmetic`, `Utopia\Database\Arithmetic\Rounding` and the internal `Arithmetic\Decimal` and
`Arithmetic\Fractional`.
- The trailing `bool $fractions` argument of `Validator\Operator` and `Validator\Structure` (and so
`Validator\PartialStructure`).
- The trailing `bool $roundFractions` argument of the `State\Snapshot` constructor.
- `Event\Permission\Created`, `Read` and `Deleted`, which `Database` never dispatched. `Event::PermissionsCreate`,
`PermissionsRead` and `PermissionsDelete` stay as statement labels; their `domain()` is `null`.
- `Adapter\SQL\Hook\Permission\AllowNullUid`. Use `Adapter\SQL\Hook\Column\AllowNull`, which it wrapped.

### Changed (breaking)

- The update models moved into the namespace of the model they update. Replace the imports:

| 8.1.1 | 8.1.2 |
|---|---|
| `Utopia\Database\AttributeUpdate` | `Utopia\Database\Attribute\Update` |
| `Utopia\Database\CollectionUpdate` | `Utopia\Database\Collection\Update` |
| `Utopia\Database\RelationshipUpdate` | `Utopia\Database\Relationship\Update` |

Their constructors and fields are unchanged.

### Changed

- On a single-valued integer or big integer attribute, `increaseDocumentAttribute()` and `decreaseDocumentAttribute()`
round a fractional change half away from zero (1.5 adds 2, 2.5 subtracts 3), floor a fractional `max` and ceil a
fractional `min`; `Operator::increment()` and `Operator::decrement()` given to `updateDocument()`,
`updateDocuments()`, `upsertDocument()` and `upsertDocuments()` round their amount and limit the same way, so
`increment(-2.5)` subtracts 3. Rounding happens once, in PHP, before validation and on every adapter alike. Decimal
text, which is what `is_numeric()` accepts without an exponent, such as `'9007199254740993.5'`, `' +.5'` or `'1.'`,
is rounded exactly at any size; text with an exponent is refused. A change that rounds to 0, such as 0.4, changes
nothing and skips the `max` or `min` check: a counter writes nothing and fires no event, and an operator is dropped
from the update, so `updateDocuments()` with nothing else to change returns 0. Before, these were refused, unless
`setRoundFractions(true)` stored what the adapter's engine stored on 7.4.1.
- Refused with `Exception\Structure`, as in 8.0.3 and in 8.1.1 without `setRoundFractions(true)`: a fractional amount
or limit of `multiply()`, `divide()`, `modulo()` or `power()` on an integer attribute, and a fractional item added to
an integer array. `setRoundFractions(true)` resolved the operators and cut the array items toward zero. Float and
double attributes are unchanged.

### Added

- `Adapter\Pool::getPool(): Utopia\Pools\Pool` returns the pool of adapters the handle borrows from.

### Fixed

- `Adapter\Pool::hostname()` returns the hostname given to `setHostname()` when it is not empty, and asks the pooled
adapter only otherwise. 8.1.1 ignored it, so `Database::getHostname()` and the cache keys built from it named the
pooled adapter's host instead. `ReadWritePool` also follows a hostname set after its first answer.

## 8.1.1

### Added
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ $profile->supports(Capability::Joins);
```php
use Utopia\Database\Attribute;
use Utopia\Database\Collection;
use Utopia\Database\CollectionUpdate;
use Utopia\Database\Collection\Update;
use Utopia\Database\Index;
use Utopia\Database\Permission;
use Utopia\Database\Role;
Expand All @@ -425,7 +425,7 @@ $database->createCollection(Collection::create(
));

// Update Collection Permissions; a field left null keeps its stored value
$database->updateCollection('users', new CollectionUpdate(
$database->updateCollection('users', new Update(
permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Expand Down Expand Up @@ -461,7 +461,7 @@ $database->deleteCollection('drafts');

```php
use Utopia\Database\Attribute;
use Utopia\Database\AttributeUpdate;
use Utopia\Database\Attribute\Update;
use Utopia\Database\Collection;
use Utopia\Database\Format;
use Utopia\Database\IntegerWidth;
Expand Down Expand Up @@ -512,12 +512,12 @@ Structure::addFormat(
);

// Updates an attribute. Only the fields given change; default: null and format: null remove them.
$database->updateAttribute('movies', 'year', new AttributeUpdate(
$database->updateAttribute('movies', 'year', new Update(
required: true,
format: new Format('year', ['min' => 1888, 'max' => 2100]),
));

$database->updateAttribute('movies', 'director', new AttributeUpdate(default: 'Unknown'));
$database->updateAttribute('movies', 'director', new Update(default: 'Unknown'));

// Check if attribute can be added to a collection
$database->checkAttribute('movies', Attribute::integer('rating'));
Expand Down Expand Up @@ -581,8 +581,8 @@ use Utopia\Database\Attribute;
use Utopia\Database\Collection;
use Utopia\Database\Permission;
use Utopia\Database\Relationship;
use Utopia\Database\Relationship\Update;
use Utopia\Database\RelationshipDeleteAction;
use Utopia\Database\RelationshipUpdate;
use Utopia\Database\Role;

// Each relationship type has a factory: Relationship::oneToOne(), oneToMany(), manyToOne() and manyToMany().
Expand All @@ -609,12 +609,12 @@ $database->createRelationship('movies', Relationship::oneToMany(
));

// Update the relationship with the default reference attributes
$database->updateRelationship('movies', 'users', new RelationshipUpdate(
$database->updateRelationship('movies', 'users', new Update(
onDelete: RelationshipDeleteAction::SetNull
));

// Update the relationship with custom reference attributes
$database->updateRelationship('movies', 'users', new RelationshipUpdate(
$database->updateRelationship('movies', 'users', new Update(
key: 'viewer',
twoWayKey: 'favoriteMovie',
twoWay: true
Expand Down
Loading
Loading