From a486a33e640ad69d54aa79412c3e3c4f6308763b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ha=C5=82as?= Date: Mon, 30 Mar 2026 10:36:30 +0200 Subject: [PATCH 1/2] Add Locality API (getBoundaryKeys) Closes #3 - Locality::getBoundaryKeys() returns start keys of contiguous ranges stored on single servers - Reads from system keyspace with READ_SYSTEM_KEYS and LOCK_AWARE options - Handles transaction_too_old errors with automatic retry for large ranges - 5 integration tests --- src/Locality.php | 50 ++++++++++++++++++ tests/Integration/LocalityTest.php | 83 ++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/Locality.php create mode 100644 tests/Integration/LocalityTest.php diff --git a/src/Locality.php b/src/Locality.php new file mode 100644 index 0000000..effe28b --- /dev/null +++ b/src/Locality.php @@ -0,0 +1,50 @@ + + */ + public static function getBoundaryKeys(Database $db, string $begin, string $end): array + { + $boundaries = []; + $currentBegin = $begin; + + while ($currentBegin < $end) { + $tr = $db->createTransaction(); + $tr->options()->setReadSystemKeys(); + $tr->options()->setLockAware(); + + $lastBegin = $currentBegin; + + try { + $rangeResult = $tr->snapshot()->getRange( + self::KEY_SERVERS_PREFIX . $currentBegin, + self::KEY_SERVERS_PREFIX . $end, + ); + + foreach ($rangeResult as $kv) { + $key = substr($kv->key, strlen(self::KEY_SERVERS_PREFIX)); + $boundaries[] = $key; + $currentBegin = $key . "\x00"; + } + + $currentBegin = $end; + } catch (FDBException $e) { + if ($e->fdbCode === 1007 && $currentBegin !== $lastBegin) { + continue; + } + + $tr->onError($e->fdbCode)->await(); + } + } + + return $boundaries; + } +} diff --git a/tests/Integration/LocalityTest.php b/tests/Integration/LocalityTest.php new file mode 100644 index 0000000..bdcab3b --- /dev/null +++ b/tests/Integration/LocalityTest.php @@ -0,0 +1,83 @@ +set('test/locality/a', 'value'); + + $boundaries = Locality::getBoundaryKeys(self::$db, '', "\xFF"); + + self::assertNotEmpty($boundaries); + } + + #[Test] + public function getBoundaryKeysReturnsStrings(): void + { + $boundaries = Locality::getBoundaryKeys(self::$db, '', "\xFF"); + + foreach ($boundaries as $boundary) { + self::assertNotEmpty($boundary); + } + } + + #[Test] + public function getBoundaryKeysWithNarrowRange(): void + { + for ($i = 0; $i < 5; $i++) { + self::$db->set("test/locality/narrow/{$i}", str_repeat('x', 100)); + } + + $boundaries = Locality::getBoundaryKeys(self::$db, 'test/locality/narrow/', 'test/locality/narrow0'); + + self::assertGreaterThanOrEqual(0, count($boundaries)); + } + + #[Test] + public function getBoundaryKeysWithEmptyRangeReturnsEmptyArray(): void + { + $boundaries = Locality::getBoundaryKeys(self::$db, "\xFE", "\xFE"); + + self::assertSame([], $boundaries); + } + + #[Test] + public function getBoundaryKeysAreSorted(): void + { + $boundaries = Locality::getBoundaryKeys(self::$db, '', "\xFF"); + $count = count($boundaries); + + for ($i = 1; $i < $count; $i++) { + self::assertGreaterThan( + $boundaries[$i - 1], + $boundaries[$i], + 'Boundary keys should be in ascending order', + ); + } + } +} From f6facbc0ba506c74100fef99b754ab920d0dfe8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ha=C5=82as?= Date: Mon, 30 Mar 2026 10:38:58 +0200 Subject: [PATCH 2/2] Fix LocalityTest: handle empty boundary key and ensure assertions --- tests/Integration/LocalityTest.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/Integration/LocalityTest.php b/tests/Integration/LocalityTest.php index bdcab3b..942aaeb 100644 --- a/tests/Integration/LocalityTest.php +++ b/tests/Integration/LocalityTest.php @@ -37,13 +37,12 @@ public function getBoundaryKeysReturnsNonEmptyArray(): void } #[Test] - public function getBoundaryKeysReturnsStrings(): void + public function getBoundaryKeysContainsOnlyStrings(): void { $boundaries = Locality::getBoundaryKeys(self::$db, '', "\xFF"); - foreach ($boundaries as $boundary) { - self::assertNotEmpty($boundary); - } + self::assertNotEmpty($boundaries); + self::assertContainsOnly('string', $boundaries); } #[Test] @@ -70,6 +69,9 @@ public function getBoundaryKeysWithEmptyRangeReturnsEmptyArray(): void public function getBoundaryKeysAreSorted(): void { $boundaries = Locality::getBoundaryKeys(self::$db, '', "\xFF"); + + self::assertGreaterThanOrEqual(1, count($boundaries)); + $count = count($boundaries); for ($i = 1; $i < $count; $i++) {