diff --git a/Firestore/src/Connection/Grpc.php b/Firestore/src/Connection/Grpc.php index e9a1218c2163..037ee6a7b96f 100644 --- a/Firestore/src/Connection/Grpc.php +++ b/Firestore/src/Connection/Grpc.php @@ -56,6 +56,11 @@ class Grpc implements ConnectionInterface */ private $resourcePrefixHeader; + /** + * @var string + */ + private $databaseRoutingHeader; + /** * @var bool */ @@ -113,10 +118,17 @@ public function __construct(array $config = []) //@codeCoverageIgnoreEnd $this->firestore = $this->constructGapic(FirestoreClient::class, $grpcConfig); + $projectId = $this->pluck('projectId', $config); + $databaseId = $this->pluck('database', $config); $this->resourcePrefixHeader = FirestoreClient::databaseRootName( - $this->pluck('projectId', $config), - $this->pluck('database', $config) + $projectId, + $databaseId + ); + $this->databaseRoutingHeader = sprintf( + 'project_id=%s&database_id=%s', + $projectId, + $databaseId ); } @@ -301,6 +313,7 @@ private function addRequestHeaders(array $args) ]; $args['headers']['google-cloud-resource-prefix'] = [$this->resourcePrefixHeader]; + $args['headers']['x-goog-request-params'] = [$this->databaseRoutingHeader]; // Provide authentication header for requests when emulator is enabled. if ($this->isUsingEmulator) { @@ -339,6 +352,7 @@ public function __debugInfo() 'serializer' => get_class($this->serializer), 'firestore' => get_class($this->firestore), 'resourcePrefixHeader' => $this->resourcePrefixHeader, + 'databaseRoutingHeader' => $this->databaseRoutingHeader, 'isUsingEmulator' => $this->isUsingEmulator ]; } diff --git a/Firestore/tests/System/FirestoreMultipleDbTest.php b/Firestore/tests/System/FirestoreMultipleDbTest.php new file mode 100644 index 000000000000..56f6d6aaac33 --- /dev/null +++ b/Firestore/tests/System/FirestoreMultipleDbTest.php @@ -0,0 +1,119 @@ +document = self::$multiDbCollection->newDocument(); + } + + public function testInsert() + { + $this->assertFalse($this->document->snapshot()->exists()); + + self::$multiDbClient->runTransaction(function ($t) { + $t->create($this->document, [ + 'foo' => 'bar' + ]); + }); + + $this->assertTrue($this->document->snapshot()->exists()); + } + + public function testUpdate() + { + $this->document->create([ + 'foo' => 'bar' + ]); + + self::$multiDbClient->runTransaction(function ($t) { + $t->update($this->document, [ + ['path' => 'bat', 'value' => 'baz'] + ]); + }); + + $this->assertEquals([ + 'foo' => 'bar', + 'bat' => 'baz' + ], $this->document->snapshot()->data()); + } + + + public function testCollectionGroup() + { + // Create a random collection name, but make sure + // it starts with 'b' for predictable ordering. + $collectionGroup = 'b' . uniqid(self::COLLECTION_NAME); + $query = $this->createDocuments([ + // following doc paths will match based on the collection group id + 'abc/123/%s/cg-doc1', + 'abc/123/%s/cg-doc2', + '%s/cg-doc3', + '%s/cg-doc4', + 'def/456/%s/cg-doc5', + // following doc paths will NOT match with collection group id + '%s/virtual-doc/nested-coll/not-cg-doc', // nested-coll + 'x%s/not-cg-doc', // x-prefix + '%sx/not-cg-doc', // x-suffix + 'abc/123/%sx/not-cg-doc', // x-prefix + 'abc/123/x%s/not-cg-doc', // x-suffix + 'abc/%s', // abc + ], $collectionGroup); + + $query = self::$multiDbClient->collectionGroup($collectionGroup); + $documentIds = array_map( + fn($doc) => $doc->id(), + // Returns docs only with matching exact collection group id + $query->documents()->rows() + ); + $this->assertEqualsCanonicalizing( + ['cg-doc1', 'cg-doc2', 'cg-doc3', 'cg-doc4', 'cg-doc5'], + $documentIds + ); + } + + private function createDocuments(array $paths, $collectionGroupId) + { + foreach ($paths as &$path) { + $path = sprintf($path, $collectionGroupId); + } + $batch = self::$multiDbClient->bulkWriter(); + + foreach ($paths as $docpath) { + $doc = self::$multiDbClient->document($docpath); + self::$localDeletionQueue->add($doc); + $batch->set($doc, [ + 'x' => 1 + ]); + } + + $batch->flush(); + self::$localDeletionQueue->add($collectionGroupId); + } +} diff --git a/Firestore/tests/System/FirestoreTestCase.php b/Firestore/tests/System/FirestoreTestCase.php index 949c7c91e5ea..770ea2e8bbb5 100644 --- a/Firestore/tests/System/FirestoreTestCase.php +++ b/Firestore/tests/System/FirestoreTestCase.php @@ -25,9 +25,12 @@ class FirestoreTestCase extends SystemTestCase { const COLLECTION_NAME = 'system-test'; + const TEST_DB_NAME = 'system-tests-named-db'; protected static $client; + protected static $multiDbClient; protected static $collection; + protected static $multiDbCollection; protected static $localDeletionQueue; private static $hasSetUp = false; @@ -43,9 +46,14 @@ public static function setUpBeforeClass(): void self::$client = new FirestoreClient([ 'keyFilePath' => $keyFilePath ]); + self::$multiDbClient = new FirestoreClient([ + 'keyFilePath' => $keyFilePath, + 'database' => self::TEST_DB_NAME + ]); self::$collection = self::$client->collection(uniqid(self::COLLECTION_NAME)); + self::$multiDbCollection = self::$multiDbClient->collection(uniqid(self::COLLECTION_NAME)); self::$localDeletionQueue->add(self::$collection); - + self::$localDeletionQueue->add(self::$multiDbCollection); self::$hasSetUp = true; } diff --git a/Firestore/tests/Unit/Connection/GrpcTest.php b/Firestore/tests/Unit/Connection/GrpcTest.php index 9f982162d908..a05499f7981b 100644 --- a/Firestore/tests/Unit/Connection/GrpcTest.php +++ b/Firestore/tests/Unit/Connection/GrpcTest.php @@ -404,7 +404,8 @@ private function header() { return [ "headers" => [ - "google-cloud-resource-prefix" => ["projects/test/databases/(default)"] + "google-cloud-resource-prefix" => ["projects/test/databases/(default)"], + "x-goog-request-params" => ["project_id=test&database_id=(default)"] ] ]; }