Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3e102c7
Authorization instance
shimonewman Oct 12, 2025
403ffbe
Authorization instance
shimonewman Oct 12, 2025
ba90674
Authorization instance
shimonewman Oct 12, 2025
742aef6
Authorization instance
shimonewman Oct 12, 2025
3dd0f05
Authorization instance
shimonewman Oct 15, 2025
de79b67
Refactor authorization handling to use instance methods instead of st…
shimonewman Oct 15, 2025
8007d6f
Update PHPUnit configuration to stop on failure and refactor authoriz…
shimonewman Oct 16, 2025
59153b1
linter
shimonewman Oct 16, 2025
85a2e71
codeQl
shimonewman Oct 16, 2025
ae1f07c
Update PHPUnit configuration to stop on failure and add setAuthorizat…
shimonewman Oct 16, 2025
4814e19
phpUnit
shimonewman Oct 16, 2025
4dc0ee7
Add setAuthorization method to Mirror class for improved authorizatio…
shimonewman Oct 16, 2025
63ba415
composer
shimonewman Oct 19, 2025
1196010
Merge branch 'main' of github.com:utopia-php/database into feat-autho…
shimonewman Oct 19, 2025
11f86ce
Refactor authorization handling in VectorTests to use database instan…
shimonewman Oct 19, 2025
5cd048e
update
shimonewman Oct 19, 2025
5096b38
linter
shimonewman Oct 19, 2025
46312fc
Merge branch 'main' of github.com:utopia-php/database into feat-autho…
shimonewman Oct 20, 2025
f317674
addresing comments
shimonewman Oct 20, 2025
63992e9
linter
shimonewman Oct 20, 2025
c6ad970
authorization tests to use hasRole method instead of isRole
shimonewman Oct 20, 2025
7dbb302
Refactor authorization checks to use instance methods in Database and…
shimonewman Oct 20, 2025
25af74e
linter
shimonewman Oct 20, 2025
acaca79
small fix
shimonewman Oct 20, 2025
b854100
small fix
shimonewman Oct 20, 2025
fc0d9e2
linter
shimonewman Oct 20, 2025
6a91dc5
utopia-php/mongo ver
shimonewman Oct 20, 2025
78eb4f5
composer.lock
shimonewman Oct 20, 2025
4088c10
Merge branch 'main' of github.com:utopia-php/database into feat-autho…
shimonewman Oct 20, 2025
0a4d6f3
Add authorization setter in Pool adapter initialization
shimonewman Oct 23, 2025
22123ad
Refactor getAuthorization method formatting and add error handling fo…
shimonewman Oct 27, 2025
61d0662
Merge branch 'main' of github.com:utopia-php/database into feat-autho…
shimonewman Nov 3, 2025
5464da0
Authorization::skip to $this->getAuthorization()->skip
shimonewman Nov 3, 2025
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ A list of the utopia/php concepts and their relevant equivalent using the differ
- **Document** - A simple JSON object that will be stored in one of the utopia/database collections. For SQL-based adapters, this will be equivalent to a row. For a No-SQL adapter, this will equivalent to a native document.
- **Attribute** - A simple document attribute. For SQL-based adapters, this will be equivalent to a column. For a No-SQL adapter, this will equivalent to a native document field.
- **Index** - A simple collection index used to improve the performance of your database queries.
- **Permissions** - Using permissions, you can decide which roles have read, create, update and delete access for a specific document. The special attribute `$permissions` is used to store permission metadata for each document in the collection. A permission role can be any string you want. You can use `Authorization::setRole()` to delegate new roles to your users, once obtained a new role a user would gain read, create, update or delete access to a relevant document.
- **Permissions** - Using permissions, you can decide which roles have read, create, update and delete access for a specific document. The special attribute `$permissions` is used to store permission metadata for each document in the collection. A permission role can be any string you want. You can use `$authorization->addRole()` to delegate new roles to your users, once obtained a new role a user would gain read, create, update or delete access to a relevant document.

### Filters

Expand Down
48 changes: 26 additions & 22 deletions bin/tasks/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\PDO;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
Expand All @@ -26,6 +25,7 @@
$genresPool = ['fashion', 'food', 'travel', 'music', 'lifestyle', 'fitness', 'diy', 'sports', 'finance'];
$tagsPool = ['short', 'quick', 'easy', 'medium', 'hard'];


