getMessage(); return str_contains($msg, 'Could not acquire lock') || str_contains($msg, 'Could not open lock file'); } /** * Map internal exceptions to safe, identifiable API error messages (details go to error_log). */ function qdb_public_error_message(Throwable $e, string $contextLabel = 'Operation'): string { $msg = $e->getMessage(); if (qdb_is_lock_exception($e)) { return 'Database is busy because another update is in progress. Please try again in a moment.'; } if (str_contains($msg, 'decrypt') || str_contains($msg, 'QDB_MASTER_KEY')) { return 'Database configuration error. Contact the server administrator.'; } if (str_contains($msg, 'Could not read') || str_contains($msg, 'Could not write') || str_contains($msg, 'Could not save')) { return 'Database storage error. Contact the server administrator.'; } if ($e instanceof PDOException) { return $contextLabel . ' failed due to a database error.'; } return $contextLabel . ' failed. If this persists, contact support.'; } /** * @return array{0: string, 1: string, 2: int} code, message, http status */ function qdb_api_error_for_exception(Throwable $e, string $contextLabel = 'Operation'): array { if (qdb_is_lock_exception($e)) { return ['LOCKED', qdb_public_error_message($e, $contextLabel), 503]; } return ['SERVER_ERROR', qdb_public_error_message($e, $contextLabel), 500]; } /** * Standard handler catch: rollback, discard DB temp file, log, JSON error (never returns). */ function qdb_handler_fail( Throwable $e, string $contextLabel, ?PDO $pdo = null, ?string $tmpDb = null, $lockFp = null ): never { if (defined('QDB_TESTING') && qdb_test_is_control_flow_exception($e)) { throw $e; } if ($pdo !== null && $pdo->inTransaction()) { try { $pdo->rollBack(); } catch (Throwable) { } } if ($tmpDb !== null && $lockFp !== null) { require_once __DIR__ . '/../db_init.php'; qdb_discard($tmpDb, $lockFp); } qdb_emit_api_error($e, $contextLabel); } /** * Log exception and exit with unified JSON error (never returns). */ function qdb_emit_api_error(Throwable $e, string $contextLabel): never { if (defined('QDB_TESTING') && qdb_test_is_control_flow_exception($e)) { throw $e; } if (function_exists('qdb_api_log_note_exception')) { qdb_api_log_note_exception($e); } error_log($contextLabel . ': ' . $e->getMessage()); [$code, $message, $status] = qdb_api_error_for_exception($e, $contextLabel); json_error($code, $message, $status); }