159 lines
5.1 KiB
JavaScript
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);
|
|
});
|
|
}
|
|
})();
|