getMessage()); return null; } return $ffi; } function qdb_close_fd(int $fd): void { if ($fd < 0) { return; } if (function_exists('posix_close')) { @posix_close($fd); return; } $ffi = qdb_linux_libc_ffi(); if ($ffi !== null) { $ffi->close($fd); } } /** * @return array{0: int, 1: string}|null [fd, /proc/self/fd/N] */ function qdb_memfd_write(string $bytes): ?array { $ffi = qdb_linux_libc_ffi(); if ($ffi === null) { return null; } try { $MFD_CLOEXEC = 1; $fd = (int)$ffi->memfd_create('qdb_read', $MFD_CLOEXEC); if ($fd < 0) { return null; } $written = @file_put_contents('/proc/self/fd/' . $fd, $bytes); if ($written === false || $written !== strlen($bytes)) { qdb_close_fd($fd); return null; } return [$fd, '/proc/self/fd/' . $fd]; } catch (Throwable $e) { error_log('qdb_memfd_write: ' . $e->getMessage()); return null; } } function qdb_read_open_sqlite_on_path(string $sqlitePath, string $schemaSql, bool $fresh): PDO { if ($fresh) { $pdo = new PDO('sqlite:' . $sqlitePath); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec($schemaSql); $pdo->exec('PRAGMA user_version = ' . QDB_VERSION . ';'); } else { $pdo = new PDO('sqlite:' . $sqlitePath); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } $pdo->exec('PRAGMA foreign_keys = ON;'); // DELETE (not WAL) so a migration persist via file_get_contents + qdb_save is complete. $pdo->exec('PRAGMA journal_mode = DELETE;'); $schemaChanged = qdb_apply_migrations($pdo, $schemaSql); if ($schemaChanged) { $migrateLock = qdb_acquire_lock(); try { if ($sqlitePath === ':memory:') { throw new Exception('Cannot persist migration from in-memory read snapshot'); } $savePath = $sqlitePath; if (str_starts_with($sqlitePath, '/proc/')) { $savePath = tempnam(sys_get_temp_dir(), 'qdb_migrate_'); $bytes = file_get_contents($sqlitePath); if ($bytes === false || file_put_contents($savePath, $bytes) === false) { throw new Exception('Could not copy memfd DB for migration save'); } } $pdo->exec('PRAGMA wal_checkpoint(FULL);'); qdb_save($savePath, $migrateLock); if ($savePath !== $sqlitePath && is_file($savePath)) { @unlink($savePath); } } catch (Throwable $e) { flock($migrateLock, LOCK_UN); fclose($migrateLock); throw $e; } return qdb_read_db_open()[0]; } return $pdo; } /** * @return PDO|null */ function qdb_read_cache_get(string $signature) { $state = &qdb_read_cache_state(); if ($state['signature'] === $signature && $state['pdo'] instanceof PDO) { return $state['pdo']; } return null; } function qdb_read_cache_store(string $signature, PDO $pdo, string $memfdPath, int $memfdFd): void { qdb_read_cache_reset(); $state = &qdb_read_cache_state(); $state['signature'] = $signature; $state['pdo'] = $pdo; $state['memfdPath'] = $memfdPath; $state['memfdFd'] = $memfdFd; } function qdb_read_cache_reset(): void { $state = &qdb_read_cache_state(); $state['signature'] = ''; $state['pdo'] = null; if ($state['memfdFd'] >= 0) { qdb_close_fd($state['memfdFd']); } $state['memfdFd'] = -1; if ($state['memfdPath'] !== '' && is_file($state['memfdPath'])) { @unlink($state['memfdPath']); } $state['memfdPath'] = ''; } /** * @return array{signature: string, pdo: ?PDO, memfdPath: string, memfdFd: int} */ function &qdb_read_cache_state(): array { static $state = [ 'signature' => '', 'pdo' => null, 'memfdPath' => '', 'memfdFd' => -1, ]; return $state; }