implemented incremental schema migration for questionnaire, added categoryKey column if missing, and improved error handling in questionnaire retrieval
This commit is contained in:
80
db_init.php
80
db_init.php
@ -8,6 +8,54 @@ define('QDB_LOCK', __DIR__ . '/uploads/.qdb_lock');
|
||||
define('QDB_SCHEMA', __DIR__ . '/schema.sql');
|
||||
define('QDB_VERSION', 4);
|
||||
|
||||
function qdb_column_exists(PDO $pdo, string $table, string $column): bool {
|
||||
$stmt = $pdo->query('PRAGMA table_info(' . $table . ')');
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) {
|
||||
if (($col['name'] ?? '') === $column) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply incremental schema fixes. Returns true if the temp DB was modified.
|
||||
*/
|
||||
function qdb_apply_migrations(PDO $pdo, string $schemaSql): bool {
|
||||
$changed = false;
|
||||
|
||||
if (!qdb_column_exists($pdo, 'questionnaire', 'categoryKey')) {
|
||||
$pdo->exec("ALTER TABLE questionnaire ADD COLUMN categoryKey TEXT NOT NULL DEFAULT ''");
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
$currentVersion = (int)$pdo->query('PRAGMA user_version')->fetchColumn();
|
||||
if ($currentVersion < QDB_VERSION) {
|
||||
$pdo->exec('PRAGMA foreign_keys = OFF;');
|
||||
$pdo->exec($schemaSql);
|
||||
if (!qdb_column_exists($pdo, 'questionnaire', 'categoryKey')) {
|
||||
$pdo->exec("ALTER TABLE questionnaire ADD COLUMN categoryKey TEXT NOT NULL DEFAULT ''");
|
||||
}
|
||||
$pdo->exec('PRAGMA user_version = ' . QDB_VERSION . ';');
|
||||
$pdo->exec('PRAGMA foreign_keys = ON;');
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
function qdb_acquire_lock() {
|
||||
$lockFp = fopen(QDB_LOCK, 'c');
|
||||
if ($lockFp === false) {
|
||||
throw new Exception('Could not open lock file');
|
||||
}
|
||||
if (!flock($lockFp, LOCK_EX)) {
|
||||
fclose($lockFp);
|
||||
throw new Exception('Could not acquire lock');
|
||||
}
|
||||
return $lockFp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the master DB file into a temp SQLite file, or create a fresh one
|
||||
* from schema.sql if no DB exists yet.
|
||||
@ -67,26 +115,20 @@ function qdb_open(bool $writable = false): array {
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->exec("PRAGMA foreign_keys = ON;");
|
||||
|
||||
// Schema upgrade: apply CREATE TABLE IF NOT EXISTS for any missing tables
|
||||
$currentVersion = (int)$pdo->query("PRAGMA user_version")->fetchColumn();
|
||||
if ($currentVersion < QDB_VERSION) {
|
||||
$pdo->exec("PRAGMA foreign_keys = OFF;");
|
||||
$pdo->exec($sql);
|
||||
if ($currentVersion < 4) {
|
||||
$cols = $pdo->query("PRAGMA table_info(questionnaire)")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$hasCategory = false;
|
||||
foreach ($cols as $col) {
|
||||
if (($col['name'] ?? '') === 'categoryKey') {
|
||||
$hasCategory = true;
|
||||
break;
|
||||
$schemaChanged = qdb_apply_migrations($pdo, $sql);
|
||||
|
||||
// Read-only opens discard the temp file; persist one-off migrations to the master DB.
|
||||
if ($schemaChanged && !$writable) {
|
||||
$migrateLock = qdb_acquire_lock();
|
||||
try {
|
||||
qdb_save($tmpDb, $migrateLock);
|
||||
} catch (Throwable $e) {
|
||||
@unlink($tmpDb);
|
||||
flock($migrateLock, LOCK_UN);
|
||||
fclose($migrateLock);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
if (!$hasCategory) {
|
||||
$pdo->exec("ALTER TABLE questionnaire ADD COLUMN categoryKey TEXT NOT NULL DEFAULT ''");
|
||||
}
|
||||
}
|
||||
$pdo->exec("PRAGMA user_version = " . QDB_VERSION . ";");
|
||||
$pdo->exec("PRAGMA foreign_keys = ON;");
|
||||
return qdb_open(false);
|
||||
}
|
||||
|
||||
return [$pdo, $tmpDb, $lockFp];
|
||||
|
||||
@ -6,6 +6,7 @@ switch ($method) {
|
||||
|
||||
case 'GET':
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
||||
try {
|
||||
$rows = $pdo->query("
|
||||
SELECT q.questionnaireID, q.name, q.version, q.state,
|
||||
q.orderIndex, q.showPoints, q.conditionJson, q.categoryKey,
|
||||
@ -23,6 +24,11 @@ case 'GET':
|
||||
unset($r);
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_success(['questionnaires' => $rows]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
error_log('questionnaires GET: ' . $e->getMessage());
|
||||
json_error('SERVER_ERROR', 'Server error', 500);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
|
||||
Reference in New Issue
Block a user