From 85e8fb2052b80de378ef2cb0f62d81b5459f34f6 Mon Sep 17 00:00:00 2001 From: hectorhammett Date: Fri, 8 May 2026 21:34:48 +0000 Subject: [PATCH 1/2] Add the Remote UDF feature --- BigQuery/src/Dataset.php | 16 ++++ BigQuery/src/Routine.php | 14 ++++ BigQuery/tests/System/ManageRoutinesTest.php | 86 ++++++++++++++++++++ BigQuery/tests/Unit/RoutineTest.php | 35 ++++++++ 4 files changed, 151 insertions(+) diff --git a/BigQuery/src/Dataset.php b/BigQuery/src/Dataset.php index 593a2dc717a4..ef31cf402a03 100644 --- a/BigQuery/src/Dataset.php +++ b/BigQuery/src/Dataset.php @@ -510,6 +510,22 @@ function (array $routine) { * ]); * ``` * + * ``` + * // Create a routine with remote function options. + * $routine = $dataset->createRoutine('my_remote_udf', [ + * 'routineType' => 'SCALAR_FUNCTION', + * 'language' => 'SQL', + * 'remoteFunctionOptions' => [ + * 'endpoint' => 'https://us-east1-my_gcf_project.cloudfunctions.net/remote_add', + * 'connection' => 'projects/project-id/locations/us/connections/connection-id', + * 'maxBatchingRows' => '10', + * 'userDefinedContext' => [ + * 'key' => 'value' + * ] + * ] + * ]); + * ``` + * * @see https://cloud.google.com/bigquery/docs/reference/rest/v2/routines/insert Insert Routines API documentation. * * @param string $id The routine ID. diff --git a/BigQuery/src/Routine.php b/BigQuery/src/Routine.php index 3794c3af64bd..e04debbd181d 100644 --- a/BigQuery/src/Routine.php +++ b/BigQuery/src/Routine.php @@ -201,6 +201,20 @@ public function exists(array $options = []) * ]); * ``` * + * ``` + * // Update the routine with remote function options. + * $routine->update([ + * 'remoteFunctionOptions' => [ + * 'endpoint' => 'https://us-east1-my_gcf_project.cloudfunctions.net/remote_add', + * 'connection' => 'projects/project-id/locations/us/connections/connection-id', + * 'maxBatchingRows' => '10', + * 'userDefinedContext' => [ + * 'key' => 'value' + * ] + * ] + * ]); + * ``` + * * @see https://cloud.google.com/bigquery/docs/reference/rest/v2/routines/update Update Routines API documentation. * @param array $metadata The full routine resource with desired * modifications. diff --git a/BigQuery/tests/System/ManageRoutinesTest.php b/BigQuery/tests/System/ManageRoutinesTest.php index 3b6f4b60c1b9..b5af2a7220bf 100644 --- a/BigQuery/tests/System/ManageRoutinesTest.php +++ b/BigQuery/tests/System/ManageRoutinesTest.php @@ -18,6 +18,8 @@ namespace Google\Cloud\BigQuery\Tests\System; use Google\Cloud\BigQuery\Routine; +use Google\Cloud\Core\RequestWrapper; +use GuzzleHttp\Psr7\Request; /** * @group bigquery @@ -133,4 +135,88 @@ public function testCreateAndDeleteRoutine() $this->assertFalse($routine->exists()); } + + public function testCreateRemoteUdf() + { + $routineId = uniqid(self::TESTING_PREFIX); + $connectionId = uniqid('udf_conn'); + + $connectionName = $this->createConnection($connectionId); + + try { + $routine = self::$dataset->createRoutine($routineId, [ + 'routineType' => 'SCALAR_FUNCTION', + 'language' => 'SQL', + 'returnType' => [ + 'typeKind' => 'STRING' + ], + 'remoteFunctionOptions' => [ + 'endpoint' => 'https://us-east1-my_gcf_project.cloudfunctions.net/remote_add', + 'connection' => $connectionName, + 'maxBatchingRows' => '10', + 'userDefinedContext' => [ + 'key' => 'value' + ] + ] + ]); + + $this->assertTrue($routine->exists()); + + $info = $routine->info(); + $this->assertEquals('SCALAR_FUNCTION', $info['routineType']); + $this->assertArrayHasKey('remoteFunctionOptions', $info); + $this->assertEquals( + 'https://us-east1-my_gcf_project.cloudfunctions.net/remote_add', + $info['remoteFunctionOptions']['endpoint'] + ); + + $routine->delete(); + } finally { + $this->deleteConnection($connectionName); + } + } + + private function createConnection($connectionId) + { + // There is a BigQueryConnection client available in the PHP cloud + // but decided to create it here manually instead of adding it as a dependency just for testing. + $projectId = self::$dataset->identity()['projectId']; + $location = 'us'; + $parent = "projects/$projectId/locations/$location"; + $uri = "https://bigqueryconnection.googleapis.com/v1/$parent/connections?connectionId=$connectionId"; + + $body = json_encode([ + 'friendlyName' => $connectionId, + 'cloudResource' => new \stdClass() + ]); + + $request = new Request( + 'POST', + $uri, + ['Content-Type' => 'application/json'], + $body + ); + + $requestWrapper = new RequestWrapper([ + 'keyFilePath' => getenv('GOOGLE_CLOUD_PHP_TESTS_KEY_PATH'), + 'scopes' => ['https://www.googleapis.com/auth/cloud-platform'] + ]); + + $response = $requestWrapper->send($request); + $data = json_decode($response->getBody(), true); + return $data['name']; + } + + private function deleteConnection($name) + { + $uri = "https://bigqueryconnection.googleapis.com/v1/$name"; + $request = new Request('DELETE', $uri); + + $requestWrapper = new RequestWrapper([ + 'keyFilePath' => getenv('GOOGLE_CLOUD_PHP_TESTS_KEY_PATH'), + 'scopes' => ['https://www.googleapis.com/auth/cloud-platform'] + ]); + + $requestWrapper->send($request); + } } diff --git a/BigQuery/tests/Unit/RoutineTest.php b/BigQuery/tests/Unit/RoutineTest.php index 19c46e61f829..1c3b76bef02c 100644 --- a/BigQuery/tests/Unit/RoutineTest.php +++ b/BigQuery/tests/Unit/RoutineTest.php @@ -209,6 +209,41 @@ public function testUpdateWithMask() $this->assertEquals($expected, $res); } + public function testUpdateRemoteFunctionOptions() + { + $old = [ + 'routineType' => 'SCALAR_FUNCTION', + 'definitionBody' => '', + 'language' => 'SQL' + ]; + + $new = [ + 'remoteFunctionOptions' => [ + 'endpoint' => 'https://us-east1-my_gcf_project.cloudfunctions.net/remote_add', + 'connection' => 'projects/project-id/locations/us/connections/connection-id', + 'maxBatchingRows' => '10', + 'userDefinedContext' => [ + 'key' => 'value' + ] + ] + ]; + + $expected = $old + $new; + + $this->connection->getRoutine($this->identity) + ->willReturn($old) + ->shouldBeCalledTimes(1); + + $this->connection->updateRoutine($this->identity + $expected + ['retries' => 0]) + ->shouldBeCalledTimes(1) + ->willReturn($expected); + + $this->routine->___setProperty('connection', $this->connection->reveal()); + $res = $this->routine->update($new); + + $this->assertEquals($expected, $res); + } + public function testDelete() { $this->connection->deleteRoutine($this->identity + ['retries' => 0]) From cc3f7d2cffe3cb0a40272850a4b38a1b0d2605d2 Mon Sep 17 00:00:00 2001 From: hectorhammett Date: Tue, 19 May 2026 19:55:16 +0000 Subject: [PATCH 2/2] Modified the system test to make use of the BigQueryConnection client --- BigQuery/composer.json | 3 +- BigQuery/tests/System/ManageRoutinesTest.php | 48 ++++++++------------ 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/BigQuery/composer.json b/BigQuery/composer.json index 85e54c772350..a76f22154485 100644 --- a/BigQuery/composer.json +++ b/BigQuery/composer.json @@ -17,7 +17,8 @@ "erusev/parsedown": "^1.6", "google/cloud-storage": "^2.0", "google/cloud-bigquery-reservation": "^2.0", - "nikic/php-parser": "^5.0" + "nikic/php-parser": "^5.0", + "google/cloud-bigquery-connection": "^2.1" }, "suggest": { "google/cloud-storage": "Makes it easier to load data from Cloud Storage into BigQuery" diff --git a/BigQuery/tests/System/ManageRoutinesTest.php b/BigQuery/tests/System/ManageRoutinesTest.php index b5af2a7220bf..df50dd56aa0c 100644 --- a/BigQuery/tests/System/ManageRoutinesTest.php +++ b/BigQuery/tests/System/ManageRoutinesTest.php @@ -17,9 +17,12 @@ namespace Google\Cloud\BigQuery\Tests\System; +use Google\Cloud\BigQuery\Connection\V1\Client\ConnectionServiceClient; +use Google\Cloud\BigQuery\Connection\V1\CloudResourceProperties; +use Google\Cloud\BigQuery\Connection\V1\Connection; +use Google\Cloud\BigQuery\Connection\V1\CreateConnectionRequest; +use Google\Cloud\BigQuery\Connection\V1\DeleteConnectionRequest; use Google\Cloud\BigQuery\Routine; -use Google\Cloud\Core\RequestWrapper; -use GuzzleHttp\Psr7\Request; /** * @group bigquery @@ -178,45 +181,32 @@ public function testCreateRemoteUdf() private function createConnection($connectionId) { - // There is a BigQueryConnection client available in the PHP cloud - // but decided to create it here manually instead of adding it as a dependency just for testing. $projectId = self::$dataset->identity()['projectId']; $location = 'us'; $parent = "projects/$projectId/locations/$location"; - $uri = "https://bigqueryconnection.googleapis.com/v1/$parent/connections?connectionId=$connectionId"; - $body = json_encode([ - 'friendlyName' => $connectionId, - 'cloudResource' => new \stdClass() - ]); + $connectionClient = new ConnectionServiceClient(); - $request = new Request( - 'POST', - $uri, - ['Content-Type' => 'application/json'], - $body - ); + $connection = new Connection(); + $connection->setFriendlyName($connectionId); + $connection->setCloudResource(new CloudResourceProperties()); - $requestWrapper = new RequestWrapper([ - 'keyFilePath' => getenv('GOOGLE_CLOUD_PHP_TESTS_KEY_PATH'), - 'scopes' => ['https://www.googleapis.com/auth/cloud-platform'] - ]); + $request = new CreateConnectionRequest(); + $request->setParent($parent); + $request->setConnectionId($connectionId); + $request->setConnection($connection); - $response = $requestWrapper->send($request); - $data = json_decode($response->getBody(), true); - return $data['name']; + $response = $connectionClient->createConnection($request); + return $response->getName(); } private function deleteConnection($name) { - $uri = "https://bigqueryconnection.googleapis.com/v1/$name"; - $request = new Request('DELETE', $uri); + $connectionClient = new ConnectionServiceClient(); - $requestWrapper = new RequestWrapper([ - 'keyFilePath' => getenv('GOOGLE_CLOUD_PHP_TESTS_KEY_PATH'), - 'scopes' => ['https://www.googleapis.com/auth/cloud-platform'] - ]); + $request = new DeleteConnectionRequest(); + $request->setName($name); - $requestWrapper->send($request); + $connectionClient->deleteConnection($request); } }