PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); } return [$pdo, '', null]; } function qdb_set_foreign_keys(PDO $pdo, bool $enabled): void { if (qdb_is_mysql()) { $pdo->exec('SET FOREIGN_KEY_CHECKS = ' . ($enabled ? '1' : '0')); return; } $pdo->exec('PRAGMA foreign_keys = ' . ($enabled ? 'ON' : 'OFF') . ';'); } function qdb_table_exists(PDO $pdo, string $table): bool { if (qdb_is_mysql()) { $stmt = $pdo->prepare( 'SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :t LIMIT 1' ); $stmt->execute([':t' => $table]); return (bool)$stmt->fetchColumn(); } $stmt = $pdo->prepare( "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = :t LIMIT 1" ); $stmt->execute([':t' => $table]); return (bool)$stmt->fetchColumn(); } function qdb_column_exists(PDO $pdo, string $table, string $column): bool { if (qdb_is_mysql()) { $stmt = $pdo->prepare( 'SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = :t AND column_name = :c LIMIT 1' ); $stmt->execute([':t' => $table, ':c' => $column]); return (bool)$stmt->fetchColumn(); } $stmt = $pdo->query('PRAGMA table_info(' . $table . ')'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { if (($col['name'] ?? '') === $column) { return true; } } return false; } function qdb_schema_version(PDO $pdo): int { if (qdb_is_mysql()) { if (!qdb_table_exists($pdo, 'schema_version')) { return 0; } return (int)$pdo->query('SELECT version FROM schema_version WHERE id = 1')->fetchColumn(); } return (int)$pdo->query('PRAGMA user_version')->fetchColumn(); } function qdb_set_schema_version(PDO $pdo, int $version): void { if (qdb_is_mysql()) { if (!qdb_table_exists($pdo, 'schema_version')) { $pdo->exec( 'CREATE TABLE IF NOT EXISTS schema_version ( id INT NOT NULL PRIMARY KEY, version INT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' ); } $pdo->prepare( 'INSERT INTO schema_version (id, version) VALUES (1, :v) ON DUPLICATE KEY UPDATE version = VALUES(version)' )->execute([':v' => $version]); return; } $pdo->exec('PRAGMA user_version = ' . (int)$version . ';'); } function qdb_stmt_affected_rows(PDO $pdo, PDOStatement $stmt): int { if (qdb_is_mysql()) { return $stmt->rowCount(); } return (int)$pdo->query('SELECT changes()')->fetchColumn(); } function qdb_create_index_if_not_exists(PDO $pdo, string $name, string $table, string $columnsSql): void { if (qdb_is_mysql()) { if (!qdb_table_exists($pdo, $table)) { return; } $stmt = $pdo->prepare( 'SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = :t AND index_name = :i LIMIT 1' ); $stmt->execute([':t' => $table, ':i' => $name]); if ($stmt->fetchColumn()) { return; } $pdo->exec("CREATE INDEX `{$name}` ON `{$table}` ({$columnsSql})"); return; } $pdo->exec("CREATE INDEX IF NOT EXISTS {$name} ON {$table}({$columnsSql})"); } /** * @param list $conflictColumns */ function qdb_upsert_do_nothing(array $conflictColumns): string { if (qdb_is_mysql()) { $first = $conflictColumns[0]; return "ON DUPLICATE KEY UPDATE `{$first}` = `{$first}`"; } return 'ON CONFLICT(' . implode(', ', $conflictColumns) . ') DO NOTHING'; } /** * @param list $conflictColumns * @param array $assignments column => expression (use excluded.col on SQLite) */ function qdb_upsert_update(array $conflictColumns, array $assignments): string { if (qdb_is_mysql()) { $parts = []; foreach ($assignments as $column => $expression) { $mysqlExpr = preg_replace('/\bexcluded\.(\w+)\b/', 'VALUES($1)', $expression) ?? $expression; $parts[] = "`{$column}` = {$mysqlExpr}"; } return 'ON DUPLICATE KEY UPDATE ' . implode(', ', $parts); } $parts = []; foreach ($assignments as $column => $expression) { $parts[] = "{$column} = {$expression}"; } return 'ON CONFLICT(' . implode(', ', $conflictColumns) . ') DO UPDATE SET ' . implode(', ', $parts); } function qdb_exec_schema_file(PDO $pdo, string $schemaPath): void { $sql = file_get_contents($schemaPath); if ($sql === false) { throw new RuntimeException("Could not read schema: {$schemaPath}"); } qdb_exec_schema_sql($pdo, $sql); } function qdb_exec_schema_sql(PDO $pdo, string $sql): void { if (qdb_is_mysql()) { foreach (qdb_split_sql_statements($sql) as $statement) { $trimmed = trim($statement); if ($trimmed === '' || str_starts_with($trimmed, '--')) { continue; } $pdo->exec($trimmed); } return; } $pdo->exec($sql); } /** @return list */ function qdb_split_sql_statements(string $sql): array { $parts = preg_split('/;\s*\n/', $sql) ?: []; return array_values(array_filter(array_map('trim', $parts), fn($s) => $s !== '')); }