SQL Playground — TypeScript source
Run real SQL on sample datasets - or your own schema - right in the browser. Write queries, see formatted results instantly, and export or share them. Powered by sql.js (SQLite WASM); 100% client-side.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
export function splitStatements(sql: string): string[] {
const stmts: string[] = [];
let current = '';
let inString = false;
for (let i = 0; i < sql.length; i++) {
const ch = sql[i];
if (inString) {
current += ch;
if (ch === "'") {
if (i + 1 < sql.length && sql[i + 1] === "'") {
current += sql[i + 1];
i++;
} else {
inString = false;
}
}
} else if (ch === "'") {
inString = true;
current += ch;
} else if (ch === ';') {
const trimmed = current.trim();
if (trimmed) stmts.push(trimmed);
current = '';
} else {
current += ch;
}
}
const trimmed = current.trim();
if (trimmed) stmts.push(trimmed);
return stmts;
}
export function isReadOnlyStatement(sql: string): boolean {
return /^(SELECT|WITH|VALUES|EXPLAIN|PRAGMA)\b/i.test(sql.trim());
}
export function formatScalar(v: unknown): string {
if (v === null || v === undefined) return 'NULL';
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
if (typeof v === 'string') return v;
return JSON.stringify(v);
}
function csvField(v: unknown): string {
const s = formatScalar(v);
if (/[,"\n\r]/.test(s)) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
}
export function rowsToCsv(columns: string[], rows: unknown[][]): string {
const lines: string[] = [columns.map((c) => csvField(c)).join(',')];
for (const row of rows) {
lines.push(row.map((cell) => csvField(cell)).join(','));
}
return lines.join('\n') + '\n';
}
export function rowsToMarkdown(columns: string[], rows: unknown[][]): string {
const esc = (s: string) => s.replace(/\|/g, '\\|');
const header = '| ' + columns.map(esc).join(' | ') + ' |';
const sep = '| ' + columns.map(() => '---').join(' | ') + ' |';
const dataRows = rows.map(
(row) => '| ' + row.map((cell) => esc(formatScalar(cell))).join(' | ') + ' |',
);
return [header, sep, ...dataRows].join('\n') + '\n';
}
export function rowsToJson(columns: string[], rows: unknown[][]): string {
const arr = rows.map((row) => {
const obj: Record<string, unknown> = {};
columns.forEach((col, i) => {
obj[col] = row[i] ?? null;
});
return obj;
});
return JSON.stringify(arr, null, 2);
}
function extractColumnName(def: string): string | null {
if (!def) return null;
if (/^(PRIMARY\s+KEY|FOREIGN\s+KEY|UNIQUE|CHECK|CONSTRAINT)\b/i.test(def)) {
return null;
}
const m = def.match(/^["'`]?(\w+)["'`]?/);
return m ? m[1].toLowerCase() : null;
}
export function summarizeSchema(
createSql: string,
): { table: string; columns: string[] }[] {
try {
const results: { table: string; columns: string[] }[] = [];
const headerRe =
/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["'`]?(\w+)["'`]?\s*\(/gi;
let match;
while ((match = headerRe.exec(createSql)) !== null) {
const table = match[1].toLowerCase();
const startIdx = match.index + match[0].length;
let depth = 1;
let i = startIdx;
while (i < createSql.length && depth > 0) {
if (createSql[i] === '(') depth++;
else if (createSql[i] === ')') depth--;
i++;
}
if (depth !== 0) continue;
headerRe.lastIndex = i;
const body = createSql.slice(startIdx, i - 1);
const columns: string[] = [];
let colDepth = 0;
let current = '';
for (const ch of body) {
if (ch === '(') colDepth++;
else if (ch === ')') colDepth--;
else if (ch === ',' && colDepth === 0) {
const col = extractColumnName(current.trim());
if (col) columns.push(col);
current = '';
continue;
}
current += ch;
}
const lastCol = extractColumnName(current.trim());
if (lastCol) columns.push(lastCol);
results.push({ table, columns });
}
return results;
} catch {
return [];
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →