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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/Subspace.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ public function unpack(string $key): array
return Tuple::unpack($key, $prefixLength);
}

/**
* @param list<null|bool|int|float|string|\GMP|Bytes|SingleFloat|Uuid|Versionstamp|list<mixed>> $tuple
*/
public function packWithVersionstamp(array $tuple): string
{
$packed = Tuple::packWithVersionstamp($tuple);
$prefixLength = strlen($this->rawPrefix);

if ($prefixLength === 0) {
return $packed;
}

$body = substr($packed, 0, -4);
$offsetBytes = substr($packed, -4);
$offsetData = unpack('V', $offsetBytes);

if ($offsetData === false) {
throw new \RuntimeException('Failed to read versionstamp offset');
}

/** @var int $originalOffset */
$originalOffset = $offsetData[1];

return $this->rawPrefix . $body . pack('V', $originalOffset + $prefixLength);
}

/**
* @param list<null|bool|int|float|string|\GMP|Bytes|SingleFloat|Uuid|Versionstamp|list<mixed>> $tuple
* @return array{string, string}
Expand Down
84 changes: 84 additions & 0 deletions tests/Unit/SubspaceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use CrazyGoat\FoundationDB\KeyConvertible;
use CrazyGoat\FoundationDB\Subspace;
use CrazyGoat\FoundationDB\Tuple\Tuple;
use CrazyGoat\FoundationDB\Tuple\Versionstamp;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -371,4 +372,87 @@ public function subspaceDoesNotMutateParent(): void

self::assertSame($originalPrefix, $parent->rawPrefix);
}

#[Test]
public function packWithVersionstampPrependsPrefix(): void
{
$sub = new Subspace(['users']);
$packed = $sub->packWithVersionstamp([Versionstamp::incomplete()]);

self::assertTrue(str_starts_with($packed, $sub->rawPrefix));
}

#[Test]
public function packWithVersionstampAdjustsOffset(): void
{
$sub = new Subspace(['users']);
$prefixLength = strlen($sub->rawPrefix);

$withoutPrefix = Tuple::packWithVersionstamp([Versionstamp::incomplete()]);
$originalOffsetData = unpack('V', substr($withoutPrefix, -4));
self::assertIsArray($originalOffsetData);
/** @var int $originalOffset */
$originalOffset = $originalOffsetData[1];

$withPrefix = $sub->packWithVersionstamp([Versionstamp::incomplete()]);
$adjustedOffsetData = unpack('V', substr($withPrefix, -4));
self::assertIsArray($adjustedOffsetData);
/** @var int $adjustedOffset */
$adjustedOffset = $adjustedOffsetData[1];

self::assertSame($originalOffset + $prefixLength, $adjustedOffset);
}

#[Test]
public function packWithVersionstampEmptySubspaceMatchesTuplePack(): void
{
$sub = new Subspace();
$fromSubspace = $sub->packWithVersionstamp([Versionstamp::incomplete()]);
$fromTuple = Tuple::packWithVersionstamp([Versionstamp::incomplete()]);

self::assertSame($fromTuple, $fromSubspace);
}

#[Test]
public function packWithVersionstampWithMultipleElements(): void
{
$sub = new Subspace(['app']);
$packed = $sub->packWithVersionstamp(['key1', Versionstamp::incomplete()]);

self::assertTrue(str_starts_with($packed, $sub->rawPrefix));

$offsetData = unpack('V', substr($packed, -4));
self::assertIsArray($offsetData);
/** @var int $offset */
$offset = $offsetData[1];

self::assertSame(chr(0x33), $packed[$offset - 1]);
}

#[Test]
public function packWithVersionstampWithRawPrefix(): void
{
$sub = new Subspace([], "\xFE");
$packed = $sub->packWithVersionstamp([Versionstamp::incomplete()]);

self::assertTrue(str_starts_with($packed, "\xFE"));

$offsetData = unpack('V', substr($packed, -4));
self::assertIsArray($offsetData);
/** @var int $offset */
$offset = $offsetData[1];

self::assertSame(chr(0x33), $packed[$offset - 1]);
}

#[Test]
public function packWithVersionstampNoVersionstampThrows(): void
{
$sub = new Subspace(['users']);

$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('packWithVersionstamp requires exactly one Versionstamp');

$sub->packWithVersionstamp(['no_versionstamp']);
}
}
Loading