Skip to content

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 →