/**
* @Example
* docker compose exec tests bin/load --adapter=mariadb --limit=1000
Expand All @@ -38,6 +38,30 @@
->param('name', 'myapp_' . uniqid(), new Text(0), 'Name of created database.', true)
->param('sharedTables', false, new Boolean(true), 'Whether to use shared tables', true)
->action(function (string $adapter, int $limit, string $name, bool $sharedTables) {


$createSchema = function (Database $database): void {
if ($database->exists($database->getDatabase())) {
$database->delete($database->getDatabase());
}
$database->getAuthorization()->addRole(Role::any()->toString());
$database->create();

$database->createCollection('articles', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
]);

$database->createAttribute('articles', 'author', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'created', Database::VAR_DATETIME, 0, true, filters: ['datetime']);
$database->createAttribute('articles', 'text', Database::VAR_STRING, 5000, true);
$database->createAttribute('articles', 'genre', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'views', Database::VAR_INTEGER, 0, true);
$database->createAttribute('articles', 'tags', Database::VAR_STRING, 0, true, array: true);
$database->createIndex('articles', 'text', Database::INDEX_FULLTEXT, ['text']);
};


$start = null;
$namespace = '_ns';
$cache = new Cache(new NoCache());
Expand Down Expand Up @@ -95,7 +119,7 @@
$cfg['attrs']
);

createSchema(
$createSchema(
(new Database(new ($cfg['adapter'])($pdo), $cache))
->setDatabase($name)
->setNamespace($namespace)
Expand Down Expand Up @@ -138,27 +162,7 @@
Console::success("Completed in {$time} seconds");
});

function createSchema(Database $database): void
{
if ($database->exists($database->getDatabase())) {
$database->delete($database->getDatabase());
}
$database->create();

Authorization::setRole(Role::any()->toString());

$database->createCollection('articles', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
]);

$database->createAttribute('articles', 'author', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'created', Database::VAR_DATETIME, 0, true, filters: ['datetime']);
$database->createAttribute('articles', 'text', Database::VAR_STRING, 5000, true);
$database->createAttribute('articles', 'genre', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'views', Database::VAR_INTEGER, 0, true);
$database->createAttribute('articles', 'tags', Database::VAR_STRING, 0, true, array: true);
}

function createDocuments(Database $database): void
{
Expand Down
26 changes: 14 additions & 12 deletions bin/tasks/query.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* @Example
* docker compose exec tests bin/query --adapter=mariadb --limit=1000 --name=testing
*/

$cli
->task('query')
->desc('Query mock data')
Expand All @@ -32,6 +33,14 @@
->param('limit', 25, new Integer(true), 'Limit on queried documents', true)
->param('sharedTables', false, new Boolean(true), 'Whether to use shared tables', true)
->action(function (string $adapter, string $name, int $limit, bool $sharedTables) {

$setRoles = function (Authorization $authorization, $faker, $count): int {
for ($i = 0; $i < $count; $i++) {
$authorization->addRole($faker->numerify('user####'));
}
return \count($authorization->getRoles());
};

$namespace = '_ns';
$cache = new Cache(new NoCache());

Expand Down Expand Up @@ -91,35 +100,35 @@

$report = [];

$count = setRoles($faker, 1);
$count = $setRoles($database->getAuthorization(), $faker, 1);
Console::info("\nRunning queries with {$count} authorization roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 100);
$count = $setRoles($database->getAuthorization(), $faker, 100);
Console::info("\nRunning queries with {$count} authorization roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 400);
$count = $setRoles($database->getAuthorization(), $faker, 400);
Console::info("\nRunning queries with {$count} authorization roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 500);
$count = $setRoles($database->getAuthorization(), $faker, 500);
Console::info("\nRunning queries with {$count} authorization roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 1000);
$count = $setRoles($database->getAuthorization(), $faker, 1000);
Console::info("\nRunning queries with {$count} authorization roles:");
$report[] = [
'roles' => $count,
Expand All @@ -136,13 +145,6 @@
\fclose($results);
});

function setRoles($faker, $count): int
{
for ($i = 0; $i < $count; $i++) {
Authorization::setRole($faker->numerify('user####'));
}
return \count(Authorization::getRoles());
}

function runQueries(Database $database, int $limit): array
{
Expand Down
144 changes: 71 additions & 73 deletions bin/tasks/relationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Utopia\Database\Helpers\Role;
use Utopia\Database\PDO;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
Expand All @@ -34,6 +33,7 @@
* @Example
* docker compose exec tests bin/relationships --adapter=mariadb --limit=1000
*/

$cli
->task('relationships')
->desc('Load database with mock relationships for testing')
Expand All @@ -49,6 +49,75 @@

Console::info("Filling {$adapter} with {$limit} records: {$name}");

$createRelationshipSchema = function (Database $database): void {
if ($database->exists($database->getDatabase())) {
$database->delete($database->getDatabase());
}
$database->getAuthorization()->addRole(Role::any()->toString());
$database->create();
$database->createCollection('authors', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('authors', 'name', Database::VAR_STRING, 256, true);
$database->createAttribute('authors', 'created', Database::VAR_DATETIME, 0, true, filters: ['datetime']);
$database->createAttribute('authors', 'bio', Database::VAR_STRING, 5000, true);
$database->createAttribute('authors', 'avatar', Database::VAR_STRING, 256, true);
$database->createAttribute('authors', 'website', Database::VAR_STRING, 256, true);

$database->createCollection('articles', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('articles', 'title', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'text', Database::VAR_STRING, 5000, true);
$database->createAttribute('articles', 'genre', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'views', Database::VAR_INTEGER, 0, true);
$database->createAttribute('articles', 'tags', Database::VAR_STRING, 0, true, array: true);

$database->createCollection('users', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('users', 'username', Database::VAR_STRING, 256, true);
$database->createAttribute('users', 'email', Database::VAR_STRING, 256, true);
$database->createAttribute('users', 'password', Database::VAR_STRING, 256, true);

$database->createCollection('comments', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('comments', 'content', Database::VAR_STRING, 256, true);
$database->createAttribute('comments', 'likes', Database::VAR_INTEGER, 8, true, signed: false);

$database->createCollection('profiles', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('profiles', 'bio_extended', Database::VAR_STRING, 10000, true);
$database->createAttribute('profiles', 'social_links', Database::VAR_STRING, 256, true, array: true);
$database->createAttribute('profiles', 'verified', Database::VAR_BOOLEAN, 0, true);

$database->createCollection('categories', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('categories', 'name', Database::VAR_STRING, 256, true);
$database->createAttribute('categories', 'description', Database::VAR_STRING, 1000, true);

$database->createRelationship('authors', 'articles', Database::RELATION_MANY_TO_MANY, true, onDelete: Database::RELATION_MUTATE_SET_NULL);
$database->createRelationship('articles', 'comments', Database::RELATION_ONE_TO_MANY, true, twoWayKey: 'article', onDelete: Database::RELATION_MUTATE_CASCADE);
$database->createRelationship('users', 'comments', Database::RELATION_ONE_TO_MANY, true, twoWayKey: 'user', onDelete: Database::RELATION_MUTATE_CASCADE);
$database->createRelationship('authors', 'profiles', Database::RELATION_ONE_TO_ONE, true, twoWayKey: 'author', onDelete: Database::RELATION_MUTATE_CASCADE);
$database->createRelationship('articles', 'categories', Database::RELATION_MANY_TO_ONE, true, id: 'category', twoWayKey: 'articles', onDelete: Database::RELATION_MUTATE_SET_NULL);
};

$dbAdapters = [
'mariadb' => [
'host' => 'mariadb',
Expand Down Expand Up @@ -98,7 +167,7 @@
->setNamespace($namespace)
->setSharedTables($sharedTables);

createRelationshipSchema($database);
$createRelationshipSchema($database);

// Create categories and users once before parallel batch creation
$globalDocs = createGlobalDocuments($database, $limit);
Expand Down Expand Up @@ -165,77 +234,6 @@
displayBenchmarkResults($results, $runs);
});

function createRelationshipSchema(Database $database): void
{
if ($database->exists($database->getDatabase())) {
$database->delete($database->getDatabase());
}
$database->create();

Authorization::setRole(Role::any()->toString());

$database->createCollection('authors', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('authors', 'name', Database::VAR_STRING, 256, true);
$database->createAttribute('authors', 'created', Database::VAR_DATETIME, 0, true, filters: ['datetime']);
$database->createAttribute('authors', 'bio', Database::VAR_STRING, 5000, true);
$database->createAttribute('authors', 'avatar', Database::VAR_STRING, 256, true);
$database->createAttribute('authors', 'website', Database::VAR_STRING, 256, true);

$database->createCollection('articles', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('articles', 'title', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'text', Database::VAR_STRING, 5000, true);
$database->createAttribute('articles', 'genre', Database::VAR_STRING, 256, true);
$database->createAttribute('articles', 'views', Database::VAR_INTEGER, 0, true);
$database->createAttribute('articles', 'tags', Database::VAR_STRING, 0, true, array: true);

$database->createCollection('users', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('users', 'username', Database::VAR_STRING, 256, true);
$database->createAttribute('users', 'email', Database::VAR_STRING, 256, true);
$database->createAttribute('users', 'password', Database::VAR_STRING, 256, true);

$database->createCollection('comments', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('comments', 'content', Database::VAR_STRING, 256, true);
$database->createAttribute('comments', 'likes', Database::VAR_INTEGER, 8, true, signed: false);

$database->createCollection('profiles', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('profiles', 'bio_extended', Database::VAR_STRING, 10000, true);
$database->createAttribute('profiles', 'social_links', Database::VAR_STRING, 256, true, array: true);
$database->createAttribute('profiles', 'verified', Database::VAR_BOOLEAN, 0, true);

$database->createCollection('categories', permissions: [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
]);
$database->createAttribute('categories', 'name', Database::VAR_STRING, 256, true);
$database->createAttribute('categories', 'description', Database::VAR_STRING, 1000, true);

$database->createRelationship('authors', 'articles', Database::RELATION_MANY_TO_MANY, true, onDelete: Database::RELATION_MUTATE_SET_NULL);
$database->createRelationship('articles', 'comments', Database::RELATION_ONE_TO_MANY, true, twoWayKey: 'article', onDelete: Database::RELATION_MUTATE_CASCADE);
$database->createRelationship('users', 'comments', Database::RELATION_ONE_TO_MANY, true, twoWayKey: 'user', onDelete: Database::RELATION_MUTATE_CASCADE);
$database->createRelationship('authors', 'profiles', Database::RELATION_ONE_TO_ONE, true, twoWayKey: 'author', onDelete: Database::RELATION_MUTATE_CASCADE);
$database->createRelationship('articles', 'categories', Database::RELATION_MANY_TO_ONE, true, id: 'category', twoWayKey: 'articles', onDelete: Database::RELATION_MUTATE_SET_NULL);
}

function createGlobalDocuments(Database $database, int $limit): array
{
Expand Down
1 change: 1 addition & 0 deletions bin/view/results/mariadb_testing_1000_1760626673.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"roles":2,"results":{"Querying greater than, equal[1] and limit":0.03397989273071289,"Querying equal[3] and limit":0.07099103927612305,"Querying greaterThan, limit(1000)":0.18834996223449707,"Query search, limit(1000)":0.0020699501037597656,"Querying contains[1], limit(1000)":0.002312898635864258}},{"roles":101,"results":{"Querying greater than, equal[1] and limit":0.026144027709960938,"Querying equal[3] and limit":0.07204604148864746,"Querying greaterThan, limit(1000)":0.18575501441955566,"Query search, limit(1000)":0.0020110607147216797,"Querying contains[1], limit(1000)":0.002538919448852539}},{"roles":489,"results":{"Querying greater than, equal[1] and limit":0.02759695053100586,"Querying equal[3] and limit":0.07203984260559082,"Querying greaterThan, limit(1000)":0.18977999687194824,"Query search, limit(1000)":0.003882884979248047,"Querying contains[1], limit(1000)":0.0029969215393066406}},{"roles":958,"results":{"Querying greater than, equal[1] and limit":0.029262065887451172,"Querying equal[3] and limit":0.0740351676940918,"Querying greaterThan, limit(1000)":0.18916702270507812,"Query search, limit(1000)":0.0023200511932373047,"Querying contains[1], limit(1000)":0.0027260780334472656}},{"roles":1825,"results":{"Querying greater than, equal[1] and limit":0.028146982192993164,"Querying equal[3] and limit":0.07226085662841797,"Querying greaterThan, limit(1000)":0.18572497367858887,"Query search, limit(1000)":0.002095937728881836,"Querying contains[1], limit(1000)":0.0025589466094970703}}]
6 changes: 3 additions & 3 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading