Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improved latency for executeStreaminSql calls #7254

Merged
merged 9 commits into from
Apr 26, 2024
2 changes: 1 addition & 1 deletion Spanner/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"php": "^8.0",
"ext-grpc": "*",
"google/cloud-core": "^1.52.7",
"google/gax": "^1.30"
"google/gax": "^1.32"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
Expand Down
87 changes: 87 additions & 0 deletions Spanner/src/Connection/Grpc.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
use Google\Cloud\Spanner\V1\Mutation;
use Google\Cloud\Spanner\V1\Mutation\Delete;
use Google\Cloud\Spanner\V1\Mutation\Write;
use Google\Cloud\Spanner\V1\PartialResultSet;
use Google\Cloud\Spanner\V1\PartitionOptions;
use Google\Cloud\Spanner\V1\RequestOptions;
use Google\Cloud\Spanner\V1\Session;
Expand All @@ -68,6 +69,7 @@
use Google\Protobuf;
use Google\Protobuf\FieldMask;
use Google\Protobuf\GPBEmpty;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\ListValue;
use Google\Protobuf\Struct;
use Google\Protobuf\Value;
Expand Down Expand Up @@ -235,6 +237,24 @@ public function __construct(array $config = [])
'google.protobuf.Timestamp' => function ($v) {
return $this->formatTimestampFromApi($v);
}
], [], [], [
// A custom encoder that short-circuits the encodeMessage in Serializer class,
// but only if the argument is of the type PartialResultSet.
PartialResultSet::class => function ($msg) {
$data = json_decode($msg->serializeToJsonString(), true);

// The transaction id is serialized as a base64 encoded string in $data. So, we
// add a step to get the transaction id using a getter instead of the serialized value.
// The null-safe operator is used to handle edge cases where the relevant fields are not present.
$data['metadata']['transaction']['id'] = (string) $msg?->getMetadata()?->getTransaction()?->getId();

// Helps convert metadata enum values from string types to their respective code/annotation
// pairs. Ex: INT64 is converted to {code: 2, typeAnnotation: 0}.
$fields = $msg->getMetadata()?->getRowType()?->getFields();
$data['metadata']['rowType']['fields'] = $this->getFieldDataFromRepeatedFields($fields);

return $data;
}
]);
//@codeCoverageIgnoreEnd

Expand Down Expand Up @@ -1620,4 +1640,71 @@ private function setDirectedReadOptions(array &$args)
);
}
}

/**
* Utiltiy method to take in a Google\Cloud\Spanner\V1\Type value and return
* the data as an array. The method takes care of array and struct elements.
*
* @param Type $type The "type" object
*
* @return array The formatted data.
*/
private function getTypeData(Type $type): array
{
$data = [
'code' => $type->getCode(),
'typeAnnotation' => $type->getTypeAnnotation(),
'protoTypeFqn' => $type->getProtoTypeFqn()
];

// If this is a struct field, then recursisevly call getTypeData
if ($type->hasStructType()) {
$nestedType = $type->getStructType();
$fields = $nestedType->getFields();
$data['structType'] = [
'fields' => $this->getFieldDataFromRepeatedFields($fields)
];
}
// If this is an array field, then recursisevly call getTypeData
if ($type->hasArrayElementType()) {
$nestedType = $type->getArrayElementType();
$data['arrayElementType'] = $this->getTypeData($nestedType);
}

return $data;
}

/**
* Utility method to return "fields data" in the format:
* [
* "name" => ""
* "type" => []
* ].
*
* The type is converted from a string like INT64 to ["code" => 2, "typeAnnotation" => 0]
* conforming with the Google\Cloud\Spanner\V1\TypeCode class.
*
* @param ?RepeatedField $fields The array contain list of fields.
*
* @return array The formatted fields data.
*/
private function getFieldDataFromRepeatedFields(?RepeatedField $fields): array
{
if (is_null($fields)) {
return [];
}

$fieldsData = [];
foreach ($fields as $key => $field) {
$type = $field->getType();
$typeData = $this->getTypeData($type);

$fieldsData[$key] = [
'name' => $field->getName(),
'type' => $typeData
];
}

return $fieldsData;
}
}
33 changes: 33 additions & 0 deletions Spanner/tests/Unit/Connection/GrpcTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Google\ApiCore\CredentialsWrapper;
use Google\ApiCore\OperationResponse;
use Google\ApiCore\Serializer;
use Google\ApiCore\Testing\MockResponse;
use Google\ApiCore\Transport\TransportInterface;
use Google\Cloud\Core\GrpcRequestWrapper;
use Google\Cloud\Core\GrpcTrait;
Expand All @@ -40,6 +41,7 @@
use Google\Cloud\Spanner\V1\Mutation;
use Google\Cloud\Spanner\V1\Mutation\Delete;
use Google\Cloud\Spanner\V1\Mutation\Write;
use Google\Cloud\Spanner\V1\PartialResultSet;
use Google\Cloud\Spanner\V1\PartitionOptions;
use Google\Cloud\Spanner\V1\RequestOptions;
use Google\Cloud\Spanner\V1\Session;
Expand Down Expand Up @@ -1418,6 +1420,37 @@ public function larOptions()
];
}

public function testPartialResultSetCustomEncoder()
{
$partialResultSet = new PartialResultSet();
$partialResultSet->mergeFromJsonString(json_encode([
'metadata' => [
'transaction' => [
'id' => base64_encode(0b00010100) // bytedata is represented as a base64-encoded string in JSON
],
'rowType' => [
'fields' => [
['type' => ['code' => 'INT64']] // enums are represented as their string equivalents in JSON
]
],
],
]));

$this->assertEquals(0b00010100, $partialResultSet->getMetadata()->getTransaction()->getId());
$this->assertEquals(2, $partialResultSet->getMetadata()->getRowType()->getFields()[0]->getType()->getCode());

// decode the message and ensure it's decoded as expected
$grpc = new Grpc();
$serializerProp = new \ReflectionProperty($grpc, 'serializer');
$serializerProp->setAccessible(true);
$serializer = $serializerProp->getValue($grpc);
$arr = $serializer->encodeMessage($partialResultSet);

saranshdhingra marked this conversation as resolved.
Show resolved Hide resolved
// We expect this to be the binary string
$this->assertEquals(0b00010100, $arr['metadata']['transaction']['id']);
// We expect this to be the integer
$this->assertEquals(2, $arr['metadata']['rowType']['fields'][0]['type']['code']);
}
private function assertCallCorrect(
$method,
array $args,
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"monolog/monolog": "^2.9||^3.0",
"psr/http-message": "^1.0|^2.0",
"ramsey/uuid": "^4.0",
"google/gax": "^1.30",
"google/gax": "^1.32",
"google/common-protos": "^4.4",
"google/auth": "^1.34"
},
Expand Down
Loading