diff --git a/Core/src/Testing/System/SystemTestCase.php b/Core/src/Testing/System/SystemTestCase.php index 1b1e257c5aec..5b2eb829e888 100644 --- a/Core/src/Testing/System/SystemTestCase.php +++ b/Core/src/Testing/System/SystemTestCase.php @@ -76,7 +76,7 @@ public static function processQueue() */ public static function randId() { - return rand(1, 9999999); + return rand(1, 999999999); } /** diff --git a/Spanner/composer.json b/Spanner/composer.json index ad0b8cfb3eb9..f1829221006a 100644 --- a/Spanner/composer.json +++ b/Spanner/composer.json @@ -22,7 +22,8 @@ "dg/bypass-finals": "^1.7", "dms/phpunit-arraysubset-asserts": "^0.5.0", "symfony/process": "^6.4", - "nikic/php-parser": "^5.0" + "nikic/php-parser": "^5.0", + "brianium/paratest": "^6.11" }, "suggest": { "ext-protobuf": "Provides a significant increase in throughput over the pure PHP protobuf implementation. See https://cloud.google.com/php/grpc for installation instructions.", diff --git a/Spanner/tests/System/AdminTest.php b/Spanner/tests/System/AdminTest.php index 383c60a0eeba..6f50962c7a03 100644 --- a/Spanner/tests/System/AdminTest.php +++ b/Spanner/tests/System/AdminTest.php @@ -68,7 +68,7 @@ public function testInstance() $this->assertEquals(Instance::STATE_READY, $instance->state()); $displayName = uniqid(self::TESTING_PREFIX); - $processingUnits = 500; + $processingUnits = 1000; $op = $instance->update([ 'displayName' => $displayName, 'processingUnits' => $processingUnits, diff --git a/Spanner/tests/System/BackupTest.php b/Spanner/tests/System/BackupTest.php index 8cae2748a873..3a234b6d5939 100644 --- a/Spanner/tests/System/BackupTest.php +++ b/Spanner/tests/System/BackupTest.php @@ -17,8 +17,12 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\ApiCore\ApiException; + use Google\Cloud\Core\Exception\BadRequestException; use Google\Cloud\Core\Exception\ConflictException; +use Google\Cloud\Core\Exception\FailedPreconditionException; +use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\System\SystemTestCase; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; @@ -26,11 +30,15 @@ use Google\Cloud\Spanner\Admin\Database\V1\EncryptionInfo\Type; use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseEncryptionConfig; use Google\Cloud\Spanner\Backup; +use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; +use Google\Rpc\Code; /** * @group spanner + * @group flakey */ + class BackupTest extends SystemTestCase { use SystemTestCaseTrait; @@ -41,21 +49,41 @@ class BackupTest extends SystemTestCase // Example: 4 hours (4 * 3600 seconds) const LONG_TIMEOUT_SECONDS = 4 * 3600; - protected static $backupId1; - protected static $backupId2; + // EXPIRE_TIME is initialized in setUpTestFixtures to support older PHP versions + + protected static $backupId; + protected static $cancelBackupId; protected static $copyBackupId; protected static $backupOperationName; protected static $restoreOperationName; - protected static $createTime1; - protected static $createTime2; - - protected static $dbName1; - protected static $dbName2; - + protected static $restoreDbName; + protected static $createTime; + protected static $expireTime; + protected static $backupDbName; protected static $project; private static $hasSetUpBackup = false; + private float $testStartTime; + + /** + * @before + */ + public function startTestTimer(): void + { + $this->testStartTime = microtime(true); + } + + /** + * @after + */ + public function logTestDuration(): void + { + $duration = microtime(true) - $this->testStartTime; + + self::debugLog($this->getName(), sprintf('Time taken: %.2f seconds', $duration)); + } + /** * @beforeClass */ @@ -68,72 +96,79 @@ public static function setUpTestFixtures(): void return; } - self::$project = self::parseName(self::$instance->name(), 'project'); + self::$expireTime = new \DateTime('+7 hours'); - if (!self::$dbName1 = getenv('GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE_1')) { - self::$dbName1 = uniqid(self::TESTING_PREFIX); - self::$deletionQueue->add(function () { - self::getDatabaseInstance(self::$dbName1)->drop(); - }); - } + self::$project = self::parseName(self::$instance->name(), 'project'); - if (!self::$dbName2 = getenv('GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE_2')) { - self::$dbName2 = uniqid(self::TESTING_PREFIX); + if (!self::$backupDbName = getenv('GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE')) { + self::$backupDbName = uniqid(self::TESTING_PREFIX); self::$deletionQueue->add(function () { - self::getDatabaseInstance(self::$dbName2)->drop(); + self::getDatabaseInstance(self::$backupDbName)->drop(); }); + } else { + self::cancelPendingBackups(self::$backupDbName); } - $db1 = self::getDatabaseInstance(self::$dbName1); - $db2 = self::getDatabaseInstance(self::$dbName2); + $db = self::getDatabaseInstance(self::$backupDbName); - if (!$db1->exists()) { - $op = self::$instance->createDatabase(self::$dbName1); - $op->pollUntilComplete(); - $db1->updateDdl( + if (!$db->exists()) { + $statements = [ 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' ( id INT64 NOT NULL, name STRING(MAX) NOT NULL, birthday DATE NOT NULL ) PRIMARY KEY (id)' - )->pollUntilComplete(); - self::insertData(5, self::$dbName1); + ]; + $dbOp = self::$instance->createDatabase(self::$backupDbName, ['statements' => $statements]); + $dbOp->pollUntilComplete(); + self::insertData(5, self::$backupDbName); } - if (!$db2->exists()) { - $op = self::$instance->createDatabase(self::$dbName2); - $op->pollUntilComplete(); - - $db2->updateDdl( - 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' ( - id INT64 NOT NULL, - name STRING(MAX) NOT NULL, - birthday DATE NOT NULL - ) PRIMARY KEY (id)' - )->pollUntilComplete(); - self::insertData(10, self::$dbName2); - } - - self::$backupId1 = uniqid(self::BACKUP_PREFIX); - self::$backupId2 = uniqid('users-'); + self::$backupId = uniqid(self::BACKUP_PREFIX); + self::$cancelBackupId = uniqid('cancel-'); self::$copyBackupId = uniqid('copy-'); self::$hasSetUpBackup = true; } - public function testCreateBackup() + /** + * Tests that attempting to delete a backup that does not exist + * is safe and does not throw an exception. + */ + public function testDeleteNonExistantBackup() + { + $backup = self::$instance->backup('does_not_exis'); + + $this->assertFalse($backup->exists()); + + $backup->delete(); + } + + /** + * Tests the successful creation of a backup. + * We start the long-running operation, verify the initial metadata (CREATING state), + * and then poll until the backup is READY. + * This primary backup is used as a fixture by many subsequent read-only tests. + */ + public function testCreateBackupAndInitCopyAndRestore(): array { - $expireTime = new \DateTime('+7 hours'); $encryptionConfig = [ 'encryptionType' => CreateBackupEncryptionConfig\EncryptionType::GOOGLE_DEFAULT_ENCRYPTION, ]; - $backup = self::$instance->backup(self::$backupId1); - $db1 = self::getDatabaseInstance(self::$dbName1); + $backup = self::$instance->backup(self::$backupId); + $db = self::getDatabaseInstance(self::$backupDbName); - self::$createTime1 = gmdate('"Y-m-d\TH:i:s\Z"'); - $op = $backup->create(self::$dbName1, $expireTime, [ + self::$createTime = gmdate('"Y-m-d\TH:i:s\Z"'); + $op = $backup->create(self::$backupDbName, self::$expireTime, [ 'encryptionConfig' => $encryptionConfig, ]); + + self::$deletionQueue->add(function () use ($backup) { + if ($backup->exists()) { + $backup->delete(); + } + }); + self::$backupOperationName = $op->name(); $metadata = null; @@ -149,34 +184,46 @@ public function testCreateBackup() $this->assertArrayHasKey('progressPercent', $metadata['progress']); $this->assertArrayHasKey('startTime', $metadata['progress']); - // Poll for completion with the extended timeout - $op->pollUntilComplete([ - 'timeoutMillis' => self::LONG_TIMEOUT_SECONDS * 1000 // GAX expects milliseconds - ]); - - self::$deletionQueue->add(function () use ($backup) { - $backup->delete(); - }); + $this->assertNotNull($metadata); $this->assertTrue($backup->exists()); $this->assertInstanceOf(Backup::class, $backup); - $this->assertEquals(self::$backupId1, DatabaseAdminClient::parseName($backup->info()['name'])['backup']); - $this->assertEquals(self::$dbName1, DatabaseAdminClient::parseName($backup->info()['database'])['database']); - $this->assertEquals($expireTime->format('Y-m-d\TH:i:s.u\Z'), $backup->info()['expireTime']); + $this->assertEquals(self::$backupId, DatabaseAdminClient::parseName($backup->info()['name'])['backup']); + $this->assertEquals( + self::$backupDbName, + DatabaseAdminClient::parseName($backup->info()['database'])['database'] + ); + $this->assertEquals(self::$expireTime->format('Y-m-d\TH:i:s.u\Z'), $backup->info()['expireTime']); $this->assertTrue(is_string($backup->info()['createTime'])); - $this->assertEquals(Backup::STATE_READY, $backup->state()); - $this->assertTrue($backup->info()['sizeBytes'] > 0); - if (!getenv('GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE_1')) { + + if (!getenv('GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE')) { // earliestVersionTime deviates from backup's versionTime by a couple of minutes - $expectedDateTime = \DateTime::createFromFormat('Y-m-d\TH:i:s.u\Z', $db1->info()['earliestVersionTime']); + $expectedDateTime = \DateTime::createFromFormat('Y-m-d\TH:i:s.u\Z', $db->info()['earliestVersionTime']); $actualDateTime = \DateTime::createFromFormat('Y-m-d\TH:i:s.u\Z', $backup->info()['versionTime']); $this->assertEqualsWithDelta($expectedDateTime->getTimestamp(), $actualDateTime->getTimestamp(), 300); } $this->assertEquals(Type::GOOGLE_DEFAULT_ENCRYPTION, $backup->info()['encryptionInfo']['encryptionType']); - $this->assertNotNull($metadata); + // Poll for completion with the extended timeout + $this->pollWithExtendedTimeout($op, __FUNCTION__); + + $backup->reload(); + $this->assertEquals(Backup::STATE_READY, $backup->state()); + $this->assertTrue($backup->info()['sizeBytes'] > 0); + + return [ + 'copy' => $this->createBackupCopy(), + 'restore' => $this->restoreToNewDatabase() + ]; } + /** + * Tests that attempting to create a backup with an expiration time in the past fails. + * It expects a BadRequestException or FailedPreconditionException to be thrown, + * and verifies that the backup is not created. + * + * @depends testCreateBackupAndInitCopyAndRestore + */ public function testCreateBackupRequestFailed() { $backupId = uniqid(self::BACKUP_PREFIX); @@ -185,15 +232,39 @@ public function testCreateBackupRequestFailed() $backup = self::$instance->backup($backupId); $e = null; - try { - $backup->create(self::$dbName1, $expireTime); - } catch (BadRequestException $e) { + $max_retries = 3; + for ($i = 0; $i < $max_retries; $i++) { + try { + $backup->create(self::$backupDbName, $expireTime); + break; + } catch (BadRequestException | FailedPreconditionException $e) { + break; + } catch (ServiceException | ApiException $ex) { + $allowed = [Code::UNAVAILABLE, Code::DEADLINE_EXCEEDED]; + if ($i === 2 || !in_array($ex->getCode(), $allowed)) { + throw $ex; + } + self::debugLog( + __FUNCTION__, + 'Caught ' . \get_class($ex) . ' with Code ' + . Code::name($ex->getCode()) . ' on retry attempt ' . ($i + 1) + . ' out of ' . $max_retries + ); + sleep(2); + } } - $this->assertInstanceOf(BadRequestException::class, $e); + $this->assertNotNull($e); + $this->assertTrue($e instanceof BadRequestException || $e instanceof FailedPreconditionException); $this->assertFalse($backup->exists()); } + /** + * Tests that providing invalid arguments (like an invalid version time type + * or a malformed KMS key name) when creating a backup fails with the expected exceptions. + * + * @depends testCreateBackupAndInitCopyAndRestore + */ public function testCreateBackupInvalidArgument() { $backupId = uniqid(self::BACKUP_PREFIX); @@ -203,7 +274,7 @@ public function testCreateBackupInvalidArgument() $e = null; try { - $backup->create(self::$dbName1, $expireTime, [ + $backup->create(self::$backupDbName, $expireTime, [ 'versionTime' => 'invalidType', ]); } catch (\InvalidArgumentException $e) { @@ -214,7 +285,7 @@ public function testCreateBackupInvalidArgument() $e = null; try { - $backup->create(self::$dbName1, $expireTime, [ + $backup->create(self::$backupDbName, $expireTime, [ 'encryptionConfig' => ['kmsKeyName' => 'validKeyName'], ]); } catch (BadRequestException $e) { @@ -225,88 +296,44 @@ public function testCreateBackupInvalidArgument() } /** - * @depends testCreateBackup + * Tests that providing an invalid KMS key name when restoring a database + * from a backup results in a BadRequestException. + * + * @depends testCreateBackupAndInitCopyAndRestore */ - public function testCancelBackupOperation() - { - $expireTime = new \DateTime('+7 hours'); - $backup = self::$instance->backup(self::$backupId2); - - self::$createTime2 = gmdate('"Y-m-d\TH:i:s\Z"'); - $op = $backup->create(self::$dbName2, $expireTime); - $op->pollUntilComplete(); - - self::$deletionQueue->add(function () use ($backup) { - $backup->delete(); - }); - - $op->cancel(); - - $this->assertTrue($backup->exists()); - } - - /** - * @depends testCreateBackup - */ - public function testCreateBackupCopy() + public function testRestoreInvalidArgument() { - $backup = self::$instance->backup(self::$backupId1); - $newBackup = self::$instance->backup(self::$copyBackupId); - $expireTime = new \DateTime('+7 hours'); - $op = $backup->createCopy($newBackup, $expireTime); + $restoreDbName = uniqid('restored_db_'); - $metadata = null; - foreach (self::$instance->backupOperations() as $lro) { - if ($lro->name() == $op->name()) { - $metadata = $lro->info()['metadata']; - break; - } + $e = null; + try { + $this::$instance->createDatabaseFromBackup( + $restoreDbName, + self::fullyQualifiedBackupName(self::$backupId), + [ + 'encryptionConfig' => [ + 'kmsKeyName' => 'validKmsKey' + ] + ] + ); + } catch (BadRequestException $e) { } + $database = self::$instance->database($restoreDbName); - $this->assertNotNull($metadata); - $this->assertArrayHasKey('progress', $metadata); - $this->assertArrayHasKey('progressPercent', $metadata['progress']); - $this->assertArrayHasKey('startTime', $metadata['progress']); - - $op->pollUntilComplete(); - - self::$deletionQueue->add(function () use ($newBackup) { - $newBackup->delete(); - }); - - $this->assertTrue($newBackup->exists()); - $this->assertInstanceOf(Backup::class, $newBackup); - $this->assertEquals(self::$copyBackupId, DatabaseAdminClient::parseName($newBackup->info()['name'])['backup']); - $this->assertEquals(self::$dbName1, DatabaseAdminClient::parseName($newBackup->info()['database'])['database']); - $this->assertEquals($expireTime->format('Y-m-d\TH:i:s.u\Z'), $newBackup->info()['expireTime']); - $this->assertTrue(is_string($newBackup->info()['createTime'])); - $this->assertEquals(Backup::STATE_READY, $newBackup->state()); - $this->assertTrue($newBackup->info()['sizeBytes'] > 0); - $this->assertEquals(Type::GOOGLE_DEFAULT_ENCRYPTION, $newBackup->info()['encryptionInfo']['encryptionType']); - } - - /** - * @depends testCreateBackup - */ - public function testReloadBackup() - { - $backup = self::$instance->backup(self::$backupId1); - $backup->reload(); - - $this->assertEquals(self::$backupId1, DatabaseAdminClient::parseName($backup->info()['name'])['backup']); - $this->assertEquals(self::$dbName1, DatabaseAdminClient::parseName($backup->info()['database'])['database']); - $this->assertTrue(is_string($backup->info()['expireTime'])); - $this->assertTrue(is_string($backup->info()['createTime'])); - $this->assertEquals(Backup::STATE_READY, $backup->state()); - $this->assertTrue($backup->info()['sizeBytes'] > 0); + $this->assertInstanceOf(BadRequestException::class, $e); + $this->assertFalse($database->exists()); } /** - * @depends testCreateBackup + * Tests successfully updating the expiration time of an existing backup. + * This modifies the primary backup's expiration to 10 days in the future, + * which is later relied upon by expiration filtering tests to differentiate backups. + * + * @depends testCreateBackupAndInitCopyAndRestore */ public function testUpdateExpirationTime() { - $backup = self::$instance->backup(self::$backupId1); + $backup = self::$instance->backup(self::$backupId); $currentExpireTime = $backup->info()['expireTime']; @@ -319,11 +346,15 @@ public function testUpdateExpirationTime() } /** - * @depends testCreateBackup + * Tests that attempting to update a backup's expiration time to a value + * that is too soon (e.g. 5 minutes from now) fails, as Spanner requires + * backups to be retained for a longer minimum duration. + * + * @depends testCreateBackupAndInitCopyAndRestore */ public function testUpdateExpirationTimeFailed() { - $backup = self::$instance->backup(self::$backupId1); + $backup = self::$instance->backup(self::$backupId); $currentExpireTime = $backup->info()['expireTime']; @@ -342,33 +373,111 @@ public function testUpdateExpirationTimeFailed() $this->assertEquals($currentExpireTime, $backup->info()['expireTime']); } + /** + * Tests that listing backups scoped to a specific database + * successfully returns the expected backups. + * + * @depends testCreateBackupAndInitCopyAndRestore + */ + public function testListAllBackupsOfDatabase() + { + $database = self::$instance->database(self::$backupDbName); + $backups = iterator_to_array($database->backups()); + + $this->assertTrue(count($backups) > 0); + + foreach ($backups as $b) { + $this->assertEquals($database->name(), $b->info()['database']); + } + } + /** - * @depends testCreateBackup + * Tests that we can successfully list backup operations and filter + * them by the specific operation name from our backup creation. + * + * @depends testCreateBackupAndInitCopyAndRestore */ - public function testListAllBackups() + public function testListAllBackupOperations() { - $allBackups = iterator_to_array(self::$instance->backups(), false); + $backupOps = iterator_to_array($this::$instance->backupOperations([ + 'filter' => sprintf('name="%s"', self::$backupOperationName) + ])); - $backupNames = []; - foreach ($allBackups as $b) { - $backupNames[] = $b->name(); + $backupOpsNames = array_map(function ($bOp) { + return $bOp->name(); + }, $backupOps); + + $this->assertTrue(count($backupOps) > 0); + $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $backupOps); + $this->assertTrue(in_array(self::$backupOperationName, $backupOpsNames)); + } + + /** + * Tests that we can successfully cancel an in-progress backup creation operation. + * It starts a backup creation, immediately cancels it, and waits for the cancellation + * to complete to ensure the pending backup slot is freed. + * + * @depends testCreateBackupAndInitCopyAndRestore + */ + public function testCancelBackupOperation() + { + $backup = self::$instance->backup(self::$cancelBackupId); + + $op = $backup->create(self::$backupDbName, self::$expireTime); + + try { + $op->cancel(); + } catch (ServiceException | ApiException $e) { + if ($e->getCode() !== Code::DEADLINE_EXCEEDED) { + throw $e; + } + self::debugLog( + __FUNCTION__, + 'Caught ' . \get_class($e) . ' with Code ' . Code::name($e->getCode()) + ); } + + $this->pollWithExtendedTimeout($op, __FUNCTION__); + + $error = $op->info()['error'] ?? null; + $this->assertNotNull($error); + $this->assertEquals(Code::CANCELLED, $error['code']); + } + + + /** + * Tests listing all backups globally (across the instance) with a database filter, + * ensuring it returns instances of the Backup class. + * + * @depends testCreateBackupAndInitCopyAndRestore + */ + public function testListAllBackups() + { + $allBackups = iterator_to_array( + self::$instance->backups(['filter' => 'database:' . self::$backupDbName]), + false + ); $this->assertTrue(count($allBackups) > 0); $this->assertContainsOnlyInstancesOf(Backup::class, $allBackups); } /** - * @depends testCreateBackup + * Tests listing backups with a name filter to retrieve exactly the primary backup. + * + * @depends testCreateBackupAndInitCopyAndRestore */ public function testListAllBackupsContainsName() { - $backups = iterator_to_array(self::$instance->backups(['filter' => 'name:' . self::$backupId1])); + $backups = iterator_to_array(self::$instance->backups(['filter' => 'name:' . self::$backupId])); $this->assertTrue(count($backups) == 1); - $this->assertEquals(self::$backupId1, DatabaseAdminClient::parseName($backups[0]->info()['name'])['backup']); + $this->assertEquals(self::$backupId, DatabaseAdminClient::parseName($backups[0]->info()['name'])['backup']); } /** - * @depends testCreateBackup + * Tests filtering backups by their state to ensure that the primary backup + * is returned when querying for READY backups. + * + * @depends testCreateBackupAndInitCopyAndRestore */ public function testListAllBackupsReady() { @@ -379,43 +488,79 @@ public function testListAllBackupsReady() $backupNames[] = $b->name(); } - $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId1), $backupNames)); + $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId), $backupNames)); } /** - * @depends testCreateBackup + * Tests filtering backups by a creation timestamp. Since the primary backup + * was created at or after the test's recorded create time, it should be included. + * + * @depends testCreateBackupAndInitCopyAndRestore */ - public function testListAllBackupsOfDatabase() + public function testListAllBackupsCreatedAfterTimestamp() { - $database = self::$instance->database(self::$dbName1); - $backups = iterator_to_array($database->backups()); + $filter = sprintf('create_time >= %s', self::$createTime); - $this->assertTrue(count($backups) > 0); + $backups = iterator_to_array(self::$instance->backups(['filter' => $filter])); + $backupNames = []; foreach ($backups as $b) { - $this->assertEquals($database->name(), $b->info()['database']); + $backupNames[] = $b->name(); } + $this->assertTrue(count($backupNames) > 0); + $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId), $backupNames)); } /** - * @depends testCreateBackup + * Tests creating a copy of an existing backup. + * It polls until the copy operation finishes. The resulting backup copy + * is used as a secondary fixture for subsequent listing and pagination tests. + * + * @depends testCreateBackupAndInitCopyAndRestore */ - public function testListAllBackupsCreatedAfterTimestamp() + public function testCreateBackupCopy(array $ops) { - $filter = sprintf('create_time >= %s', self::$createTime1); - - $backups = iterator_to_array(self::$instance->backups(['filter' => $filter])); + $op = $ops['copy']; + $newBackup = self::$instance->backup(self::$copyBackupId); - $backupNames = []; - foreach ($backups as $b) { - $backupNames[] = $b->name(); + $metadata = null; + foreach (self::$instance->backupOperations() as $lro) { + if ($lro->name() == $op->name()) { + $metadata = $lro->info()['metadata']; + break; + } } - $this->assertTrue(count($backupNames) > 0); - $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId1), $backupNames)); + + $this->assertNotNull($metadata); + $this->assertArrayHasKey('progress', $metadata); + $this->assertArrayHasKey('progressPercent', $metadata['progress']); + $this->assertArrayHasKey('startTime', $metadata['progress']); + + $this->assertTrue($newBackup->exists()); + $this->assertInstanceOf(Backup::class, $newBackup); + $this->assertEquals(self::$copyBackupId, DatabaseAdminClient::parseName($newBackup->info()['name'])['backup']); + $this->assertEquals( + self::$backupDbName, + DatabaseAdminClient::parseName($newBackup->info()['database'])['database'] + ); + $this->assertTrue(is_string($newBackup->info()['createTime'])); + $this->assertEquals(Type::GOOGLE_DEFAULT_ENCRYPTION, $newBackup->info()['encryptionInfo']['encryptionType']); + + $this->pollWithExtendedTimeout($op, __FUNCTION__); + + $newBackup->reload(); + $this->assertEquals(Backup::STATE_READY, $newBackup->state()); + $this->assertTrue($newBackup->info()['sizeBytes'] > 0); } /** - * @depends testCreateBackup + * Tests filtering backups by their expiration timestamp. + * Relies on testUpdateExpirationTime() having modified the primary backup to + * expire in 10 days, while the backup copy expires in 7 hours. + * Filtering by < 9 hours should therefore exclude the primary backup + * but include the copy. + * + * @depends testCreateBackupCopy */ public function testListAllBackupsExpireBeforeTimestamp() { @@ -428,18 +573,22 @@ public function testListAllBackupsExpireBeforeTimestamp() $backupNames[] = $b->name(); } $this->assertTrue(count($backupNames) > 0); - $this->assertFalse(in_array(self::fullyQualifiedBackupName(self::$backupId1), $backupNames)); - $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId2), $backupNames)); + $this->assertFalse(in_array(self::fullyQualifiedBackupName(self::$backupId), $backupNames)); + $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$copyBackupId), $backupNames)); } /** - * @depends testCreateBackup + * Tests filtering backups by size. Since the copy is exact, both the primary + * backup and the copy will have sizes >= the primary backup's size, + * so both should be returned. + * + * @depends testCreateBackupCopy */ - public function testListAllBackupsWithSizeGreaterThanSomeBytes() + public function testListAllBackupsWithSizeGreaterOrEqualToSomeBytes() { - $backup = self::$instance->backup(self::$backupId1); + $backup = self::$instance->backup(self::$backupId); $size = $backup->info()['sizeBytes']; - $filter = 'size_bytes > ' . $size; + $filter = 'size_bytes >= ' . $size; $backups = iterator_to_array(self::$instance->backups(['filter' => $filter])); @@ -449,12 +598,12 @@ public function testListAllBackupsWithSizeGreaterThanSomeBytes() $backupNames[] = $b->name(); } $this->assertTrue(count($backupNames) > 0); - $this->assertFalse(in_array(self::fullyQualifiedBackupName(self::$backupId1), $backupNames)); - $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId2), $backupNames)); + $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId), $backupNames)); + $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$copyBackupId), $backupNames)); } /** - * @depends testCancelBackupOperation + * @depends testCreateBackupCopy */ public function testPagination() { @@ -473,34 +622,14 @@ public function testPagination() } /** - * @depends testRestoreToNewDatabase + * Tests that a backup can be successfully deleted. + * This cleans up the secondary backup copy that was used for the list/pagination tests. + * + * @depends testCreateBackupCopy */ - public function testListAllBackupOperations() - { - $backupOps = iterator_to_array($this::$instance->backupOperations()); - - $backupOpsNames = array_map(function ($bOp) { - return $bOp->name(); - }, $backupOps); - - $this->assertTrue(count($backupOps) > 0); - $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $backupOps); - $this->assertTrue(in_array(self::$backupOperationName, $backupOpsNames)); - } - public function testDeleteBackup() { - $backupId = uniqid(self::BACKUP_PREFIX); - $expireTime = new \DateTime('+7 hours'); - - $backup = self::$instance->backup($backupId); - - $op = $backup->create(self::$dbName1, $expireTime); - - // Poll for completion with the extended timeout - $op->pollUntilComplete([ - 'timeoutMillis' => self::LONG_TIMEOUT_SECONDS * 1000 // GAX expects milliseconds - ]); + $backup = self::$instance->backup(self::$copyBackupId); $this->assertTrue($backup->exists()); @@ -509,81 +638,35 @@ public function testDeleteBackup() $this->assertFalse($backup->exists()); } - public function testDeleteNonExistantBackup() - { - $backup = self::$instance->backup('does_not_exis'); - - $this->assertFalse($backup->exists()); - - $backup->delete(); - } - - public function testRestoreInvalidArgument() - { - $restoreDbName = uniqid('restored_db_'); - - $e = null; - try { - $this::$instance->createDatabaseFromBackup( - $restoreDbName, - self::fullyQualifiedBackupName(self::$backupId1), - [ - 'encryptionConfig' => [ - 'kmsKeyName' => 'validKmsKey' - ] - ] - ); - } catch (BadRequestException $e) { - } - $database = self::$instance->database($restoreDbName); - - $this->assertInstanceOf(BadRequestException::class, $e); - $this->assertFalse($database->exists()); - } - /** - * @depends testCreateBackup + * Tests restoring a database from a backup. + * This starts the restore LRO, verifies the metadata while restoring, + * and blocks until the restore completes. + * + * @depends testCreateBackupAndInitCopyAndRestore */ - public function testRestoreToNewDatabase() + public function testRestoreToNewDatabase(array $ops) { - $restoreDbName = uniqid('restored_db_'); - $encryptionConfig = [ - 'encryptionType' => RestoreDatabaseEncryptionConfig\EncryptionType::GOOGLE_DEFAULT_ENCRYPTION - ]; - - $op = $this::$instance->createDatabaseFromBackup( - $restoreDbName, - self::fullyQualifiedBackupName(self::$backupId1), - ['encryptionConfig' => $encryptionConfig] - ); - self::$restoreOperationName = $op->name(); + $op = $ops['restore']; $metadata = null; foreach (self::$instance->databaseOperations() as $lro) { - if (basename($lro->info()['metadata']['name']) == $restoreDbName) { + if (basename($lro->info()['metadata']['name']) == self::$restoreDbName) { $metadata = $lro->info()['metadata']; break; } } + $restoredDb = $this::$instance->database(self::$restoreDbName); $this->assertNotNull($metadata); $this->assertArrayHasKey('progress', $metadata); $this->assertArrayHasKey('progressPercent', $metadata['progress']); $this->assertArrayHasKey('startTime', $metadata['progress']); - // Poll for completion with the extended timeout - $op->pollUntilComplete([ - 'timeoutMillis' => self::LONG_TIMEOUT_SECONDS * 1000 // GAX expects milliseconds - ]); - $restoredDb = $this::$instance->database($restoreDbName); - - self::$deletionQueue->add(function () use ($restoredDb) { - $restoredDb->drop(); - }); + $this->assertTrue($restoredDb->exists()); - $backup = $this::$instance->backup(self::$backupId1); + $backup = $this::$instance->backup(self::$backupId); - $this->assertTrue($restoredDb->exists()); $this->assertEquals( $backup->info()['versionTime'], $restoredDb->info()['restoreInfo']['backupInfo']['versionTime'] @@ -592,14 +675,27 @@ public function testRestoreToNewDatabase() Type::GOOGLE_DEFAULT_ENCRYPTION, current($restoredDb->info()['encryptionInfo'])['encryptionType'] ); + + $this->pollWithExtendedTimeout($op, __FUNCTION__); + + $restoredDb->reload(); + $this->assertContains($restoredDb->state(), [ + Database::STATE_READY, + Database::STATE_READY_OPTIMIZING + ]); } /** + * Tests that the database restore operation appears in the list + * of database operations when filtered by its operation name. + * * @depends testRestoreToNewDatabase */ public function testRestoreAppearsInListDatabaseOperations() { - $databaseOps = iterator_to_array($this::$instance->databaseOperations()); + $databaseOps = iterator_to_array($this::$instance->databaseOperations([ + 'filter' => sprintf('name="%s"', self::$restoreOperationName) + ])); $databaseOpsNames = array_map(function ($dOp) { return $dOp->name(); }, $databaseOps); @@ -610,19 +706,85 @@ public function testRestoreAppearsInListDatabaseOperations() } /** - * @depends testCreateBackup + * Tests that attempting to restore a backup over an existing database fails + * with a ConflictException, as restores must target newly created databases. + * + * @depends testRestoreToNewDatabase */ public function testRestoreBackupToAnExistingDatabase() { - $existingDb = self::$instance->database(self::$dbName2); + $existingDb = self::$instance->database(self::$backupDbName); $this->assertTrue($existingDb->exists()); - $this->expectException(ConflictException::class); + $e = null; + $retries = 3; + while ($retries > 0) { + try { + $this::$instance->createDatabaseFromBackup( + self::$backupDbName, + self::fullyQualifiedBackupName(self::$backupId) + ); + break; + } catch (ConflictException $e) { + break; + } catch (ServiceException | ApiException $ex) { + if ($ex->getCode() === Code::UNAVAILABLE && $retries > 0) { + self::debugLog( + __FUNCTION__, + 'Caught ' . \get_class($ex) . ' with Code ' + . Code::name($ex->getCode()) . '. ' . $retries . ' attempts left' + ); + $retries--; + sleep(2); + continue; + } + throw $ex; + } + } - $this::$instance->createDatabaseFromBackup( - self::$dbName2, - self::fullyQualifiedBackupName(self::$backupId1) + $this->assertInstanceOf(ConflictException::class, $e); + } + + + private function createBackupCopy(): LongRunningOperation + { + $backup = self::$instance->backup(self::$backupId); + $newBackup = self::$instance->backup(self::$copyBackupId); + $op = $backup->createCopy($newBackup, self::$expireTime); + + self::$deletionQueue->add(function () use ($newBackup) { + if ($newBackup->exists()) { + $newBackup->delete(); + } + }); + + return $op; + } + + private function restoreToNewDatabase(): LongRunningOperation + { + self::$restoreDbName = uniqid('restored_db_'); + $encryptionConfig = [ + 'encryptionType' => RestoreDatabaseEncryptionConfig\EncryptionType::GOOGLE_DEFAULT_ENCRYPTION + ]; + + $op = $this::$instance->createDatabaseFromBackup( + self::$restoreDbName, + self::fullyQualifiedBackupName(self::$backupId), + ['encryptionConfig' => $encryptionConfig] ); + + $restoredDb = $this::$instance->database(self::$restoreDbName); + + self::$deletionQueue->add(function () use ($restoredDb) { + if ($restoredDb->exists()) { + $restoredDb->drop(); + } + }); + + self::$restoreOperationName = $op->name(); + + return $op; } private static function fullyQualifiedBackupName($backupId) @@ -665,4 +827,62 @@ private static function parseName($name, $id) { return DatabaseAdminClient::parseName($name)[$id]; } + private function pollWithExtendedTimeout($op, $func) + { + $timeout = time() + self::LONG_TIMEOUT_SECONDS; + while (time() < $timeout) { + try { + $op->pollUntilComplete([ + 'maxPollingDurationSeconds' => $timeout - time() + ]); + break; + } catch (ServiceException | ApiException $e) { + if ($e->getCode() !== Code::DEADLINE_EXCEEDED) { + throw $e; + } + self::debugLog( + $func, + 'Caught ' . \get_class($e) . ' with Code ' . Code::name($e->getCode()) + ); + } + } + + return $op; + } + + private static function cancelPendingBackups($dbName) + { + $dbFullName = self::getDatabaseInstance($dbName)->name(); + try { + foreach (self::$instance->backupOperations() as $op) { + if ($op->done()) { + continue; + } + + $metadata = $op->info()['metadata'] ?? []; + if (!isset($metadata['database']) || $metadata['database'] !== $dbFullName) { + continue; + } + + try { + $op->cancel(); + $op->pollUntilComplete(['maxPollingDurationSeconds' => 120]); + } catch (\Exception $e) { + self::debugLog( + __FUNCTION__, + 'Ignored ' . \get_class($e) . ' while cancelling backup operation: ' . $e->getMessage() + ); + } + } + } catch (\Exception $e) { + self::debugLog( + __FUNCTION__, + 'Ignored ' . \get_class($e) . ' during overall backup cancellation cleanup: ' . $e->getMessage() + ); + } + } + private static function debugLog($functionName, $message) + { + error_log('Debug [' . $functionName . ']: ' . $message); + } } diff --git a/Spanner/tests/System/BatchTest.php b/Spanner/tests/System/BatchTest.php index 651e97682e66..9c5f3b5e286a 100644 --- a/Spanner/tests/System/BatchTest.php +++ b/Spanner/tests/System/BatchTest.php @@ -17,13 +17,14 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\ApiCore\ApiException; + use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Core\Testing\System\SystemTestCase; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; -use Google\Cloud\Spanner\KeyRange; use Google\Cloud\Spanner\KeySet; +use Google\Rpc\Code; /** * @group spanner @@ -31,10 +32,11 @@ */ class BatchTest extends SystemTestCase { + const TABLE_NAME = 'BatchTest'; use SystemTestCaseTrait; use DatabaseRoleTrait; - private static $tableName; + private static $isSetup = false; /** @@ -42,44 +44,11 @@ class BatchTest extends SystemTestCase */ public static function setUpTestFixtures(): void { + self::setUpTestDatabase(); if (self::$isSetup) { return; } - self::setUpTestDatabase(); - - self::$tableName = uniqid(self::TESTING_PREFIX); - - self::$database->updateDdl(sprintf( - 'CREATE TABLE %s ( - id INT64 NOT NULL, - decade INT64 NOT NULL - ) PRIMARY KEY (id)', - self::$tableName - ))->pollUntilComplete(); - - if (self::$database->info()['databaseDialect'] == DatabaseDialect::GOOGLE_STANDARD_SQL) { - $statements = [ - sprintf('CREATE ROLE %s', self::$dbRole), - sprintf('CREATE ROLE %s', self::$restrictiveDbRole), - ]; - - if (!self::isEmulatorUsed()) { - $statements[] = sprintf( - 'GRANT SELECT(id) ON TABLE %s TO ROLE %s', - self::$tableName, - self::$restrictiveDbRole - ); - } - - $statements[] = sprintf( - 'GRANT SELECT ON TABLE %s TO ROLE %s', - self::$tableName, - self::$dbRole - ); - - self::$database->updateDdlBatch($statements)->pollUntilComplete(); - } - + self::$database->delete(self::TABLE_NAME, new KeySet(['all' => true])); self::seedTable(); self::$isSetup = true; } @@ -87,14 +56,20 @@ public static function setUpTestFixtures(): void private static function seedTable() { $decades = [1950, 1960, 1970, 1980, 1990, 2000]; + $mutations = []; + for ($i = 0; $i < 250; $i++) { - self::$database->insert(self::$tableName, [ + $mutations[] = [ 'id' => self::randId(), - 'decade' => array_rand($decades) - ], [ - 'timeoutMillis' => 50000 - ]); + 'decade' => $decades[array_rand($decades)] + ]; } + + self::$database->insertOrUpdateBatch( + self::TABLE_NAME, + $mutations, + ['timeoutMillis' => 50000] + ); } public function testBatch() @@ -102,7 +77,7 @@ public function testBatch() $query = 'SELECT id, decade - FROM ' . self::$tableName . ' + FROM ' . self::TABLE_NAME . ' WHERE decade > @earlyBound AND @@ -120,22 +95,25 @@ public function testBatch() $snapshot = $batch->snapshotFromString($string); - $partitions = $snapshot->partitionQuery($query, ['parameters' => $parameters]); + $partitions = null; + for ($i = 0; $i < 3; $i++) { + try { + $partitions = $snapshot->partitionQuery($query, ['parameters' => $parameters]); + break; + } catch (ServiceException | ApiException $ex) { + $allowed = [Code::UNAVAILABLE, Code::DEADLINE_EXCEEDED]; + if ($i === 2 || !in_array($ex->getCode(), $allowed)) { + throw $ex; + } + sleep(2); + } + } $this->assertEquals(count($resultSet), $this->executePartitions($batch, $snapshot, $partitions)); - $keySet = new KeySet([ - 'ranges' => [ - new KeyRange([ - 'start' => $parameters['earlyBound'], - 'startType' => KeyRange::TYPE_OPEN, - 'end' => $parameters['lateBound'], - 'endType' => KeyRange::TYPE_OPEN - ]) - ] - ]); - - $partitions = $snapshot->partitionRead(self::$tableName, $keySet, ['id', 'decade']); - $this->assertEquals(count($resultSet), $this->executePartitions($batch, $snapshot, $partitions)); + $keySet = new KeySet(['all' => true]); + + $partitions = $snapshot->partitionRead(self::TABLE_NAME, $keySet, ['id', 'decade']); + $this->assertEquals(250, $this->executePartitions($batch, $snapshot, $partitions)); } /** @@ -149,7 +127,7 @@ public function testBatchWithDbRole($dbRole, $expected) $query = 'SELECT id, decade - FROM ' . self::$tableName . ' + FROM ' . self::TABLE_NAME . ' WHERE decade > @earlyBound AND diff --git a/Spanner/tests/System/BatchWriteTest.php b/Spanner/tests/System/BatchWriteTest.php index a6e93f74f99f..d6ad312bfcb2 100644 --- a/Spanner/tests/System/BatchWriteTest.php +++ b/Spanner/tests/System/BatchWriteTest.php @@ -35,20 +35,6 @@ public static function setUpTestFixtures(): void { self::skipEmulatorTests(); self::setUpTestDatabase(); - - self::$database->updateDdlBatch([ - 'CREATE TABLE Singers ( - SingerId INT64 NOT NULL, - FirstName STRING(1024), - LastName STRING(1024), - ) PRIMARY KEY (SingerId)', - 'CREATE TABLE Albums ( - SingerId INT64 NOT NULL, - AlbumId INT64 NOT NULL, - AlbumTitle STRING(1024), - ) PRIMARY KEY (SingerId, AlbumId), - INTERLEAVE IN PARENT Singers ON DELETE CASCADE' - ])->pollUntilComplete(); } public function testBatchWrite() diff --git a/Spanner/tests/System/DatabaseRoleTrait.php b/Spanner/tests/System/DatabaseRoleTrait.php index 123749a87056..9e3e4905cd57 100644 --- a/Spanner/tests/System/DatabaseRoleTrait.php +++ b/Spanner/tests/System/DatabaseRoleTrait.php @@ -24,8 +24,8 @@ */ trait DatabaseRoleTrait { - private static $restrictiveDbRole = 'restrictiveReaderRole'; - private static $dbRole = 'readerRole'; + private static $restrictiveDbRole = 'RestrictiveReader'; + private static $dbRole = 'Reader'; abstract public static function setUpBeforeClass(); diff --git a/Spanner/tests/System/LargeReadTest.php b/Spanner/tests/System/LargeReadTest.php index cf9530f7280c..d5f5926647ba 100644 --- a/Spanner/tests/System/LargeReadTest.php +++ b/Spanner/tests/System/LargeReadTest.php @@ -27,9 +27,10 @@ */ class LargeReadTest extends SystemTestCase { + const TABLE_NAME = 'LargeReadTable'; use SystemTestCaseTrait; - private static $tableName; + private static $row = []; //@codingStandardsIgnoreStart @@ -47,8 +48,7 @@ class LargeReadTest extends SystemTestCase public static function setUpTestFixtures(): void { self::setUpTestDatabase(); - - self::$tableName = uniqid(self::TESTING_PREFIX); + self::$database->delete(self::TABLE_NAME, new KeySet(['all' => true])); $str = ''; foreach (self::$data as $letter) { @@ -57,19 +57,6 @@ public static function setUpTestFixtures(): void self::$str = $str; - $db = self::$database; - - $db->updateDdl(sprintf( - 'CREATE TABLE %s ( - id INT64 NOT NULL, - stringColumn STRING(MAX) NOT NULL, - bytesColumn BYTES(MAX) NOT NULL, - stringArrayColumn ARRAY NOT NULL, - bytesArrayColumn ARRAY NOT NULL - ) PRIMARY KEY (id)', - self::$tableName - ))->pollUntilComplete(); - self::seedTable(); } @@ -84,7 +71,7 @@ private static function seedTable() ]; for ($i = 0; $i < 10; $i++) { - self::$database->insert(self::$tableName, self::$row + ['id' => self::randId()], [ + self::$database->insert(self::TABLE_NAME, self::$row + ['id' => self::randId()], [ 'timeoutMillis' => 50000 ]); } @@ -98,7 +85,7 @@ public function testLargeRead() $db = self::$database; $keyset = new KeySet(['all' => true]); - $read = $db->read(self::$tableName, $keyset, array_keys(self::$row)); + $read = $db->read(self::TABLE_NAME, $keyset, array_keys(self::$row)); foreach ($read->rows() as $row) { $this->runAssertionsOnRow($row); @@ -112,7 +99,7 @@ public function testLargeExecute() { $db = self::$database; - $execute = $db->execute('SELECT * FROM ' . self::$tableName); + $execute = $db->execute('SELECT * FROM ' . self::TABLE_NAME); foreach ($execute->rows() as $row) { $this->runAssertionsOnRow($row); diff --git a/Spanner/tests/System/OperationsTest.php b/Spanner/tests/System/OperationsTest.php index ca21dc7e375c..2377b0bdb902 100644 --- a/Spanner/tests/System/OperationsTest.php +++ b/Spanner/tests/System/OperationsTest.php @@ -97,13 +97,14 @@ public function testRead() public function testUpdate() { $db = self::$database; + $newName = uniqid('Doug'); $row = $this->getRow(); - $row['name'] = 'Doug'; + $row['name'] = $newName; $db->update('Users', $row); $row = $this->getRow(); - $this->assertEquals('Doug', $row['name']); + $this->assertEquals($newName, $row['name']); } public function testInsertOrUpdate() diff --git a/Spanner/tests/System/PartitionedDmlTest.php b/Spanner/tests/System/PartitionedDmlTest.php index a12b2d6a3f53..1924e5099c86 100644 --- a/Spanner/tests/System/PartitionedDmlTest.php +++ b/Spanner/tests/System/PartitionedDmlTest.php @@ -41,12 +41,6 @@ public function testPdml() { $db = self::$database; - $db->updateDdl('CREATE TABLE ' . self::PDML_TABLE . '( - id INT64 NOT NULL, - stringField STRING(MAX), - boolField BOOL - ) PRIMARY KEY(id)')->pollUntilComplete(); - $this->seedTable(); $opts = [ diff --git a/Spanner/tests/System/PgBatchTest.php b/Spanner/tests/System/PgBatchTest.php index 7849bc2fa334..632d8e46bf09 100644 --- a/Spanner/tests/System/PgBatchTest.php +++ b/Spanner/tests/System/PgBatchTest.php @@ -22,6 +22,7 @@ use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; +use Google\Cloud\Spanner\KeySet; /** * @group spanner @@ -30,10 +31,11 @@ */ class PgBatchTest extends SystemTestCase { + const TABLE_NAME = 'PgBatchTest'; use PgSystemTestCaseTrait; use DatabaseRoleTrait; - private static $tableName; + private static $hasSetupBatch = false; /** @@ -50,37 +52,7 @@ public static function setUpTestFixtures(): void } self::setUpTestDatabase(); - self::$tableName = uniqid(self::TESTING_PREFIX); - - self::$database->updateDdl(sprintf( - 'CREATE TABLE %s ( - id INTEGER PRIMARY KEY, - decade INTEGER NOT NULL - )', - self::$tableName - ))->pollUntilComplete(); - - if (self::$database->info()['databaseDialect'] == DatabaseDialect::POSTGRESQL) { - $statements = [ - sprintf('CREATE ROLE %s', self::$dbRole), - sprintf('CREATE ROLE %s', self::$restrictiveDbRole), - ]; - - if (!self::isEmulatorUsed()) { - $statements[] = sprintf( - 'GRANT SELECT(id) ON TABLE %s TO %s', - self::$tableName, - self::$restrictiveDbRole - ); - $statements[] = sprintf( - 'GRANT SELECT ON TABLE %s TO %s', - self::$tableName, - self::$dbRole - ); - } - - self::$database->updateDdlBatch($statements)->pollUntilComplete(); - } + self::$database->delete(self::TABLE_NAME, new KeySet(['all' => true])); self::seedTable(); self::$hasSetupBatch = true; @@ -98,7 +70,7 @@ public function testBatchWithDbRole($dbRole, $expected) $query = 'SELECT id, decade - FROM ' . self::$tableName . ' + FROM ' . self::TABLE_NAME . ' WHERE decade > $1 AND @@ -119,6 +91,9 @@ public function testBatchWithDbRole($dbRole, $expected) try { $partitions = $snapshot->partitionQuery($query, ['parameters' => $parameters]); } catch (ServiceException $e) { + if (is_null($expected)) { + throw $e; + } $error = $e; } @@ -145,13 +120,17 @@ private function executePartitions(BatchClient $client, BatchSnapshot $snapshot, private static function seedTable() { $decades = [1950, 1960, 1970, 1980, 1990, 2000]; + $mutations = []; + for ($i = 0; $i < 250; $i++) { - self::$database->insert(self::$tableName, [ + $mutations[] = [ 'id' => self::randId(), - 'decade' => array_rand($decades) - ], [ - 'timeoutMillis' => 50000 - ]); + 'decade' => $decades[array_rand($decades)] + ]; } + + self::$database->insertOrUpdateBatch(self::TABLE_NAME, $mutations, [ + 'timeoutMillis' => 50000 + ]); } } diff --git a/Spanner/tests/System/PgBatchWriteTest.php b/Spanner/tests/System/PgBatchWriteTest.php index ffd775daaeeb..ef523ce408ac 100644 --- a/Spanner/tests/System/PgBatchWriteTest.php +++ b/Spanner/tests/System/PgBatchWriteTest.php @@ -38,21 +38,6 @@ public static function setUpTestFixtures(): void // against the emulator. self::skipEmulatorTests(); self::setUpTestDatabase(); - - self::$database->updateDdlBatch([ - 'CREATE TABLE Singers ( - singerid bigint NOT NULL, - firstname varchar(1024), - lastname varchar(1024), - PRIMARY KEY (singerid) - )', - 'CREATE TABLE Albums ( - singerid bigint NOT NULL, - albumid bigint NOT NULL, - albumtitle varchar(1024), - PRIMARY KEY (singerid, albumid) - ) INTERLEAVE IN PARENT singers ON DELETE CASCADE' - ])->pollUntilComplete(); } public function testBatchWrite() diff --git a/Spanner/tests/System/PgPartitionedDmlTest.php b/Spanner/tests/System/PgPartitionedDmlTest.php index b72c3d0d537a..cfd90d0ac147 100644 --- a/Spanner/tests/System/PgPartitionedDmlTest.php +++ b/Spanner/tests/System/PgPartitionedDmlTest.php @@ -47,12 +47,6 @@ public function testPdml() $db = self::$database; - $db->updateDdl('CREATE TABLE IF NOT EXISTS ' . self::PDML_TABLE . '( - id bigint NOT NULL, - stringField varchar(1024), - boolField BOOL, - PRIMARY KEY(id) - )')->pollUntilComplete(); $this->seedTable(); diff --git a/Spanner/tests/System/PgQueryTest.php b/Spanner/tests/System/PgQueryTest.php index 9129fec7fb51..9a9b1aca839e 100644 --- a/Spanner/tests/System/PgQueryTest.php +++ b/Spanner/tests/System/PgQueryTest.php @@ -25,6 +25,7 @@ use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; use Google\Cloud\Spanner\Interval; +use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\PgJsonb; use Google\Cloud\Spanner\PgNumeric; use Google\Cloud\Spanner\Timestamp; @@ -40,7 +41,7 @@ class PgQueryTest extends SystemTestCase { use PgSystemTestCaseTrait; - const TABLE_NAME = 'test'; + const TABLE_NAME = 'PgQueryTest_2'; public static $timestampVal; @@ -51,24 +52,10 @@ public static function setUpTestFixtures(): void { self::setUpTestDatabase(); - self::$database->updateDdl( - 'CREATE TABLE ' . self::TABLE_NAME . ' ( - id bigint NOT NULL, - name varchar(1024), - registered bool, - age numeric, - rating float, - bytes_col bytea, - created_at timestamptz, - dt date, - data jsonb, - weight float4, - PRIMARY KEY (id) - )' - )->pollUntilComplete(); - self::$timestampVal = new Timestamp(new \DateTime()); + self::$database->delete(self::TABLE_NAME, new KeySet(['all' => true])); + self::$database->insertOrUpdateBatch(self::TABLE_NAME, [ [ 'id' => 1, diff --git a/Spanner/tests/System/PgReadTest.php b/Spanner/tests/System/PgReadTest.php index 317c9b36895f..adca793d51bd 100644 --- a/Spanner/tests/System/PgReadTest.php +++ b/Spanner/tests/System/PgReadTest.php @@ -30,10 +30,12 @@ */ class PgReadTest extends SystemTestCase { + const READ_TABLE_NAME = 'PgReadTable'; + const RANGE_TABLE_NAME = 'PgRangeTable'; use PgSystemTestCaseTrait; - private static $readTableName; - private static $rangeTableName; + + private static $indexes = []; private static $dataset; @@ -44,35 +46,12 @@ public static function setUpTestFixtures(): void { self::setUpTestDatabase(); - self::$readTableName = 'read_table'; - self::$rangeTableName = 'range_table'; - - $create = 'CREATE TABLE %s ( - id bigint NOT NULL, - val varchar(1024) NOT NULL, - PRIMARY KEY (id) - )'; - - $idx = 'CREATE UNIQUE INDEX %s ON %s (%s)'; - - $stmts = []; - foreach ([self::$readTableName, self::$rangeTableName] as $table) { - $index1 = ['table' => $table, 'name' => uniqid(self::TESTING_PREFIX), 'type' => 'simple']; - $index2 = ['table' => $table, 'name' => uniqid(self::TESTING_PREFIX), 'type' => 'complex']; - - $stmts[] = sprintf($create, $table); - $stmts[] = sprintf($idx, $index1['name'], $table, 'id'); - $stmts[] = sprintf($idx, $index2['name'], $table, 'id, val'); - - self::$indexes[] = $index1; - self::$indexes[] = $index2; - } - $db = self::$database; - $db->updateDdlBatch($stmts)->pollUntilComplete(); + $db->delete(self::READ_TABLE_NAME, new KeySet(['all' => true])); + $db->delete(self::RANGE_TABLE_NAME, new KeySet(['all' => true])); self::$dataset = self::generateDataset(20, true); - $db->insertBatch(self::$rangeTableName, self::$dataset); + $db->insertOrUpdateBatch(self::RANGE_TABLE_NAME, self::$dataset); } public function testRangeReadSingleKeyOpen() @@ -86,7 +65,7 @@ public function testRangeReadSingleKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); $this->assertNotContains(self::$dataset[10], $rows); @@ -105,7 +84,7 @@ public function testRangeReadSingleKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); $this->assertContains(self::$dataset[10], $rows); @@ -123,7 +102,7 @@ public function testRangeReadSingleKeyOpenClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); $this->assertContains(self::$dataset[10], $rows); @@ -141,7 +120,7 @@ public function testRangeReadSingleKeyClosedOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); $this->assertNotContains(self::$dataset[10], $rows); @@ -158,7 +137,7 @@ public function testRangeReadPartialKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); $this->assertNotContains(self::$dataset[10], $rows); @@ -177,7 +156,7 @@ public function testRangeReadPartialKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); $this->assertContains(self::$dataset[10], $rows); @@ -194,8 +173,8 @@ public function testRangeReadIndexSingleKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); @@ -215,8 +194,8 @@ public function testRangeReadIndexSingleKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); @@ -235,8 +214,8 @@ public function testRangeReadIndexSingleKeyOpenClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); @@ -255,8 +234,8 @@ public function testRangeReadIndexSingleKeyClosedOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); @@ -274,8 +253,8 @@ public function testRangeReadIndexPartialKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); @@ -295,8 +274,8 @@ public function testRangeReadIndexPartialKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); @@ -309,7 +288,7 @@ public function testReadWithLimit() $res = function ($limit) use ($db) { $keyset = new KeySet(['all' => true]); - return $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ + return $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ 'limit' => $limit ])->rows(); }; @@ -327,9 +306,9 @@ public function testReadOverIndexWithLimit() $res = function ($limit) use ($db) { $keyset = new KeySet(['all' => true]); - return $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ + return $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ 'limit' => $limit, - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ])->rows(); }; @@ -345,7 +324,7 @@ public function testReadPoint() $dataset = $this->generateDataset(); $db = self::$database; - $db->insertBatch(self::$readTableName, $dataset); + $db->insertOrUpdateBatch(self::READ_TABLE_NAME, $dataset); $indexes = array_rand($dataset, 4); $points = []; @@ -357,7 +336,7 @@ public function testReadPoint() $keyset = new KeySet(['keys' => $keys]); - $res = $db->read(self::$readTableName, $keyset, array_keys($dataset[0])); + $res = $db->read(self::READ_TABLE_NAME, $keyset, array_keys($dataset[0])); $rows = $res->rows(); foreach ($rows as $index => $row) { $this->assertContains($row, $dataset); @@ -370,7 +349,7 @@ public function testReadPointOverIndex() $dataset = $this->generateDataset(); $db = self::$database; - $db->insertBatch(self::$readTableName, $dataset); + $db->insertOrUpdateBatch(self::READ_TABLE_NAME, $dataset); $indexes = array_rand($dataset, 4); $points = []; @@ -382,8 +361,8 @@ public function testReadPointOverIndex() $keyset = new KeySet(['keys' => $keys]); - $res = $db->read(self::$readTableName, $keyset, array_keys($dataset[0]), [ - 'index' => $this->getIndexName(self::$readTableName, 'complex') + $res = $db->read(self::READ_TABLE_NAME, $keyset, array_keys($dataset[0]), [ + 'index' => $this->getIndexName(self::READ_TABLE_NAME, 'complex') ]); $rows = $res->rows(); foreach ($rows as $index => $row) { @@ -452,14 +431,6 @@ private static function generateDataset($count = 20, $ordered = false) private function getIndexName($table, $type) { - $res = array_filter(self::$indexes, function ($index) use ($table, $type) { - return $index['table'] === $table && $index['type'] === $type; - }); - - if (!$res) { - throw new \RuntimeException('index not found'); - } - - return current($res)['name']; + return $type === 'simple' ? $table . '_Idx1' : $table . '_Idx2'; } } diff --git a/Spanner/tests/System/PgSystemTestCaseTrait.php b/Spanner/tests/System/PgSystemTestCaseTrait.php index 4acf8ec3d0c9..e227b5c0ff82 100644 --- a/Spanner/tests/System/PgSystemTestCaseTrait.php +++ b/Spanner/tests/System/PgSystemTestCaseTrait.php @@ -25,32 +25,127 @@ trait PgSystemTestCaseTrait protected static function setUpTestDatabase(): void { - if (self::$hasSetUp) { + if (TestDatabaseManager::$pgHasSetUp) { + self::$client = TestDatabaseManager::$client; + self::$instance = TestDatabaseManager::$instance; + self::$database = TestDatabaseManager::$pgDatabase; + self::$dbName = TestDatabaseManager::$pgDbName; + self::$hasSetUp = true; return; } self::$instance = self::getClient()->instance(self::INSTANCE_NAME); - self::$dbName = uniqid(self::TESTING_PREFIX); + if (!self::$dbName = getenv('GOOGLE_CLOUD_SPANNER_TEST_PG_DATABASE')) { + self::$dbName = uniqid(self::TESTING_PREFIX); - // create a PG DB first - $op = self::$instance->createDatabase(self::$dbName, [ - 'databaseDialect' => DatabaseDialect::POSTGRESQL - ]); - // wait for the DB to be ready - $op->pollUntilComplete(); - - $db = self::getDatabaseInstance(self::$dbName); - - self::$deletionQueue->add(function () use ($db) { - $db->drop(); - }); + register_shutdown_function(function () { + self::getDatabaseInstance(self::$dbName)->drop(); + }); + } + + if ($token = getenv('TEST_TOKEN')) { + self::$dbName .= '-' . $token; + } + + self::$database = self::getDatabaseInstance(self::$dbName); - self::$database = $db; + if (!self::$database->exists()) { + $op = self::$instance->createDatabase(self::$dbName, [ + 'databaseDialect' => DatabaseDialect::POSTGRESQL + ]); + $op->pollUntilComplete(); + } - $db->updateDdlBatch( + self::$database->updateDdlBatch( [ - 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' ( + 'CREATE TABLE IF NOT EXISTS PgBatchTest ( + id INTEGER PRIMARY KEY, + decade INTEGER NOT NULL + )', + 'CREATE TABLE IF NOT EXISTS Singers ( + SingerId BIGINT NOT NULL, + FirstName CHARACTER VARYING(1024), + LastName CHARACTER VARYING(1024), + PRIMARY KEY(SingerId) + )', + 'CREATE TABLE IF NOT EXISTS Albums ( + SingerId BIGINT NOT NULL, + AlbumId BIGINT NOT NULL, + AlbumTitle CHARACTER VARYING(1024), + PRIMARY KEY(SingerId, AlbumId) + ) INTERLEAVE IN PARENT Singers ON DELETE CASCADE', + 'CREATE TABLE IF NOT EXISTS PgReadTable ( + id bigint NOT NULL, + val character varying NOT NULL, + PRIMARY KEY (id) + )', + 'CREATE UNIQUE INDEX IF NOT EXISTS PgReadTable_Idx1 ON PgReadTable (id)', + 'CREATE UNIQUE INDEX IF NOT EXISTS PgReadTable_Idx2 ON PgReadTable (id, val)', + 'CREATE TABLE IF NOT EXISTS PgRangeTable ( + id bigint NOT NULL, + val character varying NOT NULL, + PRIMARY KEY (id) + )', + 'CREATE UNIQUE INDEX IF NOT EXISTS PgRangeTable_Idx1 ON PgRangeTable (id)', + 'CREATE UNIQUE INDEX IF NOT EXISTS PgRangeTable_Idx2 ON PgRangeTable (id, val)', + 'CREATE TABLE IF NOT EXISTS PgTransactionTest ( + id bigint NOT NULL, + name character varying NOT NULL, + birthday date, + PRIMARY KEY (id) + )', + 'CREATE TABLE IF NOT EXISTS Writes ( + id bigint NOT NULL, + arrayField bigint[], + arrayBoolField boolean[], + arrayFloatField double precision[], + arrayfloat4field real[], + arrayStringField character varying[], + arrayBytesField bytea[], + arrayTimestampField timestamp with time zone[], + arrayDateField date[], + arraypgnumericfield numeric[], + arraypgjsonbfield jsonb[], + boolField boolean, + bytesField bytea, + dateField date, + floatField double precision, + float4field real, + intField bigint, + stringField character varying, + timestampField timestamp with time zone, + pgnumericfield numeric, + pgjsonbfield jsonb, + uuidField character varying(36), + arrayUuidField character varying(36)[], + PRIMARY KEY (id) + )', + 'CREATE TABLE IF NOT EXISTS CommitTimestamps ( + id bigint NOT NULL, + commitTimestamp spanner.commit_timestamp NOT NULL, + PRIMARY KEY(id) + )', + 'CREATE TABLE IF NOT EXISTS partitionedDml ( + id bigint NOT NULL, + stringField varchar(1024), + boolField BOOL, + PRIMARY KEY (id) + )', + 'CREATE TABLE IF NOT EXISTS PgQueryTest_2 ( + id bigint NOT NULL, + name varchar(1024), + registered bool, + age numeric, + rating float, + bytes_col bytea, + created_at timestamptz, + dt date, + data jsonb, + weight float4, + PRIMARY KEY (id) + )', + 'CREATE TABLE IF NOT EXISTS ' . self::TEST_TABLE_NAME . ' ( id bigint PRIMARY KEY, name varchar(1024) NOT NULL, birthday date @@ -61,18 +156,25 @@ protected static function setUpTestDatabase(): void // Currently, the emulator doesn't support setting roles for the PG // dialect. if (!self::isEmulatorUsed()) { - $db->updateDdlBatch( - [ - 'CREATE ROLE ' . self::DATABASE_ROLE, - 'CREATE ROLE ' . self::RESTRICTIVE_DATABASE_ROLE, - 'GRANT SELECT ON TABLE ' . self::TEST_TABLE_NAME . - ' TO ' . self::DATABASE_ROLE, - 'GRANT SELECT(id, name), INSERT(id, name), UPDATE(id, name) ON TABLE ' - . self::TEST_TABLE_NAME . ' TO ' . self::RESTRICTIVE_DATABASE_ROLE, - ] - )->pollUntilComplete(); + self::$database->updateDdlBatch([ + 'CREATE ROLE ' . self::DATABASE_ROLE, + 'CREATE ROLE ' . self::RESTRICTIVE_DATABASE_ROLE + ])->pollUntilComplete(); + self::$database->updateDdlBatch([ + 'GRANT SELECT ON TABLE ' . self::TEST_TABLE_NAME . + ' TO ' . self::DATABASE_ROLE, + 'GRANT SELECT(id, name), INSERT(id, name), UPDATE(id, name) ON TABLE ' + . self::TEST_TABLE_NAME . ' TO ' . self::RESTRICTIVE_DATABASE_ROLE, + 'GRANT SELECT(id) ON TABLE PgBatchTest TO ' . self::RESTRICTIVE_DATABASE_ROLE, + 'GRANT SELECT ON TABLE PgBatchTest TO ' . self::DATABASE_ROLE, + ])->pollUntilComplete(); } + TestDatabaseManager::$pgHasSetUp = true; + TestDatabaseManager::$client = self::$client; + TestDatabaseManager::$instance = self::$instance; + TestDatabaseManager::$pgDatabase = self::$database; + TestDatabaseManager::$pgDbName = self::$dbName; self::$hasSetUp = true; } } diff --git a/Spanner/tests/System/PgTransactionTest.php b/Spanner/tests/System/PgTransactionTest.php index a76b716505cf..8cb8d273ac50 100644 --- a/Spanner/tests/System/PgTransactionTest.php +++ b/Spanner/tests/System/PgTransactionTest.php @@ -31,12 +31,13 @@ */ class PgTransactionTest extends SystemTestCase { + const TABLE_NAME = 'PgTransactionTest'; use DatabaseRoleTrait; use PgSystemTestCaseTrait; private static $row = []; - private static $tableName; + private static $id1; private static $isSetup = false; @@ -50,17 +51,7 @@ public static function setUpTestFixtures(): void } self::setUpTestDatabase(); - self::$tableName = 'transactions_test'; - - self::$database->updateDdlBatch([ - 'CREATE TABLE IF NOT EXISTS ' . self::$tableName . ' ( - id bigint NOT NULL, - number bigint NOT NULL, - PRIMARY KEY (id) - )' - ])->pollUntilComplete(); - - self::$id1 = rand(1000, 9999); + self::$id1 = self::randId(); self::$row = [ 'id' => self::$id1, 'name' => uniqid(self::TESTING_PREFIX), @@ -76,7 +67,7 @@ public function testRunTransaction() $db = self::$database; $db->runTransaction(function ($t) { - $id = rand(1, 346464); + $id = self::randId(); $t->insertOrUpdate(self::TEST_TABLE_NAME, [ 'id' => $id, 'name' => uniqid(self::TESTING_PREFIX), @@ -105,7 +96,7 @@ public function testTransactionNoCommit() $ex = false; try { $db->runTransaction(function ($t) { - $t->execute('SELECT * FROM ' . self::$tableName); + $t->execute('SELECT * FROM ' . self::TABLE_NAME); }); } catch (\RuntimeException $e) { $this->assertEquals('Transactions must be rolled back or committed.', $e->getMessage()); @@ -155,7 +146,7 @@ public function testRunTransactionWithDbRole($db, $values, $expected) try { $db->runTransaction(function ($t) use ($values) { - $id = rand(1, 346464); + $id = self::randId(); $t->insert(self::TEST_TABLE_NAME, $values); $t->commit(); diff --git a/Spanner/tests/System/PgWriteTest.php b/Spanner/tests/System/PgWriteTest.php index 5cb8be03f341..1b29f507cfb8 100644 --- a/Spanner/tests/System/PgWriteTest.php +++ b/Spanner/tests/System/PgWriteTest.php @@ -49,41 +49,8 @@ class PgWriteTest extends SystemTestCase */ public static function setUpTestFixtures(): void { - // The equiavalent tests for the GSQL dialect are also skipped. self::skipEmulatorTests(); self::setUpTestDatabase(); - - self::$database->updateDdlBatch([ - 'CREATE TABLE ' . self::TABLE_NAME . ' ( - id bigint NOT NULL, - boolfield boolean, - bytesfield bytea, - datefield date, - floatfield float, - float4field float4, - intfield bigint, - stringfield varchar(1024), - timestampfield timestamptz, - pgnumericfield numeric, - pgjsonbfield jsonb, - arrayfield bigint[], - arrayboolfield boolean[], - arrayfloatfield float[], - arrayfloat4field float4[], - arraystringfield varchar(1024)[], - arraybytesfield bytea[], - arraytimestampfield timestamptz[], - arraydatefield date[], - arraypgnumericfield numeric[], - arraypgjsonbfield jsonb[], - PRIMARY KEY (id) - )', - 'CREATE TABLE ' . self::COMMIT_TIMESTAMP_TABLE_NAME . ' ( - id bigint NOT NULL, - commitTimestamp SPANNER.COMMIT_TIMESTAMP NOT NULL, - PRIMARY KEY (id, commitTimestamp) - )' - ])->pollUntilComplete(); } public function fieldValueProvider() @@ -114,7 +81,7 @@ public function testWriteAndReadBackValue($id, $field, $value) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -153,7 +120,7 @@ public function testWriteAndReadBackBytes() $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -182,7 +149,7 @@ public function testWriteAndReadBackNaN() $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -218,7 +185,7 @@ public function testWriteAndReadBackNullValue($id, $field) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => null ]); @@ -282,7 +249,7 @@ public function testWriteAndReadBackArrayValue($id, $field, $value) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -330,7 +297,7 @@ public function testWriteAndReadBackArrayComplexValue($id, $field, $value) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -364,7 +331,7 @@ public function testWriteToNonExistentTableFails() $db = self::$database; - $db->insert(uniqid(self::TESTING_PREFIX), ['foo' => 'bar']); + $db->insertOrUpdate(uniqid(self::TESTING_PREFIX), ['foo' => 'bar']); } public function testWriteToNonExistentColumnFails() @@ -373,7 +340,7 @@ public function testWriteToNonExistentColumnFails() $db = self::$database; - $db->insert(self::TABLE_NAME, [uniqid(self::TESTING_PREFIX) => 'bar']); + $db->insertOrUpdate(self::TABLE_NAME, [uniqid(self::TESTING_PREFIX) => 'bar']); } public function testWriteIncorrectTypeToColumn() @@ -382,7 +349,7 @@ public function testWriteIncorrectTypeToColumn() $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $this->randId(), 'boolfield' => 'bar' ]); @@ -396,7 +363,7 @@ public function testWriteAndReadBackRandomBytes($id, $bytes) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'bytesfield' => $bytes ]); @@ -428,7 +395,7 @@ public function testWriteAndReadBackRandomNumeric($id, $numeric) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'pgnumericfield' => $numeric ]); @@ -459,7 +426,7 @@ public function randomNumericProvider() public function testCommitTimestamp() { $id = $this->randId(); - $ts = self::$database->insert(self::COMMIT_TIMESTAMP_TABLE_NAME, [ + $ts = self::$database->insertOrUpdate(self::COMMIT_TIMESTAMP_TABLE_NAME, [ 'id' => $id, 'committimestamp' => new CommitTimestamp() ]); @@ -477,7 +444,7 @@ public function testSetFieldToNull() { $id = $this->randId(); $str = base64_encode(random_bytes(rand(1, 100))); - $row = self::$database->insert(self::TABLE_NAME, [ + $row = self::$database->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'stringfield' => $str ]); @@ -507,7 +474,7 @@ public function testTimestampPrecision($timestamp) { $id = $this->randId(); - $row = self::$database->insert(self::TABLE_NAME, [ + $row = self::$database->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'timestampfield' => $timestamp ]); @@ -549,7 +516,7 @@ public function testTimestampPrecisionLocale($timestamp) try { $id = $this->randId(); - $row = self::$database->insert(self::TABLE_NAME, [ + $row = self::$database->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'timestampfield' => $timestamp ]); diff --git a/Spanner/tests/System/README.md b/Spanner/tests/System/README.md index 2574d1df4147..094bfe6faa25 100644 --- a/Spanner/tests/System/README.md +++ b/Spanner/tests/System/README.md @@ -10,18 +10,27 @@ GOOGLE_CLOUD_PHP_TESTS_KEY_PATH="/path/to/service-account.json" GOOGLE_CLOUD_PHP_WHITELIST_TESTS_KEY_PATH="" GOOGLE_CLOUD_PROJECT="" +# This environment variable is required for UniverseDomainTest. If absent, UniverseDomainTest will be skipped. +GOOGLE_CLOUD_PHP_TESTS_UNIVERSE_DOMAIN_KEY_PATH="" + # These environment variables are optional, and will speed up running the tests locally GOOGLE_CLOUD_SPANNER_TEST_DATABASE=test-database -GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE_1=test-backup-database1 -GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE_2=test-backup-database2 +GOOGLE_CLOUD_SPANNER_TEST_PG_DATABASE=test-pg-database +GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE=test-backup-database +``` + +### For sequential execution: run PHPUnit + +``` +vendor/bin/phpunit -c phpunit-system.xml.dist ``` -### Run PHPUnit +### For parallel execution: run paratest ``` -vendor/bin/phpunit -c phpunit-system.xml.dist --stop-on-failure tests/System/BatchTest.php +vendor/bin/paratest -p -c phpunit-system.xml.dist ``` ## Run the emulator -Some tests ONLY run against the emulator. To run those, you'll need to run the emulator locally. \ No newline at end of file +Emulator can only run some tests and skip about 1/3 of the tests. You'll need to run the emulator locally. \ No newline at end of file diff --git a/Spanner/tests/System/ReadTest.php b/Spanner/tests/System/ReadTest.php index 7c8c314e40e5..55832cbd1ed2 100644 --- a/Spanner/tests/System/ReadTest.php +++ b/Spanner/tests/System/ReadTest.php @@ -34,10 +34,12 @@ */ class ReadTest extends SystemTestCase { + const READ_TABLE_NAME = 'ReadTable'; + const RANGE_TABLE_NAME = 'RangeTable'; use SystemTestCaseTrait; - private static $readTableName; - private static $rangeTableName; + + private static $indexes = []; private static $dataset; @@ -48,34 +50,12 @@ public static function setUpTestFixtures(): void { self::setUpTestDatabase(); - self::$readTableName = uniqid(self::TESTING_PREFIX); - self::$rangeTableName = uniqid(self::TESTING_PREFIX); - - $create = 'CREATE TABLE %s ( - id INT64 NOT NULL, - val STRING(MAX) NOT NULL, - ) PRIMARY KEY (id)'; - - $idx = 'CREATE UNIQUE INDEX %s ON %s (%s)'; - - $stmts = []; - foreach ([self::$readTableName, self::$rangeTableName] as $table) { - $index1 = ['table' => $table, 'name' => uniqid(self::TESTING_PREFIX), 'type' => 'simple']; - $index2 = ['table' => $table, 'name' => uniqid(self::TESTING_PREFIX), 'type' => 'complex']; - - $stmts[] = sprintf($create, $table); - $stmts[] = sprintf($idx, $index1['name'], $table, 'id'); - $stmts[] = sprintf($idx, $index2['name'], $table, 'id, val'); - - self::$indexes[] = $index1; - self::$indexes[] = $index2; - } - $db = self::$database; - $db->updateDdlBatch($stmts)->pollUntilComplete(); + $db->delete(self::READ_TABLE_NAME, new KeySet(['all' => true])); + $db->delete(self::RANGE_TABLE_NAME, new KeySet(['all' => true])); self::$dataset = self::generateDataset(20, true); - $db->insertBatch(self::$rangeTableName, self::$dataset); + $db->insertOrUpdateBatch(self::RANGE_TABLE_NAME, self::$dataset); } /** @@ -92,7 +72,7 @@ public function testRangeReadSingleKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); $this->assertNotContains(self::$dataset[10], $rows); @@ -114,7 +94,7 @@ public function testRangeReadSingleKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); $this->assertContains(self::$dataset[10], $rows); @@ -135,7 +115,7 @@ public function testRangeReadSingleKeyOpenClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); $this->assertContains(self::$dataset[10], $rows); @@ -156,7 +136,7 @@ public function testRangeReadSingleKeyClosedOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); $this->assertNotContains(self::$dataset[10], $rows); @@ -176,7 +156,7 @@ public function testRangeReadPartialKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); $this->assertNotContains(self::$dataset[10], $rows); @@ -198,7 +178,7 @@ public function testRangeReadPartialKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0])); + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0])); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); $this->assertContains(self::$dataset[10], $rows); @@ -218,8 +198,8 @@ public function testRangeReadIndexSingleKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); @@ -232,7 +212,7 @@ public function testOrderByReturnsRowsOrderedById() $this->insertUnorderedBatch(); - $res = $db->read(self::$rangeTableName, new KeySet(['all' => true]), array_keys(self::$dataset[0]), [ + $res = $db->read(self::RANGE_TABLE_NAME, new KeySet(['all' => true]), array_keys(self::$dataset[0]), [ 'orderBy' => OrderBy::ORDER_BY_PRIMARY_KEY ]); $rows = iterator_to_array($res->rows()); @@ -252,7 +232,7 @@ public function testLockHintReadWriteTransaction() $db = self::$database; $limit = 10; - $res = $db->read(self::$rangeTableName, new KeySet(['all' => true]), array_keys(self::$dataset[0]), [ + $res = $db->read(self::RANGE_TABLE_NAME, new KeySet(['all' => true]), array_keys(self::$dataset[0]), [ 'begin' => true, 'transactionType' => Database::CONTEXT_READWRITE, 'lockHint' => LockHint::LOCK_HINT_EXCLUSIVE, @@ -262,6 +242,8 @@ public function testLockHintReadWriteTransaction() $rows = iterator_to_array($res->rows()); $this->assertNotEmpty($rows); $this->assertEquals($limit, count($rows)); + + $res->transaction()->rollback(); } public function testLockHintOnReadOnlyThrowsAnError() @@ -270,7 +252,7 @@ public function testLockHintOnReadOnlyThrowsAnError() $db = self::$database; $this->expectException(BadRequestException::class); - $res = $db->read(self::$rangeTableName, new KeySet(['all' => true]), array_keys(self::$dataset[0]), [ + $res = $db->read(self::RANGE_TABLE_NAME, new KeySet(['all' => true]), array_keys(self::$dataset[0]), [ 'lockHint' => LockHint::LOCK_HINT_EXCLUSIVE ]); @@ -293,8 +275,8 @@ public function testRangeReadIndexSingleKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); @@ -316,8 +298,8 @@ public function testRangeReadIndexSingleKeyOpenClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); @@ -339,8 +321,8 @@ public function testRangeReadIndexSingleKeyClosedOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); @@ -361,8 +343,8 @@ public function testRangeReadIndexPartialKeyOpen() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertNotContains(self::$dataset[0], $rows); @@ -385,8 +367,8 @@ public function testRangeReadIndexPartialKeyClosed() $keyset = new KeySet(['ranges' => [$range]]); - $res = $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + $res = $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ]); $rows = iterator_to_array($res->rows()); $this->assertContains(self::$dataset[0], $rows); @@ -402,7 +384,7 @@ public function testReadWithLimit() $res = function ($limit) use ($db) { $keyset = new KeySet(['all' => true]); - return $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ + return $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ 'limit' => $limit ])->rows(); }; @@ -423,9 +405,9 @@ public function testReadOverIndexWithLimit() $res = function ($limit) use ($db) { $keyset = new KeySet(['all' => true]); - return $db->read(self::$rangeTableName, $keyset, array_keys(self::$dataset[0]), [ + return $db->read(self::RANGE_TABLE_NAME, $keyset, array_keys(self::$dataset[0]), [ 'limit' => $limit, - 'index' => $this->getIndexName(self::$rangeTableName, 'complex') + 'index' => $this->getIndexName(self::RANGE_TABLE_NAME, 'complex') ])->rows(); }; @@ -444,7 +426,7 @@ public function testReadPoint() $dataset = $this->generateDataset(); $db = self::$database; - $db->insertBatch(self::$readTableName, $dataset); + $db->insertOrUpdateBatch(self::READ_TABLE_NAME, $dataset); $indexes = array_rand($dataset, 4); $points = []; @@ -456,7 +438,7 @@ public function testReadPoint() $keyset = new KeySet(['keys' => $keys]); - $res = $db->read(self::$readTableName, $keyset, array_keys($dataset[0])); + $res = $db->read(self::READ_TABLE_NAME, $keyset, array_keys($dataset[0])); $rows = $res->rows(); foreach ($rows as $index => $row) { $this->assertContains($row, $dataset); @@ -472,7 +454,7 @@ public function testReadPointOverIndex() $dataset = $this->generateDataset(); $db = self::$database; - $db->insertBatch(self::$readTableName, $dataset); + $db->insertOrUpdateBatch(self::READ_TABLE_NAME, $dataset); $indexes = array_rand($dataset, 4); $points = []; @@ -484,8 +466,8 @@ public function testReadPointOverIndex() $keyset = new KeySet(['keys' => $keys]); - $res = $db->read(self::$readTableName, $keyset, array_keys($dataset[0]), [ - 'index' => $this->getIndexName(self::$readTableName, 'complex') + $res = $db->read(self::READ_TABLE_NAME, $keyset, array_keys($dataset[0]), [ + 'index' => $this->getIndexName(self::READ_TABLE_NAME, 'complex') ]); $rows = $res->rows(); foreach ($rows as $index => $row) { @@ -565,15 +547,7 @@ private static function generateDataset($count = 20, $ordered = false) private function getIndexName($table, $type) { - $res = array_filter(self::$indexes, function ($index) use ($table, $type) { - return $index['table'] === $table && $index['type'] === $type; - }); - - if (!$res) { - throw new \RuntimeException('index not found'); - } - - return current($res)['name']; + return $type === 'simple' ? $table . '_Idx1' : $table . '_Idx2'; } private function insertUnorderedBatch() @@ -583,7 +557,7 @@ private function insertUnorderedBatch() // If that happens, we recursively call this function to generate another set. try { $unorderedDataset = self::generateDataset(10, false); - self::$database->insertBatch(self::$rangeTableName, $unorderedDataset); + self::$database->insertOrUpdateBatch(self::RANGE_TABLE_NAME, $unorderedDataset); } catch (ConflictException $e) { $json = json_decode($e->getMessage(), true); diff --git a/Spanner/tests/System/SnapshotTest.php b/Spanner/tests/System/SnapshotTest.php index 780eaca1bf5d..92fd09e721eb 100644 --- a/Spanner/tests/System/SnapshotTest.php +++ b/Spanner/tests/System/SnapshotTest.php @@ -35,7 +35,7 @@ class SnapshotTest extends SystemTestCase const TABLE_NAME = 'Snapshots'; - private static $tableName; + /** * @beforeClass @@ -43,21 +43,8 @@ class SnapshotTest extends SystemTestCase public static function setUpTestFixtures(): void { self::setUpTestDatabase(); - - self::$tableName = uniqid(self::TABLE_NAME); - - self::$database->updateDdl( - 'CREATE TABLE ' . self::$tableName . ' ( - id INT64 NOT NULL, - number INT64 NOT NULL - ) PRIMARY KEY (id)' - )->pollUntilComplete(); } - /** - * covers 63 - * covers 68 - */ public function testSnapshotStrongRead() { $db = self::$database; @@ -68,13 +55,13 @@ public function testSnapshotStrongRead() 'number' => 1 ]; - $db->insert(self::$tableName, $row); + $db->insert(self::TABLE_NAME, $row); $snapshot = $db->snapshot(['strong' => true, 'returnReadTimestamp' => true]); $newRow = $row; $newRow['number'] = 2; - $db->replace(self::$tableName, $newRow); + $db->replace(self::TABLE_NAME, $newRow); $res = $this->getRow($snapshot, $id); $this->assertEquals($res, $row); @@ -95,14 +82,14 @@ public function testSnapshotExactTimestampRead() 'number' => 1 ]; - $db->insert(self::$tableName, $row); + $db->insert(self::TABLE_NAME, $row); sleep(1); $ts = new Timestamp(new \DateTimeImmutable()); sleep(1); $newRow = $row; $newRow['number'] = 2; - $db->replace(self::$tableName, $newRow); + $db->replace(self::TABLE_NAME, $newRow); $snapshot = $db->snapshot([ 'readTimestamp' => $ts, @@ -128,14 +115,14 @@ public function testSnapshotMinReadTimestamp() 'number' => 1 ]; - $db->insert(self::$tableName, $row); + $db->insert(self::TABLE_NAME, $row); sleep(1); $ts = new Timestamp(new \DateTimeImmutable('now', new \DateTimeZone('UTC'))); sleep(2); $newRow = $row; $newRow['number'] = 2; - $db->replace(self::$tableName, $newRow); + $db->replace(self::TABLE_NAME, $newRow); $snapshot = $db->snapshot([ 'minReadTimestamp' => $ts, @@ -160,14 +147,14 @@ public function testSnapshotExactStaleness() 'number' => 1 ]; - $db->insert(self::$tableName, $row); + $db->insert(self::TABLE_NAME, $row); sleep(1); $ts = new Timestamp(new \DateTimeImmutable()); sleep(1); $newRow = $row; $newRow['number'] = 2; - $db->replace(self::$tableName, $newRow); + $db->replace(self::TABLE_NAME, $newRow); $duration = new Duration(['seconds' => 1, 'nanos' => 0]); @@ -198,14 +185,14 @@ public function testSnapshotMaxStaleness() 'number' => 1 ]; - $db->insert(self::$tableName, $row); + $db->insert(self::TABLE_NAME, $row); sleep(1); $ts = new Timestamp(new \DateTimeImmutable()); sleep(1); $newRow = $row; $newRow['number'] = 2; - $db->replace(self::$tableName, $newRow); + $db->replace(self::TABLE_NAME, $newRow); $duration = new Duration(['seconds' => 1, 'nanos' => 0]); @@ -251,13 +238,13 @@ public function testOrderByInSnapshot() { $db = self::$database; - $db->insertBatch(self::$tableName, [ + $db->insertOrUpdateBatch(self::TABLE_NAME, [ [ - 'id' => rand(1, 346464), + 'id' => self::randId(), 'number' => 1 ], [ - 'id' => rand(1, 346464), + 'id' => self::randId(), 'number' => 2 ] ]); @@ -272,7 +259,7 @@ public function testOrderByInSnapshot() ]; $snapshot = $db->snapshot(); - $res = $snapshot->read(self::$tableName, $keySet, $cols, $options); + $res = $snapshot->read(self::TABLE_NAME, $keySet, $cols, $options); $rows = iterator_to_array($res->rows()); // Assert that the returned rows are sorted by the 'id' property. @@ -303,13 +290,13 @@ public function testLockHintInSnapshotThrowsAnException() ]; $snapshot = $db->snapshot(); - $res = $snapshot->read(self::$tableName, $keySet, $cols, $options); + $res = $snapshot->read(self::TABLE_NAME, $keySet, $cols, $options); $rows = iterator_to_array($res->rows()); } private function getRow($client, $id) { - $result = $client->execute('SELECT * FROM ' . self::$tableName . ' WHERE id=@id', [ + $result = $client->execute('SELECT * FROM ' . self::TABLE_NAME . ' WHERE id=@id', [ 'parameters' => [ 'id' => $id ] diff --git a/Spanner/tests/System/SystemTestCaseTrait.php b/Spanner/tests/System/SystemTestCaseTrait.php index 50c2259e092e..560acfb5be4c 100644 --- a/Spanner/tests/System/SystemTestCaseTrait.php +++ b/Spanner/tests/System/SystemTestCaseTrait.php @@ -44,6 +44,9 @@ private static function getClient() if (self::$client) { return self::$client; } + if (TestDatabaseManager::$client) { + return self::$client = TestDatabaseManager::$client; + } $keyFilePath = getenv('GOOGLE_CLOUD_PHP_TESTS_KEY_PATH'); @@ -68,6 +71,7 @@ private static function getClient() ] ]; $clientConfig = [ + 'projectId' => getenv('GOOGLE_CLOUD_PROJECT') ?: null, 'keyFilePath' => $keyFilePath, 'enableBuiltInMetrics' => false, // Disabling the metrics for general tests ]; @@ -93,7 +97,13 @@ private static function getClient() private static function setUpTestDatabase(): void { - if (self::$hasSetUp) { + self::setupQueue(); + if (TestDatabaseManager::$sqlHasSetUp) { + self::$client = TestDatabaseManager::$client; + self::$instance = TestDatabaseManager::$instance; + self::$database = TestDatabaseManager::$sqlDatabase; + self::$dbName = TestDatabaseManager::$sqlDbName; + self::$hasSetUp = true; return; } @@ -101,44 +111,144 @@ private static function setUpTestDatabase(): void if (!self::$dbName = getenv('GOOGLE_CLOUD_SPANNER_TEST_DATABASE')) { self::$dbName = uniqid(self::TESTING_PREFIX); - self::$deletionQueue->add(function () { + register_shutdown_function(function () { self::getDatabaseInstance(self::$dbName)->drop(); }); } + + if ($token = getenv('TEST_TOKEN')) { + self::$dbName .= '-' . $token; + } + self::$database = self::getDatabaseInstance(self::$dbName); if (!self::$database->exists()) { $op = self::$instance->createDatabase(self::$dbName); $op->pollUntilComplete(); - $op = self::$database->updateDdlBatch( - [ - 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' ( + } + + $op = self::$database->updateDdlBatch( + [ + 'CREATE TABLE IF NOT EXISTS BatchTest ( + id INT64 NOT NULL, + decade INT64 NOT NULL + ) PRIMARY KEY (id)', + 'CREATE TABLE IF NOT EXISTS Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024) + ) PRIMARY KEY (SingerId)', + 'CREATE TABLE IF NOT EXISTS Albums ( + SingerId INT64 NOT NULL, + AlbumId INT64 NOT NULL, + AlbumTitle STRING(1024) + ) PRIMARY KEY (SingerId, AlbumId), + INTERLEAVE IN PARENT Singers ON DELETE CASCADE', + 'CREATE TABLE IF NOT EXISTS LargeReadTable ( + id INT64 NOT NULL, + stringColumn STRING(MAX) NOT NULL, + bytesColumn BYTES(MAX) NOT NULL, + stringArrayColumn ARRAY NOT NULL, + bytesArrayColumn ARRAY NOT NULL + ) PRIMARY KEY (id)', + 'CREATE TABLE IF NOT EXISTS partitionedDml ( + id INT64 NOT NULL, + stringField STRING(MAX), + boolField BOOL + ) PRIMARY KEY (id)', + 'CREATE TABLE IF NOT EXISTS ReadTable ( + id INT64 NOT NULL, + val STRING(MAX) NOT NULL + ) PRIMARY KEY (id)', + 'CREATE UNIQUE INDEX IF NOT EXISTS ReadTable_Idx1 ON ReadTable (id)', + 'CREATE UNIQUE INDEX IF NOT EXISTS ReadTable_Idx2 ON ReadTable (id, val)', + 'CREATE TABLE IF NOT EXISTS RangeTable ( + id INT64 NOT NULL, + val STRING(MAX) NOT NULL + ) PRIMARY KEY (id)', + 'CREATE UNIQUE INDEX IF NOT EXISTS RangeTable_Idx1 ON RangeTable (id)', + 'CREATE UNIQUE INDEX IF NOT EXISTS RangeTable_Idx2 ON RangeTable (id, val)', + 'CREATE TABLE IF NOT EXISTS Snapshots ( + id INT64 NOT NULL, + number INT64 NOT NULL + ) PRIMARY KEY (id)', + 'CREATE TABLE IF NOT EXISTS Transactions ( + id INT64 NOT NULL, + number INT64 NOT NULL + ) PRIMARY KEY (id)', + 'CREATE TABLE IF NOT EXISTS UniverseDomainTest ( id INT64 NOT NULL, name STRING(MAX) NOT NULL, birthday DATE - ) PRIMARY KEY (id)', - 'CREATE UNIQUE INDEX ' . self::TEST_INDEX_NAME . ' - ON ' . self::TEST_TABLE_NAME . ' (name)', - ] - ); - $op->pollUntilComplete(); - - if (self::$database->info()['databaseDialect'] == DatabaseDialect::GOOGLE_STANDARD_SQL - && !self::isEmulatorUsed() - ) { - self::$database->updateDdlBatch( - [ - 'CREATE ROLE ' . self::DATABASE_ROLE, - 'CREATE ROLE ' . self::RESTRICTIVE_DATABASE_ROLE, - 'GRANT SELECT ON TABLE ' . self::TEST_TABLE_NAME . - ' TO ROLE ' . self::DATABASE_ROLE, - 'GRANT SELECT(id, name), INSERT(id, name), UPDATE(id, name) ON TABLE ' - . self::TEST_TABLE_NAME . ' TO ROLE ' . self::RESTRICTIVE_DATABASE_ROLE, - ] - )->pollUntilComplete(); - } + ) PRIMARY KEY (id)', + 'CREATE PROTO BUNDLE ( + testing.data.User, + testing.data.User.Address, + testing.data.Book + )', + 'CREATE TABLE IF NOT EXISTS Writes ( + id INT64 NOT NULL, + arrayField ARRAY, + arrayBoolField ARRAY, + arrayFloatField ARRAY, + arrayFloat32Field ARRAY, + arrayStringField ARRAY, + arrayBytesField ARRAY, + arrayTimestampField ARRAY, + arrayDateField ARRAY, + arrayNumericField ARRAY, + arrayProtoField ARRAY<`testing.data.User`>, + boolField BOOL, + bytesField BYTES(MAX), + dateField DATE, + floatField FLOAT64, + float32Field FLOAT32, + intField INT64, + stringField STRING(MAX), + timestampField TIMESTAMP, + numericField NUMERIC, + uuidField STRING(36), + arrayUuidField ARRAY, + protoField `testing.data.User` + ) PRIMARY KEY (id)', + 'CREATE TABLE IF NOT EXISTS CommitTimestamps ( + id INT64 NOT NULL, + commitTimestamp TIMESTAMP NOT NULL OPTIONS + (allow_commit_timestamp=true) + ) PRIMARY KEY (id, commitTimestamp DESC)', + 'CREATE TABLE IF NOT EXISTS ' . self::TEST_TABLE_NAME . ' ( + id INT64 NOT NULL, + name STRING(MAX) NOT NULL, + birthday DATE + ) PRIMARY KEY (id)', + 'CREATE UNIQUE INDEX IF NOT EXISTS ' . self::TEST_INDEX_NAME . ' + ON ' . self::TEST_TABLE_NAME . ' (name)', + ], + ['protoDescriptors' => file_get_contents(__DIR__ . '/../data/proto/user.pb')] + ); + $op->pollUntilComplete(); + + if (self::$database->info()['databaseDialect'] == DatabaseDialect::GOOGLE_STANDARD_SQL + && !self::isEmulatorUsed() + ) { + self::$database->updateDdlBatch([ + 'CREATE ROLE ' . self::DATABASE_ROLE, + 'CREATE ROLE ' . self::RESTRICTIVE_DATABASE_ROLE + ])->pollUntilComplete(); + self::$database->updateDdlBatch([ + 'GRANT SELECT ON TABLE ' . self::TEST_TABLE_NAME . ' TO ROLE ' . self::DATABASE_ROLE, + 'GRANT SELECT(id, name), INSERT(id, name), UPDATE(id, name) ON TABLE ' + . self::TEST_TABLE_NAME . ' TO ROLE ' . self::RESTRICTIVE_DATABASE_ROLE, + 'GRANT SELECT(id) ON TABLE BatchTest TO ROLE ' . self::RESTRICTIVE_DATABASE_ROLE, + 'GRANT SELECT ON TABLE BatchTest TO ROLE ' . self::DATABASE_ROLE, + ])->pollUntilComplete(); } + TestDatabaseManager::$sqlHasSetUp = true; + TestDatabaseManager::$client = self::$client; + TestDatabaseManager::$instance = self::$instance; + TestDatabaseManager::$sqlDatabase = self::$database; + TestDatabaseManager::$sqlDbName = self::$dbName; self::$hasSetUp = true; } diff --git a/Spanner/tests/System/TestDatabaseManager.php b/Spanner/tests/System/TestDatabaseManager.php new file mode 100644 index 000000000000..d5eda482485f --- /dev/null +++ b/Spanner/tests/System/TestDatabaseManager.php @@ -0,0 +1,38 @@ + self::$id1, @@ -69,19 +68,13 @@ public static function setUpTestFixtures(): void self::$database->insert(self::TEST_TABLE_NAME, self::$row); - self::$database->updateDdl( - 'CREATE TABLE ' . self::$tableName . ' ( - id INT64 NOT NULL, - number INT64 NOT NULL - ) PRIMARY KEY (id)' - )->pollUntilComplete(); self::$isSetup = true; } public function testRunTransaction() { $db = self::$database; - $id = rand(1, 346464); + $id = self::randId(); $keySet = new KeySet([ 'keys' => [$id] ]); @@ -122,7 +115,7 @@ public function testConcurrentTransactionsIncrementValueWithRead() $db = self::$database; $id = $this->randId(); - $db->insert(self::$tableName, [ + $db->insert(self::TABLE_NAME, [ 'id' => $id, 'number' => 0 ]); @@ -131,11 +124,11 @@ public function testConcurrentTransactionsIncrementValueWithRead() 'php', __DIR__ . '/pcntl/ConcurrentTransactionsIncrementValueWithRead.php', $db->name(), - self::$tableName, + self::TABLE_NAME, $id ])); - $row = $db->execute('SELECT * FROM ' . self::$tableName . ' WHERE id = @id', [ + $row = $db->execute('SELECT * FROM ' . self::TABLE_NAME . ' WHERE id = @id', [ 'parameters' => [ 'id' => $id ] @@ -157,7 +150,7 @@ public function testTransactionNoCommit() $ex = false; try { $db->runTransaction(function ($t) { - $t->execute('SELECT * FROM ' . self::$tableName); + $t->execute('SELECT * FROM ' . self::TABLE_NAME); }); } catch (\RuntimeException $e) { $this->assertEquals('Transactions must be rolled back or committed.', $e->getMessage()); @@ -181,7 +174,7 @@ public function testAbortedErrorCausesRetry() $db = self::$database; $id = $this->randId(); - $db->insert(self::$tableName, [ + $db->insert(self::TABLE_NAME, [ 'id' => $id, 'number' => 0 ]); @@ -190,11 +183,11 @@ public function testAbortedErrorCausesRetry() 'php', __DIR__ . '/pcntl/AbortedErrorCausesRetry.php', $db->name(), - self::$tableName, + self::TABLE_NAME, $id ])); - $row = $db->execute('SELECT * FROM ' . self::$tableName . ' WHERE id = @id', [ + $row = $db->execute('SELECT * FROM ' . self::TABLE_NAME . ' WHERE id = @id', [ 'parameters' => [ 'id' => $id ] @@ -220,7 +213,7 @@ public function testConcurrentTransactionsIncrementValueWithExecute() $db = self::$database; $id = $this->randId(); - $db->insert(self::$tableName, [ + $db->insert(self::TABLE_NAME, [ 'id' => $id, 'number' => 0 ]); @@ -229,11 +222,11 @@ public function testConcurrentTransactionsIncrementValueWithExecute() 'php', __DIR__ . '/pcntl/ConcurrentTransactionsIncrementValueWithExecute.php', $db->name(), - self::$tableName, + self::TABLE_NAME, $id ])); - $row = $db->execute('SELECT * FROM ' . self::$tableName . ' WHERE id = @id', [ + $row = $db->execute('SELECT * FROM ' . self::TABLE_NAME . ' WHERE id = @id', [ 'parameters' => [ 'id' => $id ] @@ -285,7 +278,7 @@ public function testRunTransactionWithDbRole($db, $values, $expected) try { $db->runTransaction(function ($t) use ($values) { - $id = rand(1, 346464); + $id = self::randId(); $t->insert(self::TEST_TABLE_NAME, $values); $t->commit(); @@ -312,20 +305,20 @@ public function testTransactionExecuteWithDirectedRead($directedReadOptions) $db = self::$database; $id = $this->randId(); - $db->insert(self::$tableName, [ + $db->insert(self::TABLE_NAME, [ 'id' => $id, 'number' => 0 ]); $snapshot = $db->snapshot(); $rows = $snapshot->execute( - 'SELECT * FROM ' . self::$tableName . ' WHERE id = ' . $id, + 'SELECT * FROM ' . self::TABLE_NAME . ' WHERE id = ' . $id, $directedReadOptions )->rows()->current(); $this->assertEquals(0, $rows['number']); $rows = $db->execute( - 'SELECT * FROM ' . self::$tableName . ' WHERE id = ' . $id, + 'SELECT * FROM ' . self::TABLE_NAME . ' WHERE id = ' . $id, ['transactionId' => $snapshot->id()] + $directedReadOptions )->rows()->current(); $this->assertEquals(0, $rows['number']); @@ -346,7 +339,7 @@ public function testRWTransactionExecuteFailsWithDirectedRead($directedReadOptio try { $rows = $db->execute( - 'SELECT * FROM ' . self::$tableName, + 'SELECT * FROM ' . self::TABLE_NAME, ['transactionId' => $transaction->id()] + $directedReadOptions )->rows()->current(); } catch (ServiceException $e) { @@ -357,7 +350,7 @@ public function testRWTransactionExecuteFailsWithDirectedRead($directedReadOptio $exception = null; try { $row = $transaction->execute( - 'SELECT * FROM ' . self::$tableName, + 'SELECT * FROM ' . self::TABLE_NAME, $directedReadOptions )->rows()->current(); } catch (ServiceException $e) { @@ -412,7 +405,7 @@ public function testRunTransactionILBWithMultipleOperations() $db = self::$database; $res = $db->runTransaction(function ($t) { - $id = rand(1, 346464); + $id = self::randId(); $row = [ 'id' => $id, 'name' => uniqid(self::TESTING_PREFIX), @@ -422,7 +415,7 @@ public function testRunTransactionILBWithMultipleOperations() $t->insert(self::TEST_TABLE_NAME, $row); $this->assertNull($t->id()); - $id = rand(1, 346464); + $id = self::randId(); $t->executeUpdate( 'INSERT INTO ' . self::TEST_TABLE_NAME . ' (id, name, birthday) VALUES (@id, @name, @birthday)', [ @@ -496,7 +489,7 @@ public function testTransactionToChannelAffinity() }; $res = $db->runTransaction(function ($t) use ($getChannel) { - $id = rand(1, 346464); + $id = self::randId(); $row = [ 'id' => $id, 'name' => uniqid(self::TESTING_PREFIX), @@ -506,7 +499,7 @@ public function testTransactionToChannelAffinity() $t->insert(self::TEST_TABLE_NAME, $row); $this->assertNull($t->id()); - $id = rand(1, 346464); + $id = self::randId(); $t->executeUpdate( 'INSERT INTO ' . self::TEST_TABLE_NAME . ' (id, name, birthday) VALUES (@id, @name, @birthday)', [ @@ -684,7 +677,7 @@ private function getMultipleRows(int $total) // if $total is 10, then we will generate 9 rows. for ($i = 0; $i < $total; $i++) { $rows[] = [ - 'id' => rand(1, 346464), + 'id' => self::randId(), 'name' => uniqid(self::TESTING_PREFIX), 'birthday' => new Date(new \DateTime('2000-01-01')) ]; diff --git a/Spanner/tests/System/UniverseDomainTest.php b/Spanner/tests/System/UniverseDomainTest.php index 23f9aaa59f8d..fa57235ba4a6 100644 --- a/Spanner/tests/System/UniverseDomainTest.php +++ b/Spanner/tests/System/UniverseDomainTest.php @@ -75,6 +75,12 @@ public function testCreateInstanceWithUniverseDomain() $this->assertEquals(LongRunningOperation::STATE_SUCCESS, $op->state(), json_encode($op->error())); self::$instance = self::$client->instance(self::$instanceId); + self::$deletionQueue->add(function () { + if (self::$instance->exists()) { + self::$instance->delete(); + } + }); + $info = self::$instance->info(); $this->assertStringEndsWith('/' . self::$instanceId, $info['name']); @@ -88,21 +94,25 @@ public function testCreateInstanceWithUniverseDomain() */ public function testCreateDatabaseWithUniverseDomain() { - $op = self::$instance->createDatabase(self::$dbName); + $op = self::$instance->createDatabase(self::$dbName, [ + 'statements' => [ + 'CREATE TABLE ' . self::$tableName . ' ( + id INT64 NOT NULL, + name STRING(MAX) NOT NULL + ) PRIMARY KEY (id)' + ] + ]); $op->pollUntilComplete(); self::$database = self::$instance->database(self::$dbName); + self::$deletionQueue->add(function () { + if (self::$database->exists()) { + self::$database->drop(); + } + }); + $this->assertStringEndsWith('/' . self::$dbName, self::$database->name()); - // Create a test table - $op = self::$database->updateDdlBatch([ - 'CREATE TABLE ' . self::$tableName . ' ( - id INT64 NOT NULL, - name STRING(MAX) NOT NULL - ) PRIMARY KEY (id)' - ]); - $op->pollUntilComplete(); - // Verify the table was created $result = self::$database->execute( "SELECT table_name as name FROM information_schema.tables WHERE table_catalog = '' AND table_schema = ''" diff --git a/Spanner/tests/System/WriteTest.php b/Spanner/tests/System/WriteTest.php index 5efd7b02d3c5..0dfabaa5fbd0 100644 --- a/Spanner/tests/System/WriteTest.php +++ b/Spanner/tests/System/WriteTest.php @@ -28,6 +28,7 @@ use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Numeric; use Google\Cloud\Spanner\Proto; +use Google\Cloud\Spanner\Uuid; use Google\Cloud\Spanner\Timestamp; use Google\Protobuf\Internal\Message; use Google\Rpc\Code; @@ -52,46 +53,6 @@ public static function setUpTestFixtures(): void { self::skipEmulatorTests(); self::setUpTestDatabase(); - - self::$database->updateDdlBatch([ - 'CREATE PROTO BUNDLE (' . - 'testing.data.User,' . - 'testing.data.User.Address,' . - 'testing.data.Book' . - ')', - 'CREATE TABLE ' . self::TABLE_NAME . ' ( - id INT64 NOT NULL, - arrayField ARRAY, - arrayBoolField ARRAY, - arrayFloatField ARRAY, - arrayFloat32Field ARRAY, - arrayStringField ARRAY, - arrayBytesField ARRAY, - arrayTimestampField ARRAY, - arrayDateField ARRAY, - arrayNumericField ARRAY, - arrayProtoField ARRAY<`testing.data.User`>, - boolField BOOL, - bytesField BYTES(MAX), - dateField DATE, - floatField FLOAT64, - float32Field FLOAT32, - intField INT64, - stringField STRING(MAX), - timestampField TIMESTAMP, - numericField NUMERIC, - uuidField STRING(36), - arrayUuidField ARRAY, - protoField `testing.data.User`, - ) PRIMARY KEY (id)', - 'CREATE TABLE ' . self::COMMIT_TIMESTAMP_TABLE_NAME . ' ( - id INT64 NOT NULL, - commitTimestamp TIMESTAMP NOT NULL OPTIONS - (allow_commit_timestamp=true) - ) PRIMARY KEY (id, commitTimestamp DESC)' - ], [ - 'protoDescriptors' => file_get_contents(__DIR__ . '/../data/proto/user.pb'), - ])->pollUntilComplete(); } public function fieldValueProvider() @@ -129,7 +90,7 @@ public function testWriteAndReadBackValue($id, $field, $value) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -139,8 +100,12 @@ public function testWriteAndReadBackValue($id, $field, $value) $read = $db->read(self::TABLE_NAME, $keyset, [$field]); $row = $read->rows()->current(); - if ($value instanceof Timestamp || $value instanceof Uuid) { + if ($value instanceof Timestamp) { $this->assertEquals($value->formatAsString(), $row[$field]->formatAsString()); + } elseif ($value instanceof Uuid) { + $this->assertEquals($value->formatAsString(), is_string($row[$field]) + ? $row[$field] + : $row[$field]->formatAsString()); } else { $this->assertValues($value, $row[$field]); } @@ -153,8 +118,12 @@ public function testWriteAndReadBackValue($id, $field, $value) ]); $row = $exec->rows()->current(); - if ($value instanceof Timestamp || $value instanceof Uuid) { + if ($value instanceof Timestamp) { $this->assertEquals($value->formatAsString(), $row[$field]->formatAsString()); + } elseif ($value instanceof Uuid) { + $this->assertEquals($value->formatAsString(), is_string($row[$field]) + ? $row[$field] + : $row[$field]->formatAsString()); } elseif ($value instanceof Message) { $this->assertInstanceOf(Proto::class, $row[$field]); $this->assertEquals(base64_encode($value->serializeToString()), $row[$field]->getValue()); @@ -175,7 +144,7 @@ public function testWriteAndReadBackBytes() $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -207,7 +176,7 @@ public function testWriteAndReadBackNaN() $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -250,7 +219,7 @@ public function testWriteAndReadBackNullValue($id, $field) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => null ]); @@ -344,7 +313,7 @@ public function testWriteAndReadBackFancyArrayValue($id, $field, $value) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -390,7 +359,7 @@ public function testWriteAndReadBackFancyArrayComplexValue($id, $field, $value) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, $field => $value ]); @@ -424,7 +393,7 @@ public function testWriteToNonExistentTableFails() $db = self::$database; - $db->insert(uniqid(self::TESTING_PREFIX), ['foo' => 'bar']); + $db->insertOrUpdate(uniqid(self::TESTING_PREFIX), ['foo' => 'bar']); } public function testWriteToNonExistentColumnFails() @@ -433,7 +402,7 @@ public function testWriteToNonExistentColumnFails() $db = self::$database; - $db->insert(self::TABLE_NAME, [uniqid(self::TESTING_PREFIX) => 'bar']); + $db->insertOrUpdate(self::TABLE_NAME, [uniqid(self::TESTING_PREFIX) => 'bar']); } public function testWriteIncorrectTypeToColumn() @@ -442,7 +411,7 @@ public function testWriteIncorrectTypeToColumn() $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $this->randId(), 'boolField' => 'bar' ]); @@ -456,7 +425,7 @@ public function testWriteAndReadBackRandomBytes($id, $bytes) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'bytesField' => $bytes ]); @@ -492,7 +461,7 @@ public function testWriteAndReadBackRandomNumeric($id, $numeric) { $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'numericField' => $numeric ]); @@ -527,7 +496,7 @@ public function randomNumericProvider() public function testCommitTimestamp() { $id = $this->randId(); - $ts = self::$database->insert(self::COMMIT_TIMESTAMP_TABLE_NAME, [ + $ts = self::$database->insertOrUpdate(self::COMMIT_TIMESTAMP_TABLE_NAME, [ 'id' => $id, 'commitTimestamp' => new CommitTimestamp() ]); @@ -545,7 +514,7 @@ public function testSetFieldToNull() { $id = $this->randId(); $str = base64_encode(random_bytes(rand(100, 9999))); - $row = self::$database->insert(self::TABLE_NAME, [ + $row = self::$database->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'stringField' => $str ]); @@ -575,7 +544,7 @@ public function testTimestampPrecision($timestamp) { $id = $this->randId(); - $row = self::$database->insert(self::TABLE_NAME, [ + $row = self::$database->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'timestampField' => $timestamp ]); @@ -617,7 +586,7 @@ public function testTimestampPrecisionLocale($timestamp) try { $id = $this->randId(); - $row = self::$database->insert(self::TABLE_NAME, [ + $row = self::$database->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'timestampField' => $timestamp ]); @@ -837,7 +806,7 @@ public function testExecuteUpdateTransactionMixed() $this->assertEquals(1, $count); - $t->insert(self::TABLE_NAME, [ + $t->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id2, 'stringField' => $randStr ]); @@ -909,7 +878,7 @@ public function testPdml() $randStr2 = base64_encode(random_bytes(500)); $db = self::$database; - $db->insert(self::TABLE_NAME, [ + $db->insertOrUpdate(self::TABLE_NAME, [ 'id' => $id, 'stringField' => $randStr ]); diff --git a/phpunit-system.xml.dist b/phpunit-system.xml.dist index 3626d1263fbd..6178ad72dc76 100644 --- a/phpunit-system.xml.dist +++ b/phpunit-system.xml.dist @@ -7,7 +7,6 @@ Datastore/tests/System Firestore/tests/System Logging/tests/System - Spanner/tests/System