From 107a6332c01da5c08bc2b31a158cf08acc0ac792 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 3 Oct 2023 11:37:06 +0300 Subject: [PATCH 01/15] Check database before create --- src/Database/Adapter.php | 4 +-- src/Database/Adapter/MariaDB.php | 4 +++ src/Database/Adapter/SQL.php | 34 ++++++++----------------- src/Database/Adapter/SQLite.php | 7 +++-- tests/Database/Adapter/MariaDBTest.php | 5 ---- tests/Database/Adapter/MongoDBTest.php | 5 ---- tests/Database/Adapter/MySQLTest.php | 5 ---- tests/Database/Adapter/PostgresTest.php | 5 ---- 8 files changed, 19 insertions(+), 50 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index decfd7db4..4f93d4b39 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -156,11 +156,11 @@ abstract public function create(string $name): bool; * Optionally check if collection exists in database * * @param string $database database name - * @param string $collection (optional) collection name + * @param string|null $collection (optional) collection name * * @return bool */ - abstract public function exists(string $database, ?string $collection): bool; + abstract public function exists(string $database, ?string $collection = null): bool; /** * List Databases diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index d4647f754..3eadd0739 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -27,6 +27,10 @@ public function create(string $name): bool { $name = $this->filter($name); + if ($this->exists($name)) { + return true; + } + return $this->getPDO() ->prepare("CREATE DATABASE IF NOT EXISTS `{$name}` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;") ->execute(); diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 8525d2738..c962cc30e 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -50,41 +50,27 @@ public function ping(): bool * @return bool * @throws Exception */ - public function exists(string $database, ?string $collection): bool + public function exists(string $database, ?string $collection = null): bool { $database = $this->filter($database); if (!\is_null($collection)) { $collection = $this->filter($collection); - - $select = 'TABLE_NAME'; - $from = 'INFORMATION_SCHEMA.TABLES'; - $where = 'TABLE_SCHEMA = :schema AND TABLE_NAME = :table'; - $match = "{$this->getNamespace()}_{$collection}"; - } else { - $select = 'SCHEMA_NAME'; - $from = 'INFORMATION_SCHEMA.SCHEMATA'; - $where = 'SCHEMA_NAME = :schema'; - $match = $database; - } - - $stmt = $this->getPDO() - ->prepare("SELECT {$select} - FROM {$from} - WHERE {$where};"); - - $stmt->bindValue(':schema', $database, PDO::PARAM_STR); - - if (!\is_null($collection)) { + $stmt = $this->getPDO()->prepare("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = :schema AND TABLE_NAME = :table"); + $stmt->bindValue(':schema', $database, PDO::PARAM_STR); $stmt->bindValue(':table', "{$this->getNamespace()}_{$collection}", PDO::PARAM_STR); + } else { + $stmt = $this->getPDO()->prepare("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = :schema"); + $stmt->bindValue(':schema', $database, PDO::PARAM_STR); } $stmt->execute(); - $document = $stmt->fetch(); + if (empty($document)) { + return false; + } - return (($document[$select] ?? '') === $match) || // case insensitive check - (($document[strtolower($select)] ?? '') === $match); + return true; } /** diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 932de4f3f..d2398d204 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -32,12 +32,11 @@ class SQLite extends MariaDB * Optionally check if collection exists in Database * * @param string $database - * @param string $collection + * @param string|null $collection * @return bool - * @throws Exception - * @throws PDOException + * @throws DatabaseException */ - public function exists(string $database, ?string $collection): bool + public function exists(string $database, ?string $collection = null): bool { $database = $this->filter($database); diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index af616f260..0f36f2427 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -49,11 +49,6 @@ public static function getDatabase(): Database $database = new Database(new MariaDB($pdo), $cache); $database->setDefaultDatabase('utopiaTests'); $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - $database->create(); return self::$database = $database; diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 62c48ae37..649064cd9 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -54,11 +54,6 @@ public static function getDatabase(): Database $database = new Database(new Mongo($client), $cache); $database->setDefaultDatabase($schema); $database->setNamespace('myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - $database->create(); return self::$database = $database; diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index 0faefca61..a06bf233f 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -60,11 +60,6 @@ public static function getDatabase(): Database $database = new Database(new MySQL($pdo), $cache); $database->setDefaultDatabase('utopiaTests'); $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - $database->create(); return self::$database = $database; diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index 9965a8684..e4b6bd37f 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -47,11 +47,6 @@ public static function getDatabase(): Database $database = new Database(new Postgres($pdo), $cache); $database->setDefaultDatabase('utopiaTests'); $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - $database->create(); return self::$database = $database; From 76d30511b7552628892284a80c4e30ed2b409ceb Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 8 Oct 2023 18:16:19 +0300 Subject: [PATCH 02/15] push again --- composer.lock | 10 +++++----- src/Database/Adapter/MariaDB.php | 2 +- tests/Database/Base.php | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 3ab0753d4..d476dd777 100644 --- a/composer.lock +++ b/composer.lock @@ -839,16 +839,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.35", + "version": "1.10.38", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3" + "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e730e5facb75ffe09dfb229795e8c01a459f26c3", - "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691", + "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T15:27:56+00:00" + "time": "2023-10-06T14:19:14+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index d4647f754..032b9b314 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -659,7 +659,7 @@ public function createDocument(string $collection, Document $document): Document try { $stmt->execute(); - $document['$internalId'] = $this->getDocument($collection, $document->getId())->getInternalId(); + $document['$internalId'] = $this->getDocument($collection, $document->getId(), [Query::select(['$internalId'])])->getInternalId(); if (isset($stmtPermissions)) { $stmtPermissions->execute(); diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 34609d113..0719c4dca 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -2005,6 +2005,31 @@ public function testFindByInternalID(array $data): void $this->assertEquals(1, count($documents)); } + /** + * @return void + * @throws \Utopia\Database\Exception + */ + public function testSelectInternalID(): void + { + $documents = static::getDatabase()->find('movies', [ + Query::select(['$internalId', '$id']), + Query::orderAsc(''), + Query::limit(1), + ]); + + $document = $documents[0]; + + $this->assertArrayHasKey('$internalId', $document); + $this->assertCount(2, $document); + + $document = static::getDatabase()->getDocument('movies', $document->getId(), [ + Query::select(['$internalId']), + ]); + + $this->assertArrayHasKey('$internalId', $document); + $this->assertCount(1, $document); + } + public function testFindOrderBy(): void { /** From 9a96081fb098c2c18d8bac9be940d5f4d244e7d9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 19 Oct 2023 15:56:34 +0300 Subject: [PATCH 03/15] Pull changes --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 2d1adac29..3f23d132d 100644 --- a/composer.lock +++ b/composer.lock @@ -2608,5 +2608,5 @@ "php": ">=8.0" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } From fb4530ccafb6019dcec83d2546dcfc99826c6eaa Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 29 Oct 2023 16:02:03 +0200 Subject: [PATCH 04/15] Revert delete schema test --- tests/Database/Adapter/MariaDBTest.php | 5 +++++ tests/Database/Adapter/MongoDBTest.php | 5 +++++ tests/Database/Adapter/MySQLTest.php | 5 +++++ tests/Database/Adapter/PostgresTest.php | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 0f36f2427..af616f260 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -49,6 +49,11 @@ public static function getDatabase(): Database $database = new Database(new MariaDB($pdo), $cache); $database->setDefaultDatabase('utopiaTests'); $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + $database->create(); return self::$database = $database; diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 649064cd9..62c48ae37 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -54,6 +54,11 @@ public static function getDatabase(): Database $database = new Database(new Mongo($client), $cache); $database->setDefaultDatabase($schema); $database->setNamespace('myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + $database->create(); return self::$database = $database; diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index a06bf233f..0faefca61 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -60,6 +60,11 @@ public static function getDatabase(): Database $database = new Database(new MySQL($pdo), $cache); $database->setDefaultDatabase('utopiaTests'); $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + $database->create(); return self::$database = $database; diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index e4b6bd37f..9965a8684 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -47,6 +47,11 @@ public static function getDatabase(): Database $database = new Database(new Postgres($pdo), $cache); $database->setDefaultDatabase('utopiaTests'); $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + $database->create(); return self::$database = $database; From 77beada047e1a99e12616d143394e439331cf663 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 29 Oct 2023 16:13:07 +0200 Subject: [PATCH 05/15] Merge main updates --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 91cb09a27..78b0fa67b 100644 --- a/composer.lock +++ b/composer.lock @@ -513,16 +513,16 @@ }, { "name": "laravel/pint", - "version": "v1.13.3", + "version": "v1.13.5", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "93b2d0d49719bc6e444ba21cd4dbbccec935413d" + "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/93b2d0d49719bc6e444ba21cd4dbbccec935413d", - "reference": "93b2d0d49719bc6e444ba21cd4dbbccec935413d", + "url": "https://api.github.com/repos/laravel/pint/zipball/df105cf8ce7a8f0b8a9425ff45cd281a5448e423", + "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423", "shasum": "" }, "require": { @@ -534,12 +534,12 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.34.1", - "illuminate/view": "^10.23.1", + "illuminate/view": "^10.26.2", "laravel-zero/framework": "^10.1.2", "mockery/mockery": "^1.6.6", "nunomaduro/larastan": "^2.6.4", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.18.2" + "pestphp/pest": "^2.20.0" }, "bin": [ "builds/pint" @@ -575,7 +575,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-10-10T15:39:09+00:00" + "time": "2023-10-26T09:26:10+00:00" }, { "name": "myclabs/deep-copy", @@ -2608,5 +2608,5 @@ "php": ">=8.0" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } From 8357740cc5f88b45a1f2f3e829023840020b4b73 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Nov 2023 19:00:56 +0200 Subject: [PATCH 06/15] mysql image as cloud --- docker-compose.yml | 2 +- src/Database/Database.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 296cd6095..af5a18be8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: MONGO_INITDB_ROOT_PASSWORD: example mysql: - image: mysql:8.0.31 + image: mysql:8.0.33 container_name: utopia-mysql networks: - database diff --git a/src/Database/Database.php b/src/Database/Database.php index d8393ac8f..4ee1725db 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3834,11 +3834,11 @@ public function deleteDocument(string $collection, string $id): bool $document = $this->silent(fn () => $this->deleteDocumentRelationships($collection, $document)); } + $deleted = $this->adapter->deleteDocument($collection->getId(), $id); + $this->purgeRelatedDocuments($collection, $id); $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); - $deleted = $this->adapter->deleteDocument($collection->getId(), $id); - $this->trigger(self::EVENT_DOCUMENT_DELETE, $document); return $deleted; From 656d4af49ed882fcf458ee802d6648f654a9aaac Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 8 Nov 2023 11:04:45 +0200 Subject: [PATCH 07/15] getId() on null --- src/Database/Database.php | 5 +++++ tests/Database/Base.php | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 4ee1725db..8ddc7de20 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3880,6 +3880,8 @@ private function deleteDocumentRelationships(Document $collection, Document $doc $this->deleteRestrict($relatedCollection, $document, $value, $relationType, $twoWay, $twoWayKey, $side); break; case Database::RELATION_MUTATE_SET_NULL: + var_dump("inininininininininini RELATION_MUTATE_SET_NULL ninininininininininininininininin"); + var_dump($value); $this->deleteSetNull($collection, $relatedCollection, $document, $value, $relationType, $twoWay, $twoWayKey, $side); break; case Database::RELATION_MUTATE_CASCADE: @@ -4038,6 +4040,9 @@ private function deleteSetNull(Document $collection, Document $relatedCollection Query::equal($twoWayKey, [$document->getId()]) ]); } else { + if(empty($value)) { + return; + } $related = $this->getDocument($relatedCollection->getId(), $value->getId()); } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 31b0dc9df..bc30b877d 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -5571,6 +5571,18 @@ public function testOneToOneTwoWayRelationship(): void onDelete: Database::RELATION_MUTATE_SET_NULL ); + + + $city1 = static::getDatabase()->getDocument('city', 'city1'); + var_dump($city1); + static::getDatabase()->updateDocument('city', 'city1', new Document(['newCountry' => null, '$id' => 'city1'])); + + // Check Delete TwoWay = TRUE with related value is NULL + $this->assertTrue(static::getDatabase()->deleteDocument('city', 'city1')); + // $this->assertTrue(false); + + + // Delete parent, will set child relationship to null for two-way static::getDatabase()->deleteDocument('country', 'country1'); From a2299ff52fffa93a9ba6fb1d458b4573df125e4f Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 8 Nov 2023 11:16:12 +0200 Subject: [PATCH 08/15] Remove comments --- phpunit.xml | 2 +- src/Database/Database.php | 2 -- tests/Database/Base.php | 13 +++++-------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 31b947dd6..3833748e0 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ diff --git a/src/Database/Database.php b/src/Database/Database.php index 8ddc7de20..f6bd3cdec 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3880,8 +3880,6 @@ private function deleteDocumentRelationships(Document $collection, Document $doc $this->deleteRestrict($relatedCollection, $document, $value, $relationType, $twoWay, $twoWayKey, $side); break; case Database::RELATION_MUTATE_SET_NULL: - var_dump("inininininininininini RELATION_MUTATE_SET_NULL ninininininininininininininininin"); - var_dump($value); $this->deleteSetNull($collection, $relatedCollection, $document, $value, $relationType, $twoWay, $twoWayKey, $side); break; case Database::RELATION_MUTATE_CASCADE: diff --git a/tests/Database/Base.php b/tests/Database/Base.php index bc30b877d..680f88112 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -5571,17 +5571,14 @@ public function testOneToOneTwoWayRelationship(): void onDelete: Database::RELATION_MUTATE_SET_NULL ); - - - $city1 = static::getDatabase()->getDocument('city', 'city1'); - var_dump($city1); static::getDatabase()->updateDocument('city', 'city1', new Document(['newCountry' => null, '$id' => 'city1'])); + $city1 = static::getDatabase()->getDocument('city', 'city1'); + $this->assertNull($city1->getAttribute('newCountry')); - // Check Delete TwoWay = TRUE with related value is NULL + // Check Delete TwoWay TRUE && RELATION_MUTATE_SET_NULL && related value NULL $this->assertTrue(static::getDatabase()->deleteDocument('city', 'city1')); - // $this->assertTrue(false); - - + $city1 = static::getDatabase()->getDocument('city', 'city1'); + $this->assertTrue($city1->isEmpty()); // Delete parent, will set child relationship to null for two-way static::getDatabase()->deleteDocument('country', 'country1'); From 4659cfbde303fba15d61cd19fb2b584175cfc663 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 8 Nov 2023 11:16:50 +0200 Subject: [PATCH 09/15] php unit --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 3833748e0..31b947dd6 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true"> + stopOnFailure="false"> ./tests/ From 00f7faa31c2ce7abb7f68ed6f5dd90dff45f3fd3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Nov 2023 11:06:41 +1300 Subject: [PATCH 10/15] Remove redundant if not exists --- src/Database/Adapter/MariaDB.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 25fd4a18b..2684d84ab 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -31,7 +31,7 @@ public function create(string $name): bool return true; } - $sql = "CREATE DATABASE IF NOT EXISTS `{$name}` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;"; + $sql = "CREATE DATABASE `{$name}` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;"; $sql = $this->trigger(Database::EVENT_DATABASE_CREATE, $sql); From 33b007d58ed95d0c51da25673b61c222918fcb16 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Nov 2023 11:07:34 +1300 Subject: [PATCH 11/15] Add postgres pre-exists check --- src/Database/Adapter/Postgres.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 6b9e3795c..d065b9850 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -36,7 +36,11 @@ public function create(string $name): bool { $name = $this->filter($name); - $sql = "CREATE SCHEMA IF NOT EXISTS \"{$name}\""; + if ($this->exists($name)) { + return true; + } + + $sql = "CREATE SCHEMA \"{$name}\""; $sql = $this->trigger(Database::EVENT_DATABASE_CREATE, $sql); return $this->getPDO() From dd2a9dcbcd84d83aa3486048a9e946d005413af2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Nov 2023 19:24:00 +0200 Subject: [PATCH 12/15] transactions --- src/Database/Adapter/MariaDB.php | 129 +++++++++++++++---------------- src/Database/Adapter/SQL.php | 2 +- 2 files changed, 63 insertions(+), 68 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 2684d84ab..ea890eb0f 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -646,6 +646,7 @@ public function deleteIndex(string $collection, string $id): bool * @throws Exception * @throws PDOException * @throws DuplicateException + * @throws \Throwable */ public function createDocument(string $collection, Document $document): Document { @@ -658,12 +659,6 @@ public function createDocument(string $collection, Document $document): Document $columns = ''; $columnNames = ''; - try { - $this->getPDO()->beginTransaction(); - } catch (PDOException $e) { - $this->getPDO()->rollBack(); - } - /** * Insert Attributes */ @@ -732,6 +727,7 @@ public function createDocument(string $collection, Document $document): Document } try { + $this->getPDO()->beginTransaction(); $stmt->execute(); $document['$internalId'] = $this->getDocument($collection, $document->getId())->getInternalId(); @@ -739,20 +735,20 @@ public function createDocument(string $collection, Document $document): Document if (isset($stmtPermissions)) { $stmtPermissions->execute(); } - } catch (PDOException $e) { + + $this->getPDO()->commit(); + } catch (\Throwable $e) { $this->getPDO()->rollBack(); - switch ($e->getCode()) { - case 1062: - case 23000: - throw new DuplicateException('Duplicated document: ' . $e->getMessage()); - default: - throw $e; + if($e instanceof PDOException) { + switch ($e->getCode()) { + case 1062: + case 23000: + throw new DuplicateException('Duplicated document: ' . $e->getMessage()); + } } - } - if (!$this->getPDO()->commit()) { - throw new DatabaseException('Failed to commit transaction'); + throw $e; } return $document; @@ -768,6 +764,7 @@ public function createDocument(string $collection, Document $document): Document * @return array * * @throws DuplicateException + * @throws \Throwable */ public function createDocuments(string $collection, array $documents, int $batchSize = Database::INSERT_BATCH_SIZE): array { @@ -775,9 +772,9 @@ public function createDocuments(string $collection, array $documents, int $batch return $documents; } - $this->getPDO()->beginTransaction(); - try { + $this->getPDO()->beginTransaction(); + $name = $this->filter($collection); $batches = \array_chunk($documents, max(1, $batchSize)); @@ -845,20 +842,22 @@ public function createDocuments(string $collection, array $documents, int $batch } } - if (!$this->getPDO()->commit()) { - throw new Exception('Failed to commit transaction'); - } - - return $documents; - - } catch (PDOException $e) { + $this->getPDO()->commit(); + } catch (\Throwable $e) { $this->getPDO()->rollBack(); - throw match ($e->getCode()) { - 1062, 23000 => new DuplicateException('Duplicated document: ' . $e->getMessage()), - default => $e, - }; + if($e instanceof PDOException) { + switch ($e->getCode()) { + case 1062: + case 23000: + throw new DuplicateException('Duplicated document: ' . $e->getMessage()); + } + } + + throw $e; } + + return $documents; } /** @@ -870,6 +869,7 @@ public function createDocuments(string $collection, array $documents, int $batch * @throws Exception * @throws PDOException * @throws DuplicateException + * @throws \Throwable */ public function updateDocument(string $collection, Document $document): Document { @@ -909,12 +909,6 @@ public function updateDocument(string $collection, Document $document): Document return $carry; }, $initial); - try { - $this->getPDO()->beginTransaction(); - } catch (PDOException $e) { - $this->getPDO()->rollBack(); - } - /** * Get removed Permissions */ @@ -1041,6 +1035,8 @@ public function updateDocument(string $collection, Document $document): Document } try { + $this->getPDO()->beginTransaction(); + $stmt->execute(); if (isset($stmtRemovePermissions)) { @@ -1049,20 +1045,21 @@ public function updateDocument(string $collection, Document $document): Document if (isset($stmtAddPermissions)) { $stmtAddPermissions->execute(); } - } catch (PDOException $e) { + + $this->getPDO()->commit(); + } + catch (\Throwable $e) { $this->getPDO()->rollBack(); - switch ($e->getCode()) { - case 1062: - case 23000: - throw new DuplicateException('Duplicated document: ' . $e->getMessage()); - default: - throw $e; + if($e instanceof PDOException) { + switch ($e->getCode()) { + case 1062: + case 23000: + throw new DuplicateException('Duplicated document: ' . $e->getMessage()); + } } - } - if (!$this->getPDO()->commit()) { - throw new DatabaseException('Failed to commit transaction'); + throw $e; } return $document; @@ -1078,6 +1075,7 @@ public function updateDocument(string $collection, Document $document): Document * @return array * * @throws DuplicateException + * @throws \Throwable */ public function updateDocuments(string $collection, array $documents, int $batchSize = Database::INSERT_BATCH_SIZE): array { @@ -1085,9 +1083,9 @@ public function updateDocuments(string $collection, array $documents, int $batch return $documents; } - $this->getPDO()->beginTransaction(); - try { + $this->getPDO()->beginTransaction(); + $name = $this->filter($collection); $batches = \array_chunk($documents, max(1, $batchSize)); @@ -1268,19 +1266,22 @@ public function updateDocuments(string $collection, array $documents, int $batch } } - if (!$this->getPDO()->commit()) { - throw new Exception('Failed to commit transaction'); - } - - return $documents; - } catch (PDOException $e) { + $this->getPDO()->commit(); + } catch (\Throwable $e) { $this->getPDO()->rollBack(); - throw match ($e->getCode()) { - 1062, 23000 => new DuplicateException('Duplicated document: ' . $e->getMessage()), - default => $e, - }; + if($e instanceof PDOException) { + switch ($e->getCode()) { + case 1062: + case 23000: + throw new DuplicateException('Duplicated document: ' . $e->getMessage()); + } + } + + throw $e; } + + return $documents; } /** @@ -1335,12 +1336,6 @@ public function deleteDocument(string $collection, string $id): bool { $name = $this->filter($collection); - try { - $this->getPDO()->beginTransaction(); - } catch (PDOException $e) { - $this->getPDO()->rollBack(); - } - $sql = " DELETE FROM {$this->getSQLTable($name)} WHERE _uid = :_uid @@ -1363,21 +1358,21 @@ public function deleteDocument(string $collection, string $id): bool $stmtPermissions->bindValue(':_uid', $id); try { + $this->getPDO()->beginTransaction(); + if (!$stmt->execute()) { throw new DatabaseException('Failed to delete document'); } if (!$stmtPermissions->execute()) { throw new DatabaseException('Failed to clean permissions'); } + + $this->getPDO()->commit(); } catch (\Throwable $th) { $this->getPDO()->rollBack(); throw new DatabaseException($th->getMessage()); } - if (!$this->getPDO()->commit()) { - throw new DatabaseException('Failed to commit transaction'); - } - return true; } diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index dff450ff7..344b74b72 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -829,7 +829,7 @@ protected function getSQLPermissionsCondition(string $collection, array $roles): { $roles = array_map(fn (string $role) => $this->getPDO()->quote($role), $roles); return "table_main._uid IN ( - SELECT distinct(_document) + SELECT _document FROM {$this->getSQLTable($collection . '_perms')} WHERE _permission IN (" . implode(', ', $roles) . ") AND _type = 'read' From a0380b0940466aec46d272e8cd6495ae32be1cd8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Nov 2023 19:29:16 +0200 Subject: [PATCH 13/15] formatting --- src/Database/Adapter/MariaDB.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index ea890eb0f..83c9251c6 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1047,8 +1047,7 @@ public function updateDocument(string $collection, Document $document): Document } $this->getPDO()->commit(); - } - catch (\Throwable $e) { + } catch (\Throwable $e) { $this->getPDO()->rollBack(); if($e instanceof PDOException) { From 67fb43f0fe369283450fad125814e60b6eff7e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 23 Nov 2023 08:53:25 +0000 Subject: [PATCH 14/15] Add preserveDates param --- composer.lock | 46 +++++++++++++++++++-------------------- src/Database/Database.php | 32 ++++++++++++++++++--------- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/composer.lock b/composer.lock index 8f3d208b3..e2fa0a161 100644 --- a/composer.lock +++ b/composer.lock @@ -513,16 +513,16 @@ }, { "name": "laravel/pint", - "version": "v1.13.5", + "version": "v1.13.6", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423" + "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/df105cf8ce7a8f0b8a9425ff45cd281a5448e423", - "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423", + "url": "https://api.github.com/repos/laravel/pint/zipball/3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", + "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", "shasum": "" }, "require": { @@ -533,13 +533,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.34.1", - "illuminate/view": "^10.26.2", - "laravel-zero/framework": "^10.1.2", + "friendsofphp/php-cs-fixer": "^3.38.0", + "illuminate/view": "^10.30.1", + "laravel-zero/framework": "^10.3.0", "mockery/mockery": "^1.6.6", "nunomaduro/larastan": "^2.6.4", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.20.0" + "pestphp/pest": "^2.24.2" }, "bin": [ "builds/pint" @@ -575,7 +575,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-10-26T09:26:10+00:00" + "time": "2023-11-07T17:59:57+00:00" }, { "name": "myclabs/deep-copy", @@ -839,16 +839,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.40", + "version": "1.10.44", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d" + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/93c84b5bf7669920d823631e39904d69b9c7dc5d", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b", + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ "type": "tidelift" } ], - "time": "2023-10-30T14:48:31+00:00" + "time": "2023-11-21T16:30:46+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2428,7 +2428,7 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -2475,7 +2475,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2495,16 +2495,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -2533,7 +2533,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -2541,7 +2541,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "utopia-php/cli", @@ -2608,5 +2608,5 @@ "php": ">=8.0" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Database/Database.php b/src/Database/Database.php index f6bd3cdec..a78585bc8 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -2667,6 +2667,7 @@ private function populateDocumentRelationships(Document $collection, Document $d * * @param string $collection * @param Document $document + * @param bool $preserveDates If true, createdAt and updatedAt will not be overwritten * * @return Document * @@ -2674,7 +2675,7 @@ private function populateDocumentRelationships(Document $collection, Document $d * @throws DatabaseException * @throws StructureException */ - public function createDocument(string $collection, Document $document): Document + public function createDocument(string $collection, Document $document, bool $preserveDates = false): Document { $collection = $this->silent(fn () => $this->getCollection($collection)); @@ -2687,11 +2688,14 @@ public function createDocument(string $collection, Document $document): Document $time = DateTime::now(); + $createdAt = $document->getCreatedAt(); + $updatedAt = $document->getUpdatedAt(); + $document ->setAttribute('$id', empty($document->getId()) ? ID::unique() : $document->getId()) ->setAttribute('$collection', $collection->getId()) - ->setAttribute('$createdAt', $time) - ->setAttribute('$updatedAt', $time); + ->setAttribute('$createdAt', empty($createdAt) || !$preserveDates ? $time : $createdAt) + ->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); $document = $this->encode($collection, $document); @@ -2728,6 +2732,7 @@ public function createDocument(string $collection, Document $document): Document * @param string $collection * @param array $documents * @param int $batchSize + * @param bool $preserveDates If true, createdAt and updatedAt will not be overwritten * * @return array * @@ -2735,7 +2740,7 @@ public function createDocument(string $collection, Document $document): Document * @throws StructureException * @throws Exception */ - public function createDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE): array + public function createDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE, bool $preserveDates = false): array { if (empty($documents)) { return []; @@ -2746,11 +2751,14 @@ public function createDocuments(string $collection, array $documents, int $batch $time = DateTime::now(); foreach ($documents as $key => $document) { + $createdAt = $document->getCreatedAt(); + $updatedAt = $document->getUpdatedAt(); + $document ->setAttribute('$id', empty($document->getId()) ? ID::unique() : $document->getId()) ->setAttribute('$collection', $collection->getId()) - ->setAttribute('$createdAt', $time) - ->setAttribute('$updatedAt', $time); + ->setAttribute('$createdAt', empty($createdAt) || !$preserveDates ? $time : $createdAt) + ->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); $document = $this->encode($collection, $document); @@ -3055,6 +3063,7 @@ private function relateDocumentsById( * @param string $collection * @param string $id * @param Document $document + * @param bool $preserveDates If true, updatedAt will not be overwritten * @return Document * * @throws AuthorizationException @@ -3062,7 +3071,7 @@ private function relateDocumentsById( * @throws DatabaseException * @throws StructureException */ - public function updateDocument(string $collection, string $id, Document $document): Document + public function updateDocument(string $collection, string $id, Document $document, bool $preserveDates = false): Document { if (!$document->getId() || !$id) { throw new DatabaseException('Must define $id attribute'); @@ -3176,7 +3185,8 @@ public function updateDocument(string $collection, string $id, Document $documen } if ($shouldUpdate) { - $document->setAttribute('$updatedAt', $time); + $updatedAt = $document->getUpdatedAt(); + $document->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); } // Check if document was updated after the request timestamp @@ -3220,6 +3230,7 @@ public function updateDocument(string $collection, string $id, Document $documen * @param string $collection * @param array $documents * @param int $batchSize + * @param bool $preserveDates If true, updatedAt will not be overwritten * * @return array * @@ -3227,7 +3238,7 @@ public function updateDocument(string $collection, string $id, Document $documen * @throws Exception * @throws StructureException */ - public function updateDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE): array + public function updateDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE, bool $preserveDates = false): array { if (empty($documents)) { return []; @@ -3241,7 +3252,8 @@ public function updateDocuments(string $collection, array $documents, int $batch throw new Exception('Must define $id attribute for each document'); } - $document->setAttribute('$updatedAt', $time); + $updatedAt = $document->getUpdatedAt(); + $document->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); $document = $this->encode($collection, $document); $old = Authorization::skip(fn () => $this->silent( From 0d01c296873d38c22e01918c084b3c754289700d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 23 Nov 2023 10:36:18 +0000 Subject: [PATCH 15/15] Add date preserve tests --- tests/Database/Base.php | 91 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 55c5b9688..c2727ef59 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -75,6 +75,97 @@ public function testCreateExistsDelete(): void $this->assertEquals(true, static::getDatabase()->create()); } + public function testPreserveDatesUpdate(): void + { + Authorization::disable(); + + static::getDatabase()->createCollection('preserve_update_dates'); + + static::getDatabase()->createAttribute('preserve_update_dates', 'attr1', Database::VAR_STRING, 10, false); + + $doc1 = static::getDatabase()->createDocument('preserve_update_dates', new Document([ + '$id' => 'doc1', + '$permissions' => [], + 'attr1' => 'value1', + ])); + + $doc2 = static::getDatabase()->createDocument('preserve_update_dates', new Document([ + '$id' => 'doc2', + '$permissions' => [], + 'attr1' => 'value2', + ])); + + $doc3 = static::getDatabase()->createDocument('preserve_update_dates', new Document([ + '$id' => 'doc3', + '$permissions' => [], + 'attr1' => 'value3', + ])); + + $newDate = '2000-01-01T10:00:00.000+00:00'; + + $doc1->setAttribute('$updatedAt', $newDate); + static::getDatabase()->updateDocument('preserve_update_dates', 'doc1', $doc1, true); + $doc1 = static::getDatabase()->getDocument('preserve_update_dates', 'doc1'); + $this->assertEquals($newDate, $doc1->getAttribute('$updatedAt')); + + $doc2->setAttribute('$updatedAt', $newDate); + $doc3->setAttribute('$updatedAt', $newDate); + static::getDatabase()->updateDocuments('preserve_update_dates', [$doc2, $doc3], 2, true); + + $doc2 = static::getDatabase()->getDocument('preserve_update_dates', 'doc2'); + $doc3 = static::getDatabase()->getDocument('preserve_update_dates', 'doc3'); + $this->assertEquals($newDate, $doc2->getAttribute('$updatedAt')); + $this->assertEquals($newDate, $doc3->getAttribute('$updatedAt')); + + static::getDatabase()->deleteCollection('preserve_update_dates'); + + Authorization::reset(); + } + + public function testPreserveDatesCreate(): void + { + Authorization::disable(); + + static::getDatabase()->createCollection('preserve_create_dates'); + + static::getDatabase()->createAttribute('preserve_create_dates', 'attr1', Database::VAR_STRING, 10, false); + + $date = '2000-01-01T10:00:00.000+00:00'; + + static::getDatabase()->createDocument('preserve_create_dates', new Document([ + '$id' => 'doc1', + '$permissions' => [], + 'attr1' => 'value1', + '$createdAt' => $date + ]), true); + + static::getDatabase()->createDocuments('preserve_create_dates', [ + new Document([ + '$id' => 'doc2', + '$permissions' => [], + 'attr1' => 'value2', + '$createdAt' => $date + ]), + new Document([ + '$id' => 'doc3', + '$permissions' => [], + 'attr1' => 'value3', + '$createdAt' => $date + ]), + ], 2, true); + + $doc1 = static::getDatabase()->getDocument('preserve_create_dates', 'doc1'); + $doc2 = static::getDatabase()->getDocument('preserve_create_dates', 'doc2'); + $doc3 = static::getDatabase()->getDocument('preserve_create_dates', 'doc3'); + $this->assertEquals($date, $doc1->getAttribute('$createdAt')); + $this->assertEquals($date, $doc2->getAttribute('$createdAt')); + $this->assertEquals($date, $doc3->getAttribute('$createdAt')); + + static::getDatabase()->deleteCollection('preserve_create_dates'); + + Authorization::reset(); + } + /** * @throws Exception|Throwable */