diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index c4579715f..c20593ba1 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -733,6 +733,13 @@ abstract public function createOrUpdateDocuments( array $changes ): array; + /** + * @param string $collection + * @param array $documents + * @return array + */ + abstract public function getSequences(string $collection, array $documents): array; + /** * Delete Document * diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 708926548..7dbc02245 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1171,8 +1171,6 @@ public function createOrUpdateDocuments( $bindIndex = 0; $batchKeys = []; $bindValues = []; - $documentIds = []; - $documentTenants = []; foreach ($changes as $change) { $document = $change->getNew(); @@ -1184,14 +1182,10 @@ public function createOrUpdateDocuments( if (!empty($document->getSequence())) { $attributes['_id'] = $document->getSequence(); - } else { - $documentIds[] = $document->getId(); } if ($this->sharedTables) { - $attributes['_tenant'] - = $documentTenants[] - = $document->getTenant(); + $attributes['_tenant'] = $document->getTenant(); } \ksort($attributes); @@ -1349,18 +1343,6 @@ public function createOrUpdateDocuments( } $stmtAddPermissions->execute(); } - - $sequences = $this->getSequences( - $collection, - $documentIds, - $documentTenants - ); - - foreach ($changes as $change) { - if (isset($sequences[$change->getNew()->getId()])) { - $change->getNew()->setAttribute('$sequence', $sequences[$change->getNew()->getId()]); - } - } } catch (PDOException $e) { throw $this->processException($e); } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 0a4e59018..e64db87ec 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -494,4 +494,9 @@ protected function execute(mixed $stmt): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } + + public function getSequences(string $collection, array $documents): array + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } } diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 7b45fc102..fccf0a141 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -710,49 +710,64 @@ public function deleteDocuments(string $collection, array $sequences, array $per } /** - * Get internal IDs for the given documents + * Assign internal IDs for the given documents * * @param string $collection - * @param array $documentIds - * @param array $documentTenants - * @return array + * @param array $documents + * @return array * @throws DatabaseException */ - protected function getSequences(string $collection, array $documentIds, array $documentTenants = []): array + public function getSequences(string $collection, array $documents): array { - $sequences = []; + $documentIds = []; + $keys = []; + $binds = []; - /** - * UID, _tenant bottleneck is ~ 5000 rows since we use _uid IN query - */ - foreach (\array_chunk($documentIds, 1000) as $documentIdsChunk) { - $sql = " - SELECT _uid, _id - FROM {$this->getSQLTable($collection)} - WHERE {$this->quote('_uid')} IN (" . implode(',', array_map(fn ($index) => ":_key_{$index}", array_keys($documentIdsChunk))) . ") - {$this->getTenantQuery($collection, tenantCount: \count($documentIdsChunk))} - "; + foreach ($documents as $i => $document) { + if (empty($document->getSequence())) { + $documentIds[] = $document->getId(); - $stmt = $this->getPDO()->prepare($sql); + $key = ":uid_{$i}"; - foreach ($documentIdsChunk as $index => $id) { - $stmt->bindValue(":_key_{$index}", $id); - } + $binds[$key] = $document->getId(); + $keys[] = $key; - if ($this->sharedTables) { - foreach ($documentIdsChunk as $index => $id) { - $stmt->bindValue(":_tenant_{$index}", \array_shift($documentTenants)); + if ($this->sharedTables) { + $binds[':_tenant_'.$i] = $document->getTenant(); } } + } - $stmt->execute(); - $results = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); // Fetch as [documentId => sequence] - $stmt->closeCursor(); + if (empty($documentIds)) { + return $documents; + } - $sequences = [...$sequences, ...$results]; + $placeholders = implode(',', array_values($keys)); + + $sql = " + SELECT _uid, _id + FROM {$this->getSQLTable($collection)} + WHERE {$this->quote('_uid')} IN ({$placeholders}) + {$this->getTenantQuery($collection, tenantCount: \count($documentIds))} + "; + + $stmt = $this->getPDO()->prepare($sql); + + foreach ($binds as $key => $value) { + $stmt->bindValue($key, $value); } - return $sequences; + $stmt->execute(); + $sequences = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); // Fetch as [documentId => sequence] + $stmt->closeCursor(); + + foreach ($documents as $document) { + if (isset($sequences[$document->getId()])) { + $document['$sequence'] = $sequences[$document->getId()]; + } + } + + return $documents; } /** @@ -1808,8 +1823,6 @@ public function createDocuments(string $collection, array $documents): array $batchKeys = []; $bindValues = []; $permissions = []; - $documentIds = []; - $documentTenants = []; foreach ($documents as $index => $document) { $attributes = $document->getAttributes(); @@ -1820,13 +1833,10 @@ public function createDocuments(string $collection, array $documents): array if (!empty($document->getSequence())) { $attributes['_id'] = $document->getSequence(); - } else { - $documentIds[] = $document->getId(); } if ($this->sharedTables) { $attributes['_tenant'] = $document->getTenant(); - $documentTenants[] = $document->getTenant(); } $bindKeys = []; @@ -1889,17 +1899,6 @@ public function createDocuments(string $collection, array $documents): array $this->execute($stmtPermissions); } - $sequences = $this->getSequences( - $collection, - $documentIds, - $documentTenants - ); - - foreach ($documents as $document) { - if (isset($sequences[$document->getId()])) { - $document['$sequence'] = $sequences[$document->getId()]; - } - } } catch (PDOException $e) { throw $this->processException($e); } diff --git a/src/Database/Database.php b/src/Database/Database.php index 3cba68080..47be1916b 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3759,6 +3759,8 @@ public function createDocuments( return $this->adapter->createDocuments($collection->getId(), $chunk); }); + $batch = $this->adapter->getSequences($collection->getId(), $batch); + foreach ($batch as $document) { if ($this->resolveRelationships) { $document = $this->silent(fn () => $this->populateDocumentRelationships($collection, $document)); @@ -5074,6 +5076,8 @@ public function createOrUpdateDocumentsWithIncrease( $chunk ))); + $batch = $this->adapter->getSequences($collection->getId(), $batch); + foreach ($chunk as $change) { if ($change->getOld()->isEmpty()) { $created++; diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 50bbcba57..526c7700b 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -445,6 +445,7 @@ public function testUpsertDocuments(): void foreach ($results as $index => $document) { $createdAt[$index] = $document->getCreatedAt(); $this->assertNotEmpty(true, $document->getId()); + $this->assertNotEmpty(true, $document->getSequence()); $this->assertIsString($document->getAttribute('string')); $this->assertEquals('text📝', $document->getAttribute('string')); // Also makes sure an emoji is working $this->assertIsInt($document->getAttribute('integer')); @@ -481,6 +482,7 @@ public function testUpsertDocuments(): void foreach ($results as $document) { $this->assertNotEmpty(true, $document->getId()); + $this->assertNotEmpty(true, $document->getSequence()); $this->assertIsString($document->getAttribute('string')); $this->assertEquals('new text📝', $document->getAttribute('string')); // Also makes sure an emoji is working $this->assertIsInt($document->getAttribute('integer'));