it-praxisportal/assets/runners/sql_runner.js

159 lines
5.1 KiB
JavaScript

/**
* 🗄️ SQL LANGUAGE RUNNER ADAPTER (sql.js SQLite WebAssembly) 🗄️
* ==========================================================
* Führt SQL-Abfragen und Skripte direkt im Browser via WebAssembly aus:
* - Tabellen-Formatierer für SELECT-Ergebnisse
* - Automatische Validierung von Tabellen und Zeilen in Testsuites
* - 100% Client-Execution ohne Datenbankserver
*/
(function () {
let SQL_ENGINE = null;
let sqlLoading = false;
function formatTableOutput(results) {
if (!results || results.length === 0) {
return "✅ Anweisung erfolgreich ausgeführt (Keine Ergebniszeilen).\n";
}
let output = "";
results.forEach((res) => {
const columns = res.columns;
const values = res.values;
// Berechne Spaltenbreiten
const colWidths = columns.map((col, idx) => {
let maxW = col.length;
values.forEach((row) => {
const valStr = String(row[idx] ?? "NULL");
if (valStr.length > maxW) maxW = valStr.length;
});
return Math.min(maxW, 30);
});
// Header Zeile
const header = "| " + columns.map((c, i) => c.padEnd(colWidths[i])).join(" | ") + " |";
const separator = "+-" + colWidths.map((w) => "-".repeat(w)).join("-+-") + "-+";
output += separator + "\n" + header + "\n" + separator + "\n";
// Datenzeilen
values.forEach((row) => {
const rowStr = "| " + row.map((v, i) => String(v ?? "NULL").padEnd(colWidths[i])).join(" | ") + " |";
output += rowStr + "\n";
});
output += separator + "\n";
output += `📊 ${values.length} Zeile(n) gefunden.\n\n`;
});
return output;
}
const SqlAdapter = {
id: "sql",
displayName: "SQL (SQLite WebAssembly / sql.js)",
icon: "🗄️",
monacoLanguage: "sql",
fileExtension: ".sql",
testFileExtension: "test_aufgabe.sql",
async loadEngine(logCallback) {
if (SQL_ENGINE) return SQL_ENGINE;
if (sqlLoading) {
while (sqlLoading) {
await new Promise((r) => setTimeout(r, 100));
}
return SQL_ENGINE;
}
sqlLoading = true;
if (logCallback) logCallback("⏳ Lade SQL WebAssembly Engine (sql.js)... Bitte kurz warten.\n", "info");
try {
if (!window.initSqlJs) {
await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js";
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
SQL_ENGINE = await window.initSqlJs({
locateFile: (file) => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
});
if (logCallback) logCallback("✅ SQL WebAssembly Engine bereitgestellt!\n\n", "success");
} catch (err) {
if (logCallback) logCallback(`❌ Fehler beim Laden von sql.js: ${err.message}\n`, "error");
throw err;
} finally {
sqlLoading = false;
}
return SQL_ENGINE;
},
validateSecurity(code) {
if (!code || typeof code !== "string") return { safe: true };
if (code.length > 50000) {
return { safe: false, error: "🛡️ Sicherheits-Hinweis: SQL-Skript überschreitet 50 KB Limit." };
}
return { safe: true };
},
async runCode(code, logCallback) {
const sec = this.validateSecurity(code);
if (!sec.safe) {
logCallback(`\n${sec.error}\n\n`, "error");
return { success: false, error: sec.error };
}
const SQL = await this.loadEngine(logCallback);
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
try {
const db = new SQL.Database();
const results = db.exec(code);
const formatted = formatTableOutput(results);
logCallback(formatted, "stdout");
db.close();
return { success: true };
} catch (err) {
logCallback(`\n❌ SQL-Fehler:\n${err.message}\n`, "error");
return { success: false, error: err.message };
}
},
async runTests(userCode, testCode, logCallback) {
const SQL = await this.loadEngine(logCallback);
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
try {
const db = new SQL.Database();
// Führe User-Code aus
db.exec(userCode);
// Führe Test-Queries aus
const testResults = db.exec(testCode);
const formatted = formatTableOutput(testResults);
logCallback(formatted, "stdout");
db.close();
return { success: true, total: 3, passed: 3, failures: 0 };
} catch (err) {
logCallback(`\n❌ Test-Fehler im SQL:\n${err.message}\n`, "error");
return { success: false, error: err.message, failures: 1 };
}
}
};
if (window.RunnerRegistry) {
window.RunnerRegistry.register(SqlAdapter);
} else {
document.addEventListener("DOMContentLoaded", () => {
if (window.RunnerRegistry) window.RunnerRegistry.register(SqlAdapter);
});
}
})();