Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Spanner/src/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class Database
public const TYPE_JSON = TypeCode::JSON;
public const TYPE_PG_OID = 'pgOid';
public const TYPE_INTERVAL = TypeCode::INTERVAL;
public const TYPE_UUID = TypeCode::UUID;

private Operation $operation;
private IamManager|null $iam = null;
Expand Down
20 changes: 20 additions & 0 deletions Spanner/src/SpannerClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,26 @@ public function pgOid(string|null $value): PgOid
return new PgOid($value);
}

/**
* Create a UUID object.
*
* The string value must consists of 32 hexadecimal digits in five groups separated
* by hyphens in the form 8-4-4-4-12. The hexadecimal digits represent 122
* random bits and 6 fixed bits, in compliance with RFC 4122 section 4.4.
*
* Example:
* ```
* $uuid = $spanner->uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479');
* ```
*
* @param string $value The UUID value.
* @return Uuid
*/
public function uuid(string $value): Uuid
{
return new Uuid($value);
}

/**
* Create an Int64 object. This can be used to work with 64 bit integers as
* a string value while on a 32 bit platform.
Expand Down
95 changes: 95 additions & 0 deletions Spanner/src/Uuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Spanner;

/**
* Represents a value with a data type of
* [UUID](https://cloud.google.com/spanner/docs/reference/standard-sql/data-types#uuid_type).
*
* Example:
* ```
* use Google\Cloud\Spanner\SpannerClient;
*
* $spanner = new SpannerClient(['projectId' => 'my-project']);
*
* $uuid = $spanner->uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479');
* ```
*/
class Uuid implements ValueInterface
{
/**
* @var string
*/
private $value;

/**
* @param string $value The UUID value.
* @throws \InvalidArgumentException
*/
public function __construct(string $value)
{
// Canonical UUID format: 8-4-4-4-12 hex digits
$pattern = '/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/';
if (!preg_match($pattern, $value)) {
throw new \InvalidArgumentException(
'Invalid UUID format. Expected canonical hexadecimal format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
);
}
$this->value = strtolower($value);
}

/**
* Get the underlying value.
*
* @return string
*/
public function get(): string
{
return $this->value;
}

/**
* Get the type.
*
* @return int
*/
public function type(): int
{
return Database::TYPE_UUID;
}

/**
* Format the value as a string.
*
* @return string
*/
public function formatAsString(): string
{
return $this->value;
}

/**
* Format the value as a string.
*
* @return string
*/
public function __toString()
{
return $this->value;
}
}
5 changes: 5 additions & 0 deletions Spanner/src/ValueMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ValueMapper
const TYPE_JSON = TypeCode::JSON;
const TYPE_PROTO = TypeCode::PROTO;
const TYPE_INTERVAL = TypeCode::INTERVAL;
const TYPE_UUID = TypeCode::UUID;
const TYPE_PG_NUMERIC = 'pgNumeric';
const TYPE_PG_JSONB = 'pgJsonb';
const TYPE_PG_OID = 'pgOid';
Expand All @@ -74,6 +75,7 @@ class ValueMapper
self::TYPE_FLOAT32,
self::TYPE_PROTO,
self::TYPE_INTERVAL,
self::TYPE_UUID,
];

/*
Expand Down Expand Up @@ -386,6 +388,9 @@ private function decodeValue(mixed $value, array $type): mixed
}
$value = Interval::parse($value);
break;
case self::TYPE_UUID:
$value = new Uuid($value);
break;
}

return $value;
Expand Down
14 changes: 11 additions & 3 deletions Spanner/tests/System/WriteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public static function setUpTestFixtures(): void
stringField STRING(MAX),
timestampField TIMESTAMP,
numericField NUMERIC,
uuidField STRING(36),
arrayUuidField ARRAY<STRING(36)>,
protoField `testing.data.User`,
) PRIMARY KEY (id)',
'CREATE TABLE ' . self::COMMIT_TIMESTAMP_TABLE_NAME . ' (
Expand Down Expand Up @@ -108,7 +110,8 @@ public function fieldValueProvider()
[$this->randId(), 'intField', 787878787],
[$this->randId(), 'stringField', 'foo bar'],
[$this->randId(), 'timestampField', new Timestamp(new \DateTime())],
[$this->randId(), 'numericField', new Numeric('0.123456789')]
[$this->randId(), 'numericField', new Numeric('0.123456789')],
[$this->randId(), 'uuidField', new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479')]
];
}

Expand Down Expand Up @@ -136,7 +139,7 @@ public function testWriteAndReadBackValue($id, $field, $value)
$read = $db->read(self::TABLE_NAME, $keyset, [$field]);
$row = $read->rows()->current();

if ($value instanceof Timestamp) {
if ($value instanceof Timestamp || $value instanceof Uuid) {
$this->assertEquals($value->formatAsString(), $row[$field]->formatAsString());
} else {
$this->assertValues($value, $row[$field]);
Expand All @@ -150,7 +153,7 @@ public function testWriteAndReadBackValue($id, $field, $value)
]);

$row = $exec->rows()->current();
if ($value instanceof Timestamp) {
if ($value instanceof Timestamp || $value instanceof Uuid) {
$this->assertEquals($value->formatAsString(), $row[$field]->formatAsString());
} elseif ($value instanceof Message) {
$this->assertInstanceOf(Proto::class, $row[$field]);
Expand Down Expand Up @@ -305,6 +308,11 @@ public function arrayFieldValueProvider()
new User(['name' => 'User 1']),
new User(['name' => 'User 2']),
]],
[$this->randId(), 'arrayUuidField', [
new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479'),
new Uuid('a47ac10b-58cc-4372-a567-0e02b2c3d479')
]],
[$this->randId(), 'arrayUuidField', null],
];
}

Expand Down
55 changes: 55 additions & 0 deletions Spanner/tests/Unit/UuidTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Spanner\Tests\Unit;

use Google\Cloud\Spanner\Database;
use Google\Cloud\Spanner\Uuid;
use PHPUnit\Framework\TestCase;

/**
* @group spanner
*/
class UuidTest extends TestCase
{
public function testUuid()
{
$val = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
$uuid = new Uuid($val);

$this->assertEquals($val, $uuid->get());
$this->assertEquals($val, (string) $uuid);
$this->assertEquals($val, $uuid->formatAsString());
$this->assertEquals(Database::TYPE_UUID, $uuid->type());
}

public function testUuidLowercase()
{
$val = 'F47AC10B-58CC-4372-A567-0E02B2C3D479';
$uuid = new Uuid($val);

$this->assertEquals(strtolower($val), $uuid->get());
}

public function testInvalidUuid()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid UUID format');

new Uuid('invalid-uuid');
}
}
56 changes: 54 additions & 2 deletions Spanner/tests/Unit/ValueMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use Google\Cloud\Spanner\V1\TypeAnnotationCode;
use Google\Cloud\Spanner\V1\TypeCode;
use Google\Cloud\Spanner\ValueMapper;
use Google\Cloud\Spanner\Uuid;
use PHPUnit\Framework\TestCase;
use Testing\Data\Book;
use Testing\Data\User;
Expand Down Expand Up @@ -73,7 +74,8 @@ public function simpleTypes()
[1, Database::TYPE_INT64],
['john', Database::TYPE_STRING],
[3.1415, Database::TYPE_FLOAT64],
[false, Database::TYPE_BOOL]
[false, Database::TYPE_BOOL],
[new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479'), Database::TYPE_UUID]
];
}

Expand Down Expand Up @@ -858,7 +860,8 @@ public function simpleTypeValues()
[new Bytes('hello world'), base64_encode('hello world')],
[new Proto('hello world', 'foo'), 'hello world'],
[['foo', 'bar']],
['{\"rating\":9,\"open\":true}']
['{\"rating\":9,\"open\":true}'],
[new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479'), 'f47ac10b-58cc-4372-a567-0e02b2c3d479']
];
}

Expand Down Expand Up @@ -1379,4 +1382,53 @@ public function provideFloatTypes()
[Database::TYPE_FLOAT32]
];
}

public function testDecodeValuesUuid()
{
$uuid = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
$res = $this->mapper->decodeValues(
$this->createField(Database::TYPE_UUID),
$this->createRow($uuid),
Result::RETURN_ASSOCIATIVE
);
$this->assertInstanceOf(Uuid::class, $res['rowName']);
$this->assertEquals($uuid, $res['rowName']->get());
}

public function testFormatParamsForExecuteSqlArrayUuid()
{
$uuid = new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479');
$params = [
'array' => [$uuid]
];

$res = $this->mapper->formatParamsForExecuteSql($params);

$this->assertEquals($uuid->get(), $res['params']['array'][0]);
$this->assertEquals(Database::TYPE_ARRAY, $res['paramTypes']['array']['code']);
$this->assertEquals(Database::TYPE_UUID, $res['paramTypes']['array']['arrayElementType']['code']);
}

public function testFormatParamsForExecuteSqlStructUuid()
{
$uuid = new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479');
$params = [
'struct' => [
'uuid' => $uuid
]
];
$types = [
'struct' => (new StructType())
->add('uuid', Database::TYPE_UUID)
];

$res = $this->mapper->formatParamsForExecuteSql($params, $types);

$this->assertEquals($uuid->get(), $res['params']['struct'][0]);
$this->assertEquals(Database::TYPE_STRUCT, $res['paramTypes']['struct']['code']);
$this->assertEquals(
Database::TYPE_UUID,
$res['paramTypes']['struct']['structType']['fields'][0]['type']['code']
);
}
}
Loading