diff --git a/composer.json b/composer.json index 9e021c7..1353bba 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ }, "require": { "php": ">=8.5", - "utopia-php/database": "^7.0.0", + "utopia-php/database": "dev-feat-query-lib as 7.0.0", "utopia-php/fetch": "^1.1", "utopia-php/query": "0.6.*", "utopia-php/validators": "^0.5" @@ -37,5 +37,19 @@ "php-http/discovery": true, "tbachert/spi": true } - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/utopia-php/database.git" + }, + { + "type": "vcs", + "url": "https://github.com/utopia-php/query.git" + }, + { + "type": "vcs", + "url": "https://github.com/utopia-php/async.git" + } + ] } diff --git a/composer.lock b/composer.lock index 76b01b5..5d19a19 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0c3378591a7b499d440f28324d6df869", + "content-hash": "d1a40a25035242925dac974928909be4", "packages": [ { "name": "brick/math", @@ -144,23 +144,23 @@ }, { "name": "google/protobuf", - "version": "v5.35.1", + "version": "v5.36.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "55bb4a7d6739b5af0927b96213c1371a3afb7cfb" + "reference": "9c105104b54709ecd902494ab340ed2122789b2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/55bb4a7d6739b5af0927b96213c1371a3afb7cfb", - "reference": "55bb4a7d6739b5af0927b96213c1371a3afb7cfb", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9c105104b54709ecd902494ab340ed2122789b2d", + "reference": "9c105104b54709ecd902494ab340ed2122789b2d", "shasum": "" }, "require": { "php": ">=8.2.0" }, "require-dev": { - "phpunit/phpunit": ">=11.5.0 <12.0.0" + "phpunit/phpunit": ">=11.5.50 <12.0.0" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -182,9 +182,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v5.35.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v5.36.0" }, - "time": "2026-06-11T21:19:23+00:00" + "time": "2026-08-20T13:06:50+00:00" }, { "name": "mongodb/mongodb", @@ -820,6 +820,71 @@ }, "time": "2026-01-21T04:14:03+00:00" }, + { + "name": "opis/closure", + "version": "4.5.0", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "b97e42b95bb72d87507f5e2d137ceb239aea8d6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/b97e42b95bb72d87507f5e2d137ceb239aea8d6b", + "reference": "b97e42b95bb72d87507f5e2d137ceb239aea8d6b", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary data.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous classes", + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/4.5.0" + }, + "time": "2026-03-05T13:32:42+00:00" + }, { "name": "php-http/discovery", "version": "1.20.0", @@ -1389,16 +1454,16 @@ }, { "name": "symfony/http-client", - "version": "v7.4.16", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "c513ed0ba5d1784a6b55fc84190dbe4451b12f41" + "reference": "d0aca330a822fe40376cd04c335cb48a24743d0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/c513ed0ba5d1784a6b55fc84190dbe4451b12f41", - "reference": "c513ed0ba5d1784a6b55fc84190dbe4451b12f41", + "url": "https://api.github.com/repos/symfony/http-client/zipball/d0aca330a822fe40376cd04c335cb48a24743d0c", + "reference": "d0aca330a822fe40376cd04c335cb48a24743d0c", "shasum": "" }, "require": { @@ -1466,7 +1531,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.16" + "source": "https://github.com/symfony/http-client/tree/v7.4.17" }, "funding": [ { @@ -1486,7 +1551,7 @@ "type": "tidelift" } ], - "time": "2026-07-29T16:20:51+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/http-client-contracts", @@ -2034,24 +2099,142 @@ }, "time": "2025-06-29T15:42:06+00:00" }, + { + "name": "utopia-php/async", + "version": "0.1.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/async.git", + "reference": "3ee4fc3d505113d0d6050f35f5bf85e706866a22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/async/zipball/3ee4fc3d505113d0d6050f35f5bf85e706866a22", + "reference": "3ee4fc3d505113d0d6050f35f5bf85e706866a22", + "shasum": "" + }, + "require": { + "opis/closure": "4.*", + "php": ">=8.1" + }, + "require-dev": { + "amphp/amp": "3.*", + "amphp/parallel": "2.*", + "amphp/process": "^2.0", + "laravel/pint": "1.*", + "phpstan/phpstan": "2.*", + "phpunit/phpunit": "11.5.45", + "react/child-process": "0.*", + "react/event-loop": "1.*", + "swoole/ide-helper": "*" + }, + "suggest": { + "amphp/amp": "Required for Amp promise adapter", + "amphp/parallel": "Required for Amp parallel adapter", + "ext-ev": "Required for ReactPHP event loop (recommended for best performance)", + "ext-parallel": "Required for parallel adapter (requires PHP ZTS build)", + "ext-sockets": "Required for Swoole Process adapter", + "ext-swoole": "Required for Swoole Thread and Process adapters (recommended for best performance)", + "react/child-process": "Required for ReactPHP parallel adapter", + "react/event-loop": "Required for ReactPHP promise and parallel adapters" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Async\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\": "tests/" + } + }, + "scripts": { + "test-unit": [ + "vendor/bin/phpunit tests/Unit --exclude-group no-swoole" + ], + "test-promise-sync": [ + "vendor/bin/phpunit tests/E2e/Promise/SyncTest.php" + ], + "test-promise-swoole": [ + "vendor/bin/phpunit tests/E2e/Promise/Swoole" + ], + "test-promise-amp": [ + "vendor/bin/phpunit tests/E2e/Promise/Amp" + ], + "test-promise-react": [ + "vendor/bin/phpunit tests/E2e/Promise/React" + ], + "test-parallel-sync": [ + "vendor/bin/phpunit tests/E2e/Parallel/Sync" + ], + "test-parallel-swoole-thread": [ + "vendor/bin/phpunit tests/E2e/Parallel/Swoole/ThreadTest.php" + ], + "test-parallel-swoole-process": [ + "vendor/bin/phpunit tests/E2e/Parallel/Swoole/ProcessTest.php" + ], + "test-parallel-amp": [ + "vendor/bin/phpunit tests/E2e/Parallel/Amp" + ], + "test-parallel-react": [ + "vendor/bin/phpunit tests/E2e/Parallel/React" + ], + "test-parallel-ext": [ + "php -n -d extension=parallel.so -d extension=sockets.so vendor/bin/phpunit tests/E2e/Parallel/Parallel" + ], + "test-e2e": [ + "vendor/bin/phpunit tests/E2e --exclude-group ext-parallel" + ], + "test": [ + "@test-unit", + "@test-e2e", + "@test-parallel-ext" + ], + "lint": [ + "vendor/bin/pint" + ], + "format": [ + "php -d memory_limit=4G vendor/bin/pint" + ], + "check": [ + "vendor/bin/phpstan analyse src tests --level=max --memory-limit=4G" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Appwrite Team", + "email": "team@appwrite.io" + } + ], + "description": "High-performance concurrent + parallel library with Promise and Parallel execution support for PHP.", + "support": { + "source": "https://github.com/utopia-php/async/tree/0.1.1", + "issues": "https://github.com/utopia-php/async/issues" + }, + "time": "2026-06-08T05:10:34+00:00" + }, { "name": "utopia-php/cache", - "version": "4.0.2", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "92e02dab63606234b993b841ebf4c58845dd4620" + "reference": "0d0752785fc81b5afd6571f4291763166a6532bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/92e02dab63606234b993b841ebf4c58845dd4620", - "reference": "92e02dab63606234b993b841ebf4c58845dd4620", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/0d0752785fc81b5afd6571f4291763166a6532bc", + "reference": "0d0752785fc81b5afd6571f4291763166a6532bc", "shasum": "" }, "require": { "ext-json": "*", "php": ">=8.4", - "utopia-php/circuit-breaker": "^0.3", + "utopia-php/circuit-breaker": "^0.4", "utopia-php/pools": "^2.0", "utopia-php/telemetry": "^0.4" }, @@ -2089,22 +2272,22 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/4.0.2" + "source": "https://github.com/utopia-php/cache/tree/5.0.0" }, - "time": "2026-08-12T07:48:59+00:00" + "time": "2026-08-21T11:04:48+00:00" }, { "name": "utopia-php/circuit-breaker", - "version": "0.3.2", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/circuit-breaker.git", - "reference": "5fbc3802471b0d1b4260bd9f5544514e6929b481" + "reference": "c6d93c7ba9d895cf906360e000f74ff762d83e17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/circuit-breaker/zipball/5fbc3802471b0d1b4260bd9f5544514e6929b481", - "reference": "5fbc3802471b0d1b4260bd9f5544514e6929b481", + "url": "https://api.github.com/repos/utopia-php/circuit-breaker/zipball/c6d93c7ba9d895cf906360e000f74ff762d83e17", + "reference": "c6d93c7ba9d895cf906360e000f74ff762d83e17", "shasum": "" }, "require": { @@ -2148,9 +2331,9 @@ ], "support": { "issues": "https://github.com/utopia-php/circuit-breaker/issues", - "source": "https://github.com/utopia-php/circuit-breaker/tree/0.3.2" + "source": "https://github.com/utopia-php/circuit-breaker/tree/0.4.0" }, - "time": "2026-08-05T18:07:20+00:00" + "time": "2026-08-21T10:20:24+00:00" }, { "name": "utopia-php/console", @@ -2202,16 +2385,16 @@ }, { "name": "utopia-php/database", - "version": "7.2.2", + "version": "dev-feat-query-lib", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "761050b576d18aee26810229b5c60df75cedbf79" + "reference": "b0254667fa7aa2131a1d72f23a16f73924bb04e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/761050b576d18aee26810229b5c60df75cedbf79", - "reference": "761050b576d18aee26810229b5c60df75cedbf79", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b0254667fa7aa2131a1d72f23a16f73924bb04e7", + "reference": "b0254667fa7aa2131a1d72f23a16f73924bb04e7", "shasum": "" }, "require": { @@ -2220,21 +2403,29 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.5", - "utopia-php/cache": "^4.0.0", + "utopia-php/async": "^0.1", + "utopia-php/cache": "^4.0 || ^5.0", "utopia-php/console": "0.1.*", "utopia-php/mongo": "1.*", "utopia-php/pools": "2.*", + "utopia-php/query": "0.6.*", "utopia-php/validators": "^0.5" }, "require-dev": { + "brianium/paratest": "7.20.*", "fakerphp/faker": "1.23.*", "laravel/pint": "*", - "pcov/clobber": "2.*", - "phpstan/phpstan": "1.*", - "phpunit/phpunit": "9.*", + "phpstan/phpstan": "2.1.*", + "phpstan/phpstan-phpunit": "2.0.*", + "phpunit/phpunit": "12.5.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "0.22.*" + "utopia-php/cli": "^0.22" + }, + "suggest": { + "ext-pdo": "Needed to support MariaDB, MySQL or SQLite Database Adapter", + "ext-redis": "Needed to support Redis Cache Adapter", + "mongodb/mongodb": "Needed to support MongoDB Database Adapter" }, "type": "library", "autoload": { @@ -2242,7 +2433,38 @@ "Utopia\\Database\\": "src/Database" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Tests\\E2E\\": "tests/e2e", + "Tests\\Unit\\": "tests/unit" + } + }, + "scripts": { + "build": [ + "Composer\\Config::disableProcessTimeout", + "docker compose build" + ], + "start": [ + "Composer\\Config::disableProcessTimeout", + "docker compose up -d" + ], + "test": [ + "Composer\\Config::disableProcessTimeout", + "docker compose exec tests vendor/bin/paratest --configuration phpunit.xml --functional --processes 4" + ], + "lint": [ + "php -d memory_limit=2G ./vendor/bin/pint --test" + ], + "format": [ + "php -d memory_limit=2G ./vendor/bin/pint" + ], + "check": [ + "./vendor/bin/phpstan analyse --memory-limit 2G" + ], + "coverage": [ + "./vendor/bin/coverage-check ./tmp/clover.xml 90" + ] + }, "license": [ "MIT" ], @@ -2255,10 +2477,10 @@ "utopia" ], "support": { - "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/7.2.2" + "source": "https://github.com/utopia-php/database/tree/feat-query-lib", + "issues": "https://github.com/utopia-php/database/issues" }, - "time": "2026-08-14T06:28:08+00:00" + "time": "2026-08-26T01:17:32+00:00" }, { "name": "utopia-php/fetch", @@ -2448,7 +2670,38 @@ "Utopia\\Query\\": "src/Query" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Tests\\Query\\": "tests/Query", + "Tests\\Integration\\": "tests/Integration" + } + }, + "scripts": { + "test": [ + "vendor/bin/paratest --testsuite Query --processes=auto --exclude-group=performance" + ], + "test:coverage": [ + "vendor/bin/paratest --testsuite Query --processes=auto --exclude-group=performance --coverage-php coverage/unit.cov" + ], + "test:performance": [ + "vendor/bin/phpunit --testsuite Query --group=performance" + ], + "test:integration": [ + "vendor/bin/phpunit --testsuite Integration" + ], + "test:integration:coverage": [ + "vendor/bin/phpunit --testsuite Integration --coverage-php coverage/integration.cov" + ], + "lint": [ + "php -d memory_limit=2G ./vendor/bin/pint --test" + ], + "format": [ + "php -d memory_limit=2G ./vendor/bin/pint" + ], + "check": [ + "./vendor/bin/phpstan analyse --level max src tests --memory-limit 2G" + ] + }, "license": [ "MIT" ], @@ -2461,8 +2714,8 @@ "utopia" ], "support": { - "issues": "https://github.com/utopia-php/query/issues", - "source": "https://github.com/utopia-php/query/tree/0.6.0" + "source": "https://github.com/utopia-php/query/tree/0.6.0", + "issues": "https://github.com/utopia-php/query/issues" }, "time": "2026-08-21T11:03:36+00:00" }, @@ -2559,9 +2812,18 @@ } ], "packages-dev": [], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-query-lib", + "alias": "7.0.0", + "alias_normalized": "7.0.0.0" + } + ], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Audit/Adapter/ClickHouse.php b/src/Audit/Adapter/ClickHouse.php index 7b3bdbe..188785a 100644 --- a/src/Audit/Adapter/ClickHouse.php +++ b/src/Audit/Adapter/ClickHouse.php @@ -5,7 +5,9 @@ use Exception; use Utopia\Audit\Log; use Utopia\Audit\Query; +use Utopia\Database\Attribute; use Utopia\Database\Database; +use Utopia\Database\Index; use Utopia\Fetch\Client; use Utopia\Query\Builder\ClickHouse as ClickHouseBuilder; use Utopia\Query\Builder\ClickHouse\Format; @@ -375,394 +377,65 @@ public function getRetention(): ?int * Override getAttributes to provide extended attributes for ClickHouse. * Includes existing attributes from parent and adds new missing ones. * - * @return array> + * @return array */ #[\Override] public function getAttributes(): array { $parentAttributes = parent::getAttributes(); - foreach ($parentAttributes as &$attribute) { - if (($attribute['$id'] ?? null) === 'userId') { - $attribute['$id'] = 'actorId'; + foreach ($parentAttributes as $index => $attribute) { + if ($attribute->key === 'userId') { + $parentAttributes[$index] = new Attribute( + key: 'actorId', + type: $attribute->type, + size: $attribute->size, + required: $attribute->required, + signed: $attribute->signed, + array: $attribute->array, + filters: $attribute->filters, + ); break; } } - unset($attribute); return [ ...$parentAttributes, - [ - '$id' => 'actorType', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => true, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'actorInternalId', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'resourceParent', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'resourceType', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => true, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'resourceId', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => true, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'resourceInternalId', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'country', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - // premium geo - [ - '$id' => 'city', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'continentCode', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'subdivisions', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'isp', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'autonomousSystemNumber', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'autonomousSystemOrganization', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'connectionType', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'connectionUsageType', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'connectionOrganization', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'default' => null, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'projectId', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'projectInternalId', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'teamId', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'teamInternalId', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'hostname', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'sdk', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'sdkVersion', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - // user-agent — parsed OS / client / device dimensions - [ - '$id' => 'osCode', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'osName', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'osVersion', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'clientType', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'clientCode', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'clientName', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'clientVersion', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'clientEngine', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'clientEngineVersion', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'deviceName', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'deviceBrand', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'deviceModel', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], + Attribute::string(key: 'actorType', required: true), + Attribute::string(key: 'actorInternalId'), + Attribute::string(key: 'resourceParent'), + Attribute::string(key: 'resourceType', required: true), + Attribute::string(key: 'resourceId', required: true), + Attribute::string(key: 'resourceInternalId'), + Attribute::string(key: 'country'), + Attribute::string(key: 'city'), + Attribute::string(key: 'continentCode'), + Attribute::string(key: 'subdivisions'), + Attribute::string(key: 'isp'), + Attribute::string(key: 'autonomousSystemNumber'), + Attribute::string(key: 'autonomousSystemOrganization'), + Attribute::string(key: 'connectionType'), + Attribute::string(key: 'connectionUsageType'), + Attribute::string(key: 'connectionOrganization'), + Attribute::string(key: 'projectId', required: true), + Attribute::string(key: 'projectInternalId', required: true), + Attribute::string(key: 'teamId', required: true), + Attribute::string(key: 'teamInternalId', required: true), + Attribute::string(key: 'hostname', required: true), + Attribute::string(key: 'sdk'), + Attribute::string(key: 'sdkVersion'), + Attribute::string(key: 'osCode'), + Attribute::string(key: 'osName'), + Attribute::string(key: 'osVersion'), + Attribute::string(key: 'clientType'), + Attribute::string(key: 'clientCode'), + Attribute::string(key: 'clientName'), + Attribute::string(key: 'clientVersion'), + Attribute::string(key: 'clientEngine'), + Attribute::string(key: 'clientEngineVersion'), + Attribute::string(key: 'deviceName'), + Attribute::string(key: 'deviceBrand'), + Attribute::string(key: 'deviceModel'), ]; } @@ -770,80 +443,37 @@ public function getAttributes(): array * Override getIndexes to provide extended indexes for ClickHouse. * Includes existing indexes from parent and adds new missing ones. * - * @return array> + * @return array */ #[\Override] public function getIndexes(): array { $parentIndexes = parent::getIndexes(); - foreach ($parentIndexes as &$index) { - if (($index['$id'] ?? null) === 'idx_userId_event') { - $index['$id'] = 'idx_actorId_event'; - $index['attributes'] = ['actorId', 'event']; + foreach ($parentIndexes as $index => $definition) { + if ($definition->key === 'idx_userId_event') { + $parentIndexes[$index] = new Index( + key: 'idx_actorId_event', + type: $definition->type, + attributes: ['actorId', 'event'], + lengths: $definition->lengths, + orders: $definition->orders, + ttl: $definition->ttl, + ); break; } } - unset($index); return [ ...$parentIndexes, - [ - '$id' => '_key_actor_internal_and_event', - 'type' => Database::INDEX_KEY, - 'attributes' => ['actorInternalId', 'event'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_project_internal_id', - 'type' => Database::INDEX_KEY, - 'attributes' => ['projectInternalId'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_team_internal_id', - 'type' => Database::INDEX_KEY, - 'attributes' => ['teamInternalId'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_actor_internal_id', - 'type' => Database::INDEX_KEY, - 'attributes' => ['actorInternalId'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_actor_type', - 'type' => Database::INDEX_KEY, - 'attributes' => ['actorType'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_country', - 'type' => Database::INDEX_KEY, - 'attributes' => ['country'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_hostname', - 'type' => Database::INDEX_KEY, - 'attributes' => ['hostname'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_sdk', - 'type' => Database::INDEX_KEY, - 'attributes' => ['sdk'], - 'lengths' => [], - 'orders' => [], - ], + Index::key(key: '_key_actor_internal_and_event', attributes: ['actorInternalId', 'event']), + Index::key(key: '_key_project_internal_id', attributes: ['projectInternalId']), + Index::key(key: '_key_team_internal_id', attributes: ['teamInternalId']), + Index::key(key: '_key_actor_internal_id', attributes: ['actorInternalId']), + Index::key(key: '_key_actor_type', attributes: ['actorType']), + Index::key(key: '_key_country', attributes: ['country']), + Index::key(key: '_key_hostname', attributes: ['hostname']), + Index::key(key: '_key_sdk', attributes: ['sdk']), ]; } @@ -879,9 +509,7 @@ private function getColumnTypeMap(): array $map = ['id' => 'String']; foreach ($this->getAttributes() as $attribute) { - /** @var string $id */ - $id = $attribute['$id']; - $map[$id] = ($attribute['type'] ?? null) === Database::VAR_DATETIME + $map[$attribute->key] = $attribute->type === ColumnType::Datetime ? 'DateTime64(3)' : 'String'; } @@ -1058,7 +686,7 @@ public function setup(): void continue; } - $column = $table->addColumn($id, ($attribute['type'] ?? null) === Database::VAR_DATETIME + $column = $table->addColumn($id, $attribute->type === ColumnType::Datetime ? ColumnType::Datetime : ColumnType::String); if (\in_array($id, self::LOW_CARDINALITY_COLUMNS, true)) { @@ -2137,11 +1765,11 @@ protected function getColumnDefinition(string $id): string // Dynamically determine type based on attribute metadata // DateTime attributes use DateTime64(3), all others use String - $type = (isset($attribute['type']) && $attribute['type'] === Database::VAR_DATETIME) + $type = $attribute->type === ColumnType::Datetime ? 'DateTime64(3)' : 'String'; - $required = (bool) $attribute['required']; + $required = $attribute->required; if ($type === 'String' && \in_array($id, self::LOW_CARDINALITY_COLUMNS, true)) { $columnType = $required diff --git a/src/Audit/Adapter/Database.php b/src/Audit/Adapter/Database.php index 796f598..cc2a5de 100644 --- a/src/Audit/Adapter/Database.php +++ b/src/Audit/Adapter/Database.php @@ -4,6 +4,7 @@ use Exception; use Utopia\Audit\Log; +use Utopia\Database\Collection; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as AuthorizationException; @@ -48,7 +49,7 @@ public function ping(): bool /** * Setup database structure. * - * @throws \Exception + * @throws Exception */ public function setup(): void { @@ -60,11 +61,7 @@ public function setup(): void $indexes = $this->getIndexDocuments(); try { - $this->db->createCollection( - $this->getCollectionName(), - $attributes, - $indexes, - ); + $this->db->createCollection(new Collection(id: $this->getCollectionName(), attributes: $attributes, indexes: $indexes)); } catch (DuplicateException) { // Collection already exists } @@ -73,13 +70,14 @@ public function setup(): void /** * Create an audit log entry. * - * @param array $log - * @throws AuthorizationException|\Exception + * @param array $log + * + * @throws AuthorizationException|Exception */ public function create(array $log): Log { $log['time'] ??= DateTime::now(); - $document = $this->db->getAuthorization()->skip(fn(): \Utopia\Database\Document => $this->db->createDocument($this->getCollectionName(), new Document($log))); + $document = $this->db->getAuthorization()->skip(fn (): Document => $this->db->createDocument($this->getCollectionName(), new Document($log))); return new Log($document->getArrayCopy()); } @@ -87,19 +85,21 @@ public function create(array $log): Log /** * Create multiple audit log entries in batch. * - * @param array> $logs - * @throws AuthorizationException|\Exception + * @param array> $logs + * + * @throws AuthorizationException|Exception */ public function createBatch(array $logs): bool { $this->db->getAuthorization()->skip(function () use ($logs): void { - $documents = array_map(function (array $log): \Utopia\Database\Document { - $time = $log['time'] ?? new \DateTime(); + $documents = array_map(function (array $log): Document { + $time = $log['time'] ?? new \DateTime; if (\is_string($time)) { $time = new \DateTime($time); } \assert($time instanceof \DateTime); $log['time'] = DateTime::format($time); + return new Document($log); }, $logs); $this->db->createDocuments($this->getCollectionName(), $documents); @@ -112,11 +112,12 @@ public function createBatch(array $logs): bool * Get a single log by its ID. * * @return Log|null The log entry or null if not found - * @throws AuthorizationException|\Exception + * + * @throws AuthorizationException|Exception */ public function getById(string $id): ?Log { - $document = $this->db->getAuthorization()->skip(fn(): \Utopia\Database\Document => $this->db->getDocument($this->getCollectionName(), $id)); + $document = $this->db->getAuthorization()->skip(fn (): Document => $this->db->getDocument($this->getCollectionName(), $id)); if ($document->isEmpty()) { return null; @@ -139,6 +140,7 @@ private function buildTimeQueries(?\DateTime $after, ?\DateTime $before): array if ($afterStr !== null && $beforeStr !== null) { $queries[] = Query::between('time', $afterStr, $beforeStr); + return $queries; } @@ -157,7 +159,8 @@ private function buildTimeQueries(?\DateTime $after, ?\DateTime $before): array * Get audit logs by user ID. * * @return array - * @throws AuthorizationException|\Exception + * + * @throws AuthorizationException|Exception */ public function getByUser( string $userId, @@ -183,13 +186,13 @@ public function getByUser( ); }); - return array_map(fn(\Utopia\Database\Document $doc): \Utopia\Audit\Log => new Log($doc->getArrayCopy()), $documents); + return array_map(fn (Document $doc): Log => new Log($doc->getArrayCopy()), $documents); } /** * Count audit logs by user ID. * - * @throws AuthorizationException|\Exception + * @throws AuthorizationException|Exception */ public function countByUser( string $userId, @@ -198,7 +201,8 @@ public function countByUser( ?int $max = null, ): int { $timeQueries = $this->buildTimeQueries($after, $before); - return $this->db->getAuthorization()->skip(fn(): int => $this->db->count( + + return $this->db->getAuthorization()->skip(fn (): int => $this->db->count( collection: $this->getCollectionName(), queries: [ Query::equal('userId', [$userId]), @@ -212,6 +216,7 @@ public function countByUser( * Get logs by resource. * * @return array + * * @throws Timeout|\Utopia\Database\Exception|\Utopia\Database\Exception\Query */ public function getByResource( @@ -238,7 +243,7 @@ public function getByResource( ); }); - return array_map(fn(\Utopia\Database\Document $doc): \Utopia\Audit\Log => new Log($doc->getArrayCopy()), $documents); + return array_map(fn (Document $doc): Log => new Log($doc->getArrayCopy()), $documents); } /** @@ -253,7 +258,8 @@ public function countByResource( ?int $max = null, ): int { $timeQueries = $this->buildTimeQueries($after, $before); - return $this->db->getAuthorization()->skip(fn(): int => $this->db->count( + + return $this->db->getAuthorization()->skip(fn (): int => $this->db->count( collection: $this->getCollectionName(), queries: [ Query::equal('resource', [$resource]), @@ -266,8 +272,9 @@ public function countByResource( /** * Get logs by user and events. * - * @param array $events + * @param array $events * @return array + * * @throws Timeout|\Utopia\Database\Exception|\Utopia\Database\Exception\Query */ public function getByUserAndEvents( @@ -296,13 +303,14 @@ public function getByUserAndEvents( ); }); - return array_map(fn(\Utopia\Database\Document $doc): \Utopia\Audit\Log => new Log($doc->getArrayCopy()), $documents); + return array_map(fn (Document $doc): Log => new Log($doc->getArrayCopy()), $documents); } /** * Count logs by user and events. * - * @param array $events + * @param array $events + * * @throws \Utopia\Database\Exception */ public function countByUserAndEvents( @@ -313,7 +321,8 @@ public function countByUserAndEvents( ?int $max = null, ): int { $timeQueries = $this->buildTimeQueries($after, $before); - return $this->db->getAuthorization()->skip(fn(): int => $this->db->count( + + return $this->db->getAuthorization()->skip(fn (): int => $this->db->count( collection: $this->getCollectionName(), queries: [ Query::equal('userId', [$userId]), @@ -327,8 +336,9 @@ public function countByUserAndEvents( /** * Get logs by resource and events. * - * @param array $events + * @param array $events * @return array + * * @throws Timeout|\Utopia\Database\Exception|\Utopia\Database\Exception\Query */ public function getByResourceAndEvents( @@ -357,13 +367,14 @@ public function getByResourceAndEvents( ); }); - return array_map(fn(\Utopia\Database\Document $doc): \Utopia\Audit\Log => new Log($doc->getArrayCopy()), $documents); + return array_map(fn (Document $doc): Log => new Log($doc->getArrayCopy()), $documents); } /** * Count logs by resource and events. * - * @param array $events + * @param array $events + * * @throws \Utopia\Database\Exception */ public function countByResourceAndEvents( @@ -374,7 +385,8 @@ public function countByResourceAndEvents( ?int $max = null, ): int { $timeQueries = $this->buildTimeQueries($after, $before); - return $this->db->getAuthorization()->skip(fn(): int => $this->db->count( + + return $this->db->getAuthorization()->skip(fn (): int => $this->db->count( collection: $this->getCollectionName(), queries: [ Query::equal('resource', [$resource]), @@ -388,9 +400,7 @@ public function countByResourceAndEvents( /** * Delete logs older than the specified datetime. * - * @param \DateTime $datetime - /** - * @throws AuthorizationException|\Exception + * @throws AuthorizationException|Exception */ public function cleanup(\DateTime $datetime): bool { @@ -422,24 +432,21 @@ public function cleanup(\DateTime $datetime): bool * type mapping internally. However, this implementation is required to satisfy * the abstract method declaration in the base SQL adapter. * - * @param string $id Attribute identifier + * @param string $id Attribute identifier * @return string Database-agnostic column description + * * @throws Exception */ protected function getColumnDefinition(string $id): string { $attribute = $this->getAttribute($id); - if (!$attribute) { + if (! $attribute) { throw new Exception("Attribute {$id} not found"); } - // For the Database adapter, we use Utopia's VAR_* type constants internally - // This method provides a description for reference purposes - /** @var string $type */ - $type = $attribute['type']; - /** @var int $size */ - $size = $attribute['size'] ?? 0; + $type = $attribute->type->value; + $size = $attribute->size; if ($size > 0) { return "{$id}: {$type}({$size})"; @@ -453,17 +460,18 @@ protected function getColumnDefinition(string $id): string * * Translates Audit Query objects to Database Query objects. * - * @param array<\Utopia\Audit\Query> $queries - * @return array<\Utopia\Audit\Log> - * @throws AuthorizationException|\Exception + * @param array<\Utopia\Audit\Query> $queries + * @return array + * + * @throws AuthorizationException|Exception */ public function find(array $queries = []): array { $dbQueries = []; foreach ($queries as $query) { - if (!($query instanceof \Utopia\Audit\Query)) { - throw new \Exception('Invalid query type. Expected Utopia\\Audit\\Query'); + if (! ($query instanceof \Utopia\Audit\Query)) { + throw new Exception('Invalid query type. Expected Utopia\\Audit\\Query'); } // Convert Audit Query to Database Query @@ -471,12 +479,12 @@ public function find(array $queries = []): array $dbQueries[] = Query::parseQuery($query->toArray()); } - $documents = $this->db->getAuthorization()->skip(fn(): array => $this->db->find( + $documents = $this->db->getAuthorization()->skip(fn (): array => $this->db->find( collection: $this->getCollectionName(), queries: $dbQueries, )); - return array_map(fn(\Utopia\Database\Document $doc): \Utopia\Audit\Log => new Log($doc->getArrayCopy()), $documents); + return array_map(fn (Document $doc): Log => new Log($doc->getArrayCopy()), $documents); } /** @@ -485,17 +493,18 @@ public function find(array $queries = []): array * Translates Audit Query objects to Database Query objects. * Ignores limit, offset, and cursor queries as they don't apply to count. * - * @param array<\Utopia\Audit\Query> $queries - * @param int|null $max Optional upper bound (inclusive) for the count - * @throws AuthorizationException|\Exception + * @param array<\Utopia\Audit\Query> $queries + * @param int|null $max Optional upper bound (inclusive) for the count + * + * @throws AuthorizationException|Exception */ public function count(array $queries = [], ?int $max = null): int { $dbQueries = []; foreach ($queries as $query) { - if (!($query instanceof \Utopia\Audit\Query)) { - throw new \Exception('Invalid query type. Expected Utopia\\Audit\\Query'); + if (! ($query instanceof \Utopia\Audit\Query)) { + throw new Exception('Invalid query type. Expected Utopia\\Audit\\Query'); } // Skip limit, offset, and cursor queries — they don't apply to count @@ -519,7 +528,7 @@ public function count(array $queries = [], ?int $max = null): int $dbQueries[] = Query::parseQuery($queryArray); } - return $this->db->getAuthorization()->skip(fn(): int => $this->db->count( + return $this->db->getAuthorization()->skip(fn (): int => $this->db->count( collection: $this->getCollectionName(), queries: $dbQueries, max: $max, diff --git a/src/Audit/Adapter/SQL.php b/src/Audit/Adapter/SQL.php index f5208a3..10afbb8 100644 --- a/src/Audit/Adapter/SQL.php +++ b/src/Audit/Adapter/SQL.php @@ -3,8 +3,9 @@ namespace Utopia\Audit\Adapter; use Utopia\Audit\Adapter; +use Utopia\Database\Attribute; use Utopia\Database\Database; -use Utopia\Database\Document; +use Utopia\Database\Index; /** * Base SQL Adapter for Audit @@ -27,152 +28,63 @@ public function getCollectionName(): string /** * Get attribute definitions for audit logs. * - * Each attribute is an array with the following string keys: - * - $id: string (attribute identifier) - * - type: string - * - size: int - * - required: bool - * - signed: bool - * - array: bool - * - filters: array - * - * @return array> + * @return array */ public function getAttributes(): array { return [ - [ - '$id' => 'userId', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'event', - 'type' => Database::VAR_STRING, - 'size' => 255, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'resource', - 'type' => Database::VAR_STRING, - 'size' => 255, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'userAgent', - 'type' => Database::VAR_STRING, - 'size' => 65534, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'ip', - 'type' => Database::VAR_STRING, - 'size' => 45, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'time', - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => true, - 'required' => false, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => 'data', - 'type' => Database::VAR_STRING, - 'size' => 16777216, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => ['json'], - ], + Attribute::string(key: 'userId'), + Attribute::string(key: 'event', required: true), + Attribute::string(key: 'resource'), + Attribute::string(key: 'userAgent', size: 65534, required: true), + Attribute::string(key: 'ip', size: 45, required: true), + Attribute::datetime(key: 'time', filters: ['datetime']), + Attribute::string(key: 'data', size: 16777216, filters: ['json']), ]; } /** - * Get attribute documents for audit logs. + * Get attribute value objects for createCollection. * - * @return array + * @return array */ public function getAttributeDocuments(): array { - return array_map(static fn(array $attribute): \Utopia\Database\Document => new Document($attribute), $this->getAttributes()); + return $this->getAttributes(); } /** * Get index definitions for audit logs. * - * Each index is an array with the following string keys: - * - $id: string (index identifier) - * - type: string - * - attributes: array - * - * @return array> + * @return array */ public function getIndexes(): array { return [ - [ - '$id' => 'idx_event', - 'type' => 'key', - 'attributes' => ['event'], - ], - [ - '$id' => 'idx_userId_event', - 'type' => 'key', - 'attributes' => ['userId', 'event'], - ], - [ - '$id' => 'idx_resource_event', - 'type' => 'key', - 'attributes' => ['resource', 'event'], - ], - [ - '$id' => 'idx_time_desc', - 'type' => 'key', - 'attributes' => ['time'], - ], + Index::key(key: 'idx_event', attributes: ['event']), + Index::key(key: 'idx_userId_event', attributes: ['userId', 'event']), + Index::key(key: 'idx_resource_event', attributes: ['resource', 'event']), + Index::key(key: 'idx_time_desc', attributes: ['time']), ]; } /** - * Get index documents for audit logs. + * Get index value objects for createCollection. * - * @return array + * @return array */ public function getIndexDocuments(): array { - return array_map(static fn(array $index): \Utopia\Database\Document => new Document($index), $this->getIndexes()); + return $this->getIndexes(); } /** - * Get a single attribute by ID. - * - * @return array|null + * Get a single attribute by key. */ - protected function getAttribute(string $id) + protected function getAttribute(string $id): ?Attribute { foreach ($this->getAttributes() as $attribute) { - if ($attribute['$id'] === $id) { + if ($attribute->key === $id) { return $attribute; } } @@ -184,7 +96,7 @@ protected function getAttribute(string $id) * Get SQL column definition for a given attribute ID. * This method is database-specific and must be implemented by each concrete adapter. * - * @param string $id Attribute identifier + * @param string $id Attribute identifier * @return string Database-specific column definition */ abstract protected function getColumnDefinition(string $id): string; @@ -199,9 +111,7 @@ protected function getAllColumnDefinitions(): array { $definitions = []; foreach ($this->getAttributes() as $attribute) { - /** @var string $id */ - $id = $attribute['$id']; - $definitions[] = $this->getColumnDefinition($id); + $definitions[] = $this->getColumnDefinition($attribute->key); } return $definitions; diff --git a/tests/Audit/Adapter/ClickHouseTest.php b/tests/Audit/Adapter/ClickHouseTest.php index cc0b061..d2affae 100644 --- a/tests/Audit/Adapter/ClickHouseTest.php +++ b/tests/Audit/Adapter/ClickHouseTest.php @@ -9,6 +9,9 @@ use Utopia\Audit\Adapter\ClickHouse; use Utopia\Audit\Audit; use Utopia\Audit\Query; +use Utopia\Database\Attribute; +use Utopia\Database\Index; +use Utopia\Query\Schema\ColumnType; use Utopia\Tests\Audit\AuditBase; /** @@ -515,7 +518,7 @@ public function testClickHouseAdapterAttributes(): void ); $attributes = $adapter->getAttributes(); - $attributeIds = array_map(fn(array $attr): mixed => $attr['$id'], $attributes); + $attributeIds = array_map(fn (Attribute $attr): string => $attr->key, $attributes); // Verify all expected attributes exist $expectedAttributes = [ @@ -627,7 +630,7 @@ public function testPremiumGeoAttributesAreOptionalStrings(): void $attributes = $adapter->getAttributes(); $byId = []; foreach ($attributes as $attribute) { - $byId[$attribute['$id']] = $attribute; + $byId[$attribute->key] = $attribute; } $geoColumns = [ @@ -644,9 +647,9 @@ public function testPremiumGeoAttributesAreOptionalStrings(): void foreach ($geoColumns as $column) { $this->assertArrayHasKey($column, $byId, "Premium geo attribute '{$column}' not found"); - $this->assertEquals(\Utopia\Database\Database::VAR_STRING, $byId[$column]['type'], "'{$column}' should be a string"); - $this->assertFalse($byId[$column]['required'], "'{$column}' should be optional"); - $this->assertFalse($byId[$column]['array'], "'{$column}' should not be an array"); + $this->assertSame(ColumnType::String, $byId[$column]->type, "'{$column}' should be a string"); + $this->assertFalse($byId[$column]->required, "'{$column}' should be optional"); + $this->assertFalse($byId[$column]->array, "'{$column}' should not be an array"); } } @@ -805,7 +808,7 @@ public function testClickHouseAdapterIndexes(): void ); $indexes = $adapter->getIndexes(); - $indexIds = array_map(fn(array $idx): mixed => $idx['$id'], $indexes); + $indexIds = array_map(fn (Index $idx): string => $idx->key, $indexes); // Verify all ClickHouse-specific indexes exist $expectedClickHouseIndexes = [ diff --git a/tests/Audit/Adapter/DatabaseTest.php b/tests/Audit/Adapter/DatabaseTest.php index f1a818c..3a9ed86 100644 --- a/tests/Audit/Adapter/DatabaseTest.php +++ b/tests/Audit/Adapter/DatabaseTest.php @@ -28,7 +28,9 @@ protected function initializeAudit(): void $dbUser = 'root'; $dbPass = 'password'; - $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPdoAttributes()); + $attributes = MariaDB::getPdoAttributes(); + $attributes[PDO::ATTR_PERSISTENT] = false; + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, $attributes); $cache = new Cache(new NoCache()); $database = new Database(new MariaDB($pdo), $cache); $database->setDatabase('utopiaTests');