From fc7416a7fe093d3ef24adba7a151ad8848d2678e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 24 Mar 2025 19:12:50 +1300 Subject: [PATCH 01/16] Structure check tenant as int --- src/Database/Validator/Structure.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 7b2e8b6f5..8910b8088 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -50,11 +50,11 @@ class Structure extends Validator ], [ '$id' => '$tenant', - 'type' => Database::VAR_STRING, - 'size' => 36, + 'type' => Database::VAR_INTEGER, + 'size' => 8, 'required' => false, 'default' => null, - 'signed' => true, + 'signed' => false, 'array' => false, 'filters' => [], ], From 06fab72564da90bd0402429d968e9d8a4ad2e445 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 24 Mar 2025 19:13:02 +1300 Subject: [PATCH 02/16] Fix signature --- src/Database/Adapter/MariaDB.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index cc7b5338c..993dac312 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -382,10 +382,9 @@ public function createAttribute(string $collection, string $id, string $type, in * @param int $size * @param bool $signed * @param bool $array - * @param string $newKey + * @param string|null $newKey * @return bool - * @throws Exception - * @throws PDOException + * @throws DatabaseException */ public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null): bool { From 9615099b605b8f52c170bfd9df5a12c1ddbde5d2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 24 Mar 2025 19:14:15 +1300 Subject: [PATCH 03/16] Add tenant per document flag --- src/Database/Adapter.php | 36 +++++++++++++++++++++++++++++++++--- src/Database/Database.php | 23 +++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 694af73f9..01aceec51 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -18,6 +18,8 @@ abstract class Adapter protected ?int $tenant = null; + protected bool $tenantPerDocument = false; + protected int $inTransaction = 0; /** @@ -190,6 +192,34 @@ public function getTenant(): ?int return $this->tenant; } + /** + * Set Tenant Per Document. + * + * Set whether to use a different tenant for each document + * + * @param bool $tenantPerDocument + * + * @return bool + */ + public function setTenantPerDocument(bool $tenantPerDocument): bool + { + $this->tenantPerDocument = $tenantPerDocument; + + return true; + } + + /** + * Get Tenant Per Document. + * + * Get whether to use a different tenant for each document + * + * @return bool + */ + public function getTenantPerDocument(): bool + { + return $this->tenantPerDocument; + } + /** * Set metadata for query comments * @@ -261,7 +291,7 @@ abstract public function setTimeout(int $milliseconds, string $event = Database: public function clearTimeout(string $event): void { // Clear existing callback - $this->before($event, 'timeout', null); + $this->before($event, 'timeout'); } /** @@ -301,7 +331,6 @@ abstract public function rollbackTransaction(): bool; * Check if a transaction is active. * * @return bool - * @throws DatabaseException */ public function inTransaction(): bool { @@ -482,7 +511,7 @@ abstract public function createAttribute(string $collection, string $id, string * @param int $size * @param bool $signed * @param bool $array - * @param string $newKey + * @param string|null $newKey * * @return bool */ @@ -620,6 +649,7 @@ abstract public function createDocuments(string $collection, array $documents): * Update Document * * @param string $collection + * @param string $id * @param Document $document * * @return Document diff --git a/src/Database/Database.php b/src/Database/Database.php index 068a68b44..b222f91e9 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -941,6 +941,29 @@ public function withTenant(?int $tenant, callable $callback): mixed } } + /** + * Set whether to allow creating documents with tenant set per document. + * + * @param bool $enabled + * @return static + */ + public function setTenantPerDocument(bool $enabled): static + { + $this->adapter->setTenantPerDocument($enabled); + + return $this; + } + + /** + * Get whether to allow creating documents with tenant set per document. + * + * @return bool + */ + public function getTenantPerDocument(): bool + { + return $this->adapter->getTenantPerDocument(); + } + public function getPreserveDates(): bool { return $this->preserveDates; From 1ef843326e9a618669d4a496d003c06cadc6d634 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 24 Mar 2025 19:15:01 +1300 Subject: [PATCH 04/16] Remove mongo docker leftovers --- Dockerfile | 25 ++++++------------------- docker-compose.yml | 11 ----------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22ecf2532..381e801f7 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM composer:2.0 AS composer +FROM composer:2.8 AS composer WORKDIR /usr/local/src/ @@ -11,13 +11,12 @@ RUN composer install \ --no-plugins \ --no-scripts \ --prefer-dist - -FROM php:8.3.10-cli-alpine3.20 AS compile + +FROM php:8.3.19-cli-alpine3.21 AS compile ENV PHP_REDIS_VERSION="6.0.2" \ - PHP_SWOOLE_VERSION="v5.1.3" \ - PHP_MONGO_VERSION="1.16.1" \ - PHP_XDEBUG_VERSION="3.3.2" + PHP_SWOOLE_VERSION="v5.1.7" \ + PHP_XDEBUG_VERSION="3.4.2" RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone @@ -58,20 +57,10 @@ RUN \ && ./configure --enable-http2 \ && make && make install -## MongoDB Extension -FROM compile AS mongodb -RUN \ - git clone --depth 1 --branch $PHP_MONGO_VERSION https://github.com/mongodb/mongo-php-driver.git \ - && cd mongo-php-driver \ - && git submodule update --init \ - && phpize \ - && ./configure \ - && make && make install - ## PCOV Extension FROM compile AS pcov RUN \ - git clone https://github.com/krakjoe/pcov.git \ + git clone --depth 1 https://github.com/krakjoe/pcov.git \ && cd pcov \ && phpize \ && ./configure --enable-pcov \ @@ -97,7 +86,6 @@ WORKDIR /usr/src/code RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini RUN echo extension=swoole.so >> /usr/local/etc/php/conf.d/swoole.ini -RUN echo extension=mongodb.so >> /usr/local/etc/php/conf.d/mongodb.ini RUN echo extension=pcov.so >> /usr/local/etc/php/conf.d/pcov.ini RUN echo extension=xdebug.so >> /usr/local/etc/php/conf.d/xdebug.ini @@ -110,7 +98,6 @@ RUN echo "memory_limit=1024M" >> $PHP_INI_DIR/php.ini COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor COPY --from=swoole /usr/local/lib/php/extensions/no-debug-non-zts-20230831/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/ COPY --from=redis /usr/local/lib/php/extensions/no-debug-non-zts-20230831/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/ -COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20230831/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/ COPY --from=pcov /usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcov.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/ COPY --from=xdebug /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xdebug.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/ diff --git a/docker-compose.yml b/docker-compose.yml index c49290aaf..a560cc9cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,17 +70,6 @@ services: - "8704:3306" environment: - MYSQL_ROOT_PASSWORD=password - - mongo: - image: mongo:5.0 - container_name: utopia-mongo - networks: - - database - ports: - - "8705:27017" - environment: - MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: example mysql: image: mysql:8.0.33 From e9bb9b151bfd30b4a7243011b90e4befbe9f54d8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 24 Mar 2025 19:15:25 +1300 Subject: [PATCH 05/16] Add document tenant getter --- src/Database/Document.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Database/Document.php b/src/Database/Document.php index 9d41f171b..688ee1c69 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -162,6 +162,18 @@ public function getUpdatedAt(): ?string return $this->getAttribute('$updatedAt'); } + /** + * @return int|null + */ + public function getTenant(): ?int + { + $tenant = $this->getAttribute('$tenant'); + if ($tenant !== null) { + return (int)$tenant; + } + return null; + } + /** * Get Document Attributes * From ae01630126782bedc8a4994b94d76e8830218670 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 24 Mar 2025 23:20:29 +1300 Subject: [PATCH 06/16] Add tenant per document checks --- src/Database/Database.php | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index b222f91e9..13c783ab7 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3369,11 +3369,19 @@ public function createDocument(string $collection, Document $document): Document if ( $collection !== self::METADATA && $this->adapter->getSharedTables() + && !$this->adapter->getTenantPerDocument() && empty($this->adapter->getTenant()) ) { throw new DatabaseException('Missing tenant. Tenant must be set when table sharing is enabled.'); } + if ( + !$this->adapter->getSharedTables() + && $this->adapter->getTenantPerDocument() + ) { + throw new DatabaseException('Shared tables must be enabled if tenant per document is enabled.'); + } + $collection = $this->silent(fn () => $this->getCollection($collection)); if ($collection->getId() !== self::METADATA) { @@ -3395,7 +3403,16 @@ public function createDocument(string $collection, Document $document): Document ->setAttribute('$updatedAt', empty($updatedAt) || !$this->preserveDates ? $time : $updatedAt); if ($this->adapter->getSharedTables()) { - $document['$tenant'] = (string)$this->adapter->getTenant(); + if ($this->adapter->getTenantPerDocument()) { + if ( + $collection->getId() !== static::METADATA + && $document->getTenant() === null + ) { + throw new DatabaseException('Missing tenant. Tenant must be set when tenant per document is enabled.'); + } + } else { + $document->setAttribute('$tenant', (string)$this->adapter->getTenant()); + } } $document = $this->encode($collection, $document); @@ -3442,12 +3459,20 @@ public function createDocument(string $collection, Document $document): Document * @param array $documents * @param int $batchSize * @return array + * @throws AuthorizationException + * @throws StructureException + * @throws NotFoundException + * @throws \Throwable */ public function createDocuments( string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE, ): array { + if (!$this->adapter->getSharedTables() && $this->adapter->getTenantPerDocument()) { + throw new DatabaseException('Shared tables must be enabled if tenant per document is enabled.'); + } + if (empty($documents)) { return []; } @@ -3478,6 +3503,16 @@ public function createDocuments( ->setAttribute('$createdAt', empty($createdAt) || !$this->preserveDates ? $time : $createdAt) ->setAttribute('$updatedAt', empty($updatedAt) || !$this->preserveDates ? $time : $updatedAt); + if ($this->adapter->getSharedTables()) { + if ($this->adapter->getTenantPerDocument()) { + if ($document->getTenant() === null) { + throw new DatabaseException('Missing tenant. Tenant must be set when tenant per document is enabled.'); + } + } else { + $document->setAttribute('$tenant', (string)$this->adapter->getTenant()); + } + } + $document = $this->encode($collection, $document); $validator = new Structure( From 4cc88546102b0987e1e6cdaff472847ce94fe54f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 24 Mar 2025 23:20:45 +1300 Subject: [PATCH 07/16] Bind to document tenant --- src/Database/Adapter/MariaDB.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 993dac312..3bae793ff 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -853,7 +853,7 @@ public function createDocument(string $collection, Document $document): Document $attributes['_permissions'] = \json_encode($document->getPermissions()); if ($this->sharedTables) { - $attributes['_tenant'] = $this->tenant; + $attributes['_tenant'] = $document->getTenant(); } $name = $this->filter($collection); @@ -896,13 +896,13 @@ public function createDocument(string $collection, Document $document): Document $attributeIndex = 0; foreach ($attributes as $value) { - if (is_array($value)) { - $value = json_encode($value); + if (\is_array($value)) { + $value = \json_encode($value); } $bindKey = 'key_' . $attributeIndex; $attribute = $this->filter($attribute); - $value = (is_bool($value)) ? (int)$value : $value; + $value = (\is_bool($value)) ? (int)$value : $value; $stmt->bindValue(':' . $bindKey, $value, $this->getPDOType($value)); $attributeIndex++; } @@ -941,7 +941,7 @@ public function createDocument(string $collection, Document $document): Document $stmtPermissions = $this->getPDO()->prepare($sqlPermissions); if ($this->sharedTables) { - $stmtPermissions->bindValue(':_tenant', $this->tenant); + $stmtPermissions->bindValue(':_tenant', $document->getTenant()); } } @@ -1015,9 +1015,6 @@ public function createDocuments(string $collection, array $documents): array $documentIds = []; foreach ($documents as $document) { - /** - * @var Document $document - */ $attributes = $document->getAttributes(); $attributes['_uid'] = $document->getId(); $attributes['_createdAt'] = $document->getCreatedAt(); @@ -1032,7 +1029,7 @@ public function createDocuments(string $collection, array $documents): array } if ($this->sharedTables) { - $attributes['_tenant'] = $this->tenant; + $attributes['_tenant'] = $document->getTenant(); } $bindKeys = []; From dde5ce5ba9c1a04266d88a642e7550e266d94884 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 02:04:35 +1300 Subject: [PATCH 08/16] Use PDO last insert --- 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 3bae793ff..7aae9cf7f 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -947,7 +947,7 @@ public function createDocument(string $collection, Document $document): Document $stmt->execute(); - $document['$internalId'] = $this->getDocument($collection, $document->getId(), [Query::select(['$internalId'])])->getInternalId(); + $document['$internalId'] = $this->pdo->lastInsertId(); if (empty($document['$internalId'])) { throw new DatabaseException('Error creating document empty "$internalId"'); From 1952f4c3a9b88535a90cea9b45c57fa27958e099 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 02:04:48 +1300 Subject: [PATCH 09/16] Fix tenant type --- src/Database/Adapter/SQL.php | 2 +- src/Database/Database.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index eafa917d6..18a8ec2ca 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -255,7 +255,7 @@ public function getDocument(string $collection, string $id, array $queries = [], unset($document['_uid']); } if (\array_key_exists('_tenant', $document)) { - $document['$tenant'] = $document['_tenant']; + $document['$tenant'] = (int)$document['_tenant']; unset($document['_tenant']); } if (\array_key_exists('_createdAt', $document)) { diff --git a/src/Database/Database.php b/src/Database/Database.php index 13c783ab7..1379d8c33 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3411,7 +3411,7 @@ public function createDocument(string $collection, Document $document): Document throw new DatabaseException('Missing tenant. Tenant must be set when tenant per document is enabled.'); } } else { - $document->setAttribute('$tenant', (string)$this->adapter->getTenant()); + $document->setAttribute('$tenant', $this->adapter->getTenant()); } } @@ -3509,7 +3509,7 @@ public function createDocuments( throw new DatabaseException('Missing tenant. Tenant must be set when tenant per document is enabled.'); } } else { - $document->setAttribute('$tenant', (string)$this->adapter->getTenant()); + $document->setAttribute('$tenant', $this->adapter->getTenant()); } } From ed352868ea62b59f517fde98808f78d4bce8cf64 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 02:05:07 +1300 Subject: [PATCH 10/16] Add test --- tests/e2e/Adapter/Base.php | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 54b38f7c0..455277f69 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -16438,6 +16438,95 @@ public function testSharedTablesDuplicates(): void $database->setDatabase($this->testDatabase); } + public function testSharedTablesTenantPerDocument(): void + { + $database = static::getDatabase(); + $sharedTables = $database->getSharedTables(); + $namespace = $database->getNamespace(); + $schema = $database->getDatabase(); + + if (!$database->getAdapter()->getSupportForSchemas()) { + $this->expectNotToPerformAssertions(); + return; + } + + if ($database->exists(__FUNCTION__)) { + $database->delete(__FUNCTION__); + } + + $database + ->setDatabase(__FUNCTION__) + ->setNamespace('') + ->setSharedTables(true) + ->setTenant(null) + ->create(); + + // Create collection + $database->createCollection(__FUNCTION__, permissions: [ + Permission::create(Role::any()), + Permission::read(Role::any()), + ], documentSecurity: false); + + $database->createAttribute(__FUNCTION__, 'name', Database::VAR_STRING, 10, false); + $database->createIndex(__FUNCTION__, 'nameIndex', Database::INDEX_KEY, ['name']); + + $doc1Id = ID::unique(); + + // Create doc for tenant 1 + $database + ->setTenant(null) + ->setTenantPerDocument(true) + ->createDocument(__FUNCTION__, new Document([ + '$id' => $doc1Id, + '$tenant' => 1, + 'name' => 'Spiderman', + ])); + + // Set to tenant 1 and read + $doc = $database + ->setTenantPerDocument(false) + ->setTenant(1) + ->getDocument(__FUNCTION__, $doc1Id); + + $this->assertEquals('Spiderman', $doc['name']); + + $doc2Id = ID::unique(); + + // Create doc for tenant 2 + $database + ->setTenant(null) + ->setTenantPerDocument(true) + ->createDocument(__FUNCTION__, new Document([ + '$id' => $doc2Id, + '$tenant' => 2, + 'name' => 'Batman', + ])); + + // Set to tenant 2 and read + $doc = $database + ->setTenantPerDocument(false) + ->setTenant(2) + ->getDocument(__FUNCTION__, $doc2Id); + + $this->assertEquals('Batman', $doc['name']); + $this->assertEquals(2, $doc->getAttribute('$tenant')); + + // Ensure no read cross-tenant + $docs = $database + ->setTenantPerDocument(false) + ->setTenant(1) + ->find(__FUNCTION__); + + $this->assertEquals(1, \count($docs)); + $this->assertEquals($doc1Id, $docs[0]->getId()); + + // Reset instance + $database + ->setSharedTables($sharedTables) + ->setNamespace($namespace) + ->setDatabase($schema); + } + public function testTransformations(): void { static::getDatabase()->createCollection('docs', attributes: [ From 73e21fcf9c0137b14180798414f4446b80d5c492 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 02:12:13 +1300 Subject: [PATCH 11/16] Format --- tests/e2e/Adapter/Base.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 455277f69..5fd2c00cb 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -16477,10 +16477,10 @@ public function testSharedTablesTenantPerDocument(): void ->setTenant(null) ->setTenantPerDocument(true) ->createDocument(__FUNCTION__, new Document([ - '$id' => $doc1Id, - '$tenant' => 1, - 'name' => 'Spiderman', - ])); + '$id' => $doc1Id, + '$tenant' => 1, + 'name' => 'Spiderman', + ])); // Set to tenant 1 and read $doc = $database @@ -16497,10 +16497,10 @@ public function testSharedTablesTenantPerDocument(): void ->setTenant(null) ->setTenantPerDocument(true) ->createDocument(__FUNCTION__, new Document([ - '$id' => $doc2Id, - '$tenant' => 2, - 'name' => 'Batman', - ])); + '$id' => $doc2Id, + '$tenant' => 2, + 'name' => 'Batman', + ])); // Set to tenant 2 and read $doc = $database From e07e7e11e79dc7e0658fd85874c40bc709365487 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 16:29:46 +1300 Subject: [PATCH 12/16] Add missing int cast --- 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 7aae9cf7f..079022454 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -2225,7 +2225,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, unset($results[$index]['_id']); } if (\array_key_exists('_tenant', $document)) { - $results[$index]['$tenant'] = $document['_tenant']; + $results[$index]['$tenant'] = (int)$document['_tenant']; unset($results[$index]['_tenant']); } if (\array_key_exists('_createdAt', $document)) { From ac840b54e37871fbb53444cd9bae9048592b77cf Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 16:39:31 +1300 Subject: [PATCH 13/16] Fix int cast from null --- src/Database/Adapter/MariaDB.php | 2 +- src/Database/Adapter/SQL.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 079022454..301a8a3c0 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -2225,7 +2225,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, unset($results[$index]['_id']); } if (\array_key_exists('_tenant', $document)) { - $results[$index]['$tenant'] = (int)$document['_tenant']; + $document['$tenant'] = $document['_tenant'] === null ? null : (int)$document['_tenant']; unset($results[$index]['_tenant']); } if (\array_key_exists('_createdAt', $document)) { diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 18a8ec2ca..3d9c8d8da 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -255,7 +255,7 @@ public function getDocument(string $collection, string $id, array $queries = [], unset($document['_uid']); } if (\array_key_exists('_tenant', $document)) { - $document['$tenant'] = (int)$document['_tenant']; + $document['$tenant'] = $document['_tenant'] === null ? null : (int)$document['_tenant']; unset($document['_tenant']); } if (\array_key_exists('_createdAt', $document)) { From 79d38d80e7c39cef3fafeb0edb8f2176fc27dcfb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 17:29:44 +1300 Subject: [PATCH 14/16] Fix permissions tenant bind --- src/Database/Adapter/MariaDB.php | 42 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 301a8a3c0..0347b93fa 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1014,7 +1014,7 @@ public function createDocuments(string $collection, array $documents): array $permissions = []; $documentIds = []; - foreach ($documents as $document) { + foreach ($documents as $index => $document) { $attributes = $document->getAttributes(); $attributes['_uid'] = $document->getId(); $attributes['_createdAt'] = $document->getCreatedAt(); @@ -1049,25 +1049,20 @@ public function createDocuments(string $collection, array $documents): array $batchKeys[] = '(' . \implode(', ', $bindKeys) . ')'; foreach (Database::PERMISSIONS as $type) { foreach ($document->getPermissionsByType($type) as $permission) { + $tenantBind = $this->sharedTables ? ", :_tenant_{$index}" : ''; $permission = \str_replace('"', '', $permission); - $permission = "('{$type}', '{$permission}', '{$document->getId()}'"; - - if ($this->sharedTables) { - $permission .= ", :_tenant)"; - } else { - $permission .= ")"; - } - + $permission = "('{$type}', '{$permission}', :_uid_{$index} {$tenantBind})"; $permissions[] = $permission; } } } - $stmt = $this->getPDO()->prepare( - " + $batchKeys = \implode(', ', $batchKeys); + + $stmt = $this->getPDO()->prepare(" INSERT INTO {$this->getSQLTable($name)} {$columns} - VALUES " . \implode(', ', $batchKeys) - ); + VALUES {$batchKeys} + "); foreach ($bindValues as $key => $value) { $stmt->bindValue($key, $value, $this->getPDOType($value)); @@ -1076,22 +1071,21 @@ public function createDocuments(string $collection, array $documents): array $stmt->execute(); if (!empty($permissions)) { + $tenantColumn = $this->sharedTables ? ', _tenant' : ''; + $permissions = \implode(', ', $permissions); + $sqlPermissions = " - INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document + INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document {$tenantColumn}) + VALUES {$permissions}; "; - if ($this->sharedTables) { - $sqlPermissions .= ', _tenant)'; - } else { - $sqlPermissions .= ")"; - } - - $sqlPermissions .= " VALUES " . \implode(', ', $permissions); - $stmtPermissions = $this->getPDO()->prepare($sqlPermissions); - if ($this->sharedTables) { - $stmtPermissions->bindValue(':_tenant', $this->tenant); + foreach ($documents as $index => $document) { + $stmtPermissions->bindValue(":_uid_{$index}", $document->getId()); + if ($this->sharedTables) { + $stmtPermissions->bindValue(":_tenant_{$index}", $document->getTenant()); + } } $stmtPermissions?->execute(); From 2f0f6d8b6794e2cab1011f612b53db81977a8197 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 17:33:43 +1300 Subject: [PATCH 15/16] Clean up permissions --- src/Database/Adapter/MariaDB.php | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 0347b93fa..ed5765e93 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -910,36 +910,24 @@ public function createDocument(string $collection, Document $document): Document $permissions = []; foreach (Database::PERMISSIONS as $type) { foreach ($document->getPermissionsByType($type) as $permission) { + $tenantBind = $this->sharedTables ? ", :_tenant" : ''; $permission = \str_replace('"', '', $permission); - $permission = "('{$type}', '{$permission}', '{$document->getId()}'"; - - if ($this->sharedTables) { - $permission .= ", :_tenant)"; - } else { - $permission .= ")"; - } - + $permission = "('{$type}', '{$permission}', :_uid {$tenantBind})"; $permissions[] = $permission; } } if (!empty($permissions)) { + $tenantColumn = $this->sharedTables ? ', _tenant' : ''; $permissions = \implode(', ', $permissions); $sqlPermissions = " - INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document - "; - - if ($this->sharedTables) { - $sqlPermissions .= ', _tenant)'; - } else { - $sqlPermissions .= ")"; - } + INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document {$tenantColumn}) + VALUES {$permissions}; + "; - $sqlPermissions .= " VALUES {$permissions}"; - $sqlPermissions = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sqlPermissions); $stmtPermissions = $this->getPDO()->prepare($sqlPermissions); - + $stmtPermissions->bindValue(':_uid', $document->getId()); if ($this->sharedTables) { $stmtPermissions->bindValue(':_tenant', $document->getTenant()); } From 339447dc15a05acd1faacb5167e88e9136a85e56 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 17:44:50 +1300 Subject: [PATCH 16/16] Apply changes to postgres --- src/Database/Adapter/Postgres.php | 57 ++++++++++++++++++------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 65d5c076e..090efb40d 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -924,17 +924,13 @@ public function createDocument(string $collection, Document $document): Document $attributes['_permissions'] = \json_encode($document->getPermissions()); if ($this->sharedTables) { - $attributes['_tenant'] = $this->tenant; + $attributes['_tenant'] = $document->getTenant(); } $name = $this->filter($collection); $columns = ''; $columnNames = ''; - /** - * Insert Attributes - */ - // Insert internal id if set if (!empty($document->getInternalId())) { $bindKey = '_id'; @@ -969,12 +965,12 @@ public function createDocument(string $collection, Document $document): Document $attributeIndex = 0; foreach ($attributes as $value) { - if (is_array($value)) { + if (\is_array($value)) { $value = \json_encode($value); } $bindKey = 'key_' . $attributeIndex; - $value = (is_bool($value)) ? ($value ? "true" : "false") : $value; + $value = (\is_bool($value)) ? ($value ? "true" : "false") : $value; $stmt->bindValue(':' . $bindKey, $value, $this->getPDOType($value)); $attributeIndex++; } @@ -984,7 +980,7 @@ public function createDocument(string $collection, Document $document): Document foreach ($document->getPermissionsByType($type) as $permission) { $permission = \str_replace('"', '', $permission); $sqlTenant = $this->sharedTables ? ', :_tenant' : ''; - $permissions[] = "('{$type}', '{$permission}', '{$document->getId()}' {$sqlTenant})"; + $permissions[] = "('{$type}', '{$permission}', :_uid {$sqlTenant})"; } } @@ -1000,8 +996,9 @@ public function createDocument(string $collection, Document $document): Document $queryPermissions = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $queryPermissions); $stmtPermissions = $this->getPDO()->prepare($queryPermissions); + $stmtPermissions->bindValue(':_uid', $document->getId()); if ($sqlTenant) { - $stmtPermissions->bindValue(':_tenant', $this->tenant); + $stmtPermissions->bindValue(':_tenant', $document->getTenant()); } } @@ -1070,7 +1067,7 @@ public function createDocuments(string $collection, array $documents): array $bindValues = []; $permissions = []; - foreach ($documents as $document) { + foreach ($documents as $index => $document) { $attributes = $document->getAttributes(); $attributes['_uid'] = $document->getId(); $attributes['_createdAt'] = $document->getCreatedAt(); @@ -1084,7 +1081,7 @@ public function createDocuments(string $collection, array $documents): array } if ($this->sharedTables) { - $attributes['_tenant'] = $this->tenant; + $attributes['_tenant'] = $document->getTenant(); } $bindKeys = []; @@ -1104,17 +1101,20 @@ public function createDocuments(string $collection, array $documents): array $batchKeys[] = '(' . \implode(', ', $bindKeys) . ')'; foreach (Database::PERMISSIONS as $type) { foreach ($document->getPermissionsByType($type) as $permission) { + $tenantBind = $this->sharedTables ? ", :_tenant_{$index}" : ''; $permission = \str_replace('"', '', $permission); - $permissions[] = "('{$type}', '{$permission}', '{$document->getId()}', :_tenant)"; + $permission = "('{$type}', '{$permission}', :_uid_{$index} {$tenantBind})"; + $permissions[] = $permission; } } } - $stmt = $this->getPDO()->prepare( - " + $batchKeys = \implode(', ', $batchKeys); + + $stmt = $this->getPDO()->prepare(" INSERT INTO {$this->getSQLTable($name)} {$columns} - VALUES " . \implode(', ', $batchKeys) - ); + VALUES {$batchKeys} + "); foreach ($bindValues as $key => $value) { $stmt->bindValue($key, $value, $this->getPDOType($value)); @@ -1123,12 +1123,23 @@ public function createDocuments(string $collection, array $documents): array $stmt->execute(); if (!empty($permissions)) { - $stmtPermissions = $this->getPDO()->prepare( - " - INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document, _tenant) - VALUES " . \implode(', ', $permissions) - ); - $stmtPermissions->bindValue(':_tenant', $this->tenant); + $tenantColumn = $this->sharedTables ? ', _tenant' : ''; + $permissions = \implode(', ', $permissions); + + $sqlPermissions = " + INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document {$tenantColumn}) + VALUES {$permissions}; + "; + + $stmtPermissions = $this->getPDO()->prepare($sqlPermissions); + + foreach ($documents as $index => $document) { + $stmtPermissions->bindValue(":_uid_{$index}", $document->getId()); + if ($this->sharedTables) { + $stmtPermissions->bindValue(":_tenant_{$index}", $document->getTenant()); + } + } + $stmtPermissions?->execute(); } } catch (PDOException $e) { @@ -2000,7 +2011,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, unset($results[$index]['_id']); } if (\array_key_exists('_tenant', $document)) { - $results[$index]['$tenant'] = $document['_tenant']; + $results[$index]['$tenant'] = $document['_tenant'] === null ? null : (int)$document['_tenant']; unset($results[$index]['_tenant']); } if (\array_key_exists('_createdAt', $document)) {