Index.php
PHP
Path: src/Table/Index.php
<?php
namespace mini\Table;
use Closure;
use mini\Table\Index\IndexInterface;
use mini\Table\Index\TreapIndex;
/**
* Index factory and key encoding utilities.
*
* For bulk builds (load once, query many times):
* ```php
* $index = Index::fromGenerator(function() use ($table) {
* foreach ($table as $rowId => $row) {
* yield [Index::packInt($row['age']), $rowId];
* }
* });
* ```
*
* For dynamic insert-heavy workloads, use LsmIndex directly:
* ```php
* $index = new LsmIndex();
* $index->insert(Index::packInt($age), $rowId);
* ```
*
* Query:
* ```php
* foreach ($index->eq(Index::packInt(25)) as $rowId) { ... }
* foreach ($index->range(Index::packInt(18), Index::packInt(65)) as $rowId) { ... }
* ```
*/
final class Index
{
private function __construct() {} // Factory only - not instantiable
/**
* Build index from a generator function.
* Generator should yield [string $key, int $rowId] pairs.
*/
public static function fromGenerator(Closure $fn): IndexInterface
{
return TreapIndex::fromGenerator($fn);
}
/**
* Build index from array of [key, rowId] pairs.
*/
public static function fromArray(array $rows): IndexInterface
{
return TreapIndex::fromArray($rows);
}
// =========================================================================
// Static packing helpers (sortable binary encoding)
// =========================================================================
/**
* Pack integer to 8-byte sortable binary
*/
public static function packInt(int $n): string
{
$hi = ($n >> 32) & 0xFFFFFFFF;
$lo = $n & 0xFFFFFFFF;
$hi ^= 0x80000000; // Flip sign bit for sortability
return pack('N2', $hi, $lo);
}
/**
* Unpack 8-byte binary to integer
*/
public static function unpackInt(string $data): int
{
$parts = unpack('N2', $data);
$hi = $parts[1] ^ 0x80000000; // Restore sign bit
$lo = $parts[2];
return ($hi << 32) | $lo;
}
/**
* Pack float to 8-byte sortable binary
*/
public static function packFloat(float $f): string
{
$bin = pack('E', $f); // Big-endian IEEE-754
$bytes = array_values(unpack('C8', $bin));
if (($bytes[0] & 0x80) !== 0) {
// Negative: invert all bits
for ($i = 0; $i < 8; $i++) {
$bytes[$i] ^= 0xFF;
}
} else {
// Positive: flip sign bit
$bytes[0] ^= 0x80;
}
return pack('C8', ...$bytes);
}
/**
* Unpack 8-byte binary to float
*/
public static function unpackFloat(string $data): float
{
$bytes = array_values(unpack('C8', $data));
if (($bytes[0] & 0x80) === 0) {
// Was negative: invert all bits back
for ($i = 0; $i < 8; $i++) {
$bytes[$i] ^= 0xFF;
}
} else {
// Was positive: flip sign bit back
$bytes[0] ^= 0x80;
}
$bin = pack('C8', ...$bytes);
return unpack('E', $bin)[1];
}
/**
* Pack string with optional max length (for fixed-width keys)
*/
public static function packString(string $s, ?int $maxLength = null): string
{
if ($maxLength !== null && strlen($s) > $maxLength) {
return substr($s, 0, $maxLength);
}
return $s;
}
}