Skip to content

SQL Playground — Zig 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 Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// sql-playground — SQL statement splitting, read-only classification, CSV / Markdown serialization (Zig).
const std = @import("std");
// Split on ';' outside '...' literals ('' is an escaped quote); trimmed, non-empty only.
fn splitStatements(alloc: std.mem.Allocator, sql: []const u8) ![][]const u8 {
    var stmts = std.ArrayList([]const u8).init(alloc);
    var current = std.ArrayList(u8).init(alloc);
    var in_string = false;
    var i: usize = 0;
    while (i < sql.len) : (i += 1) {
        const ch = sql[i];
        if (in_string) {
            try current.append(ch);
            if (ch == '\'') {
                if (i + 1 < sql.len and sql[i + 1] == '\'') { try current.append('\''); i += 1; } // '' escape
                else in_string = false;
            }
        } else if (ch == '\'') { in_string = true; try current.append(ch); }
        else if (ch == ';') {
            const t = std.mem.trim(u8, current.items, " \t\r\n");
            if (t.len > 0) try stmts.append(try alloc.dupe(u8, t));
            current.clearRetainingCapacity();
        } else try current.append(ch);
    }
    const t = std.mem.trim(u8, current.items, " \t\r\n");
    if (t.len > 0) try stmts.append(try alloc.dupe(u8, t));
    return stmts.toOwnedSlice();
}
// A leading SELECT / WITH / VALUES / EXPLAIN / PRAGMA marks the statement read-only.
fn isReadOnlyStatement(stmt: []const u8) bool {
    const t = std.mem.trim(u8, stmt, " \t\r\n");
    for ([_][]const u8{ "SELECT", "WITH", "VALUES", "EXPLAIN", "PRAGMA" }) |kw| {
        if (!std.ascii.startsWithIgnoreCase(t, kw)) continue;
        const rest = t[kw.len..];
        if (rest.len == 0 or (!std.ascii.isAlphanumeric(rest[0]) and rest[0] != '_')) return true; // word boundary
    }
    return false;
}
// One cell: CSV mode quotes fields holding , " CR or LF (doubling quotes); Markdown escapes pipes.
fn writeCell(out: *std.ArrayList(u8), cell: ?[]const u8, md: bool) !void {
    const v = cell orelse "NULL"; // a null cell is SQL NULL and renders as that text
    const plain = md or std.mem.indexOfAny(u8, v, ",\"\r\n") == null;
    if (!plain) try out.append('"');
    for (v) |c| { if (!plain and c == '"') try out.append('"'); if (md and c == '|') try out.append('\\'); try out.append(c); }
    if (!plain) try out.append('"');
}
// Header row, then (Markdown only) a --- separator, then one row per record.
fn writeTable(alloc: std.mem.Allocator, columns: []const ?[]const u8, rows: []const []const ?[]const u8, md: bool) ![]u8 {
    var out = std.ArrayList(u8).init(alloc);
    try writeRow(&out, columns, md);
    if (md) {
        try out.appendSlice("|");
        for (columns) |_| try out.appendSlice(" --- |");
        try out.append('\n');
    }
    for (rows) |row| try writeRow(&out, row, md);
    return out.toOwnedSlice();
}
fn writeRow(out: *std.ArrayList(u8), cells: []const ?[]const u8, md: bool) !void {
    if (md) try out.appendSlice("| ");
    for (cells, 0..) |cell, c| { if (c > 0) try out.appendSlice(if (md) " | " else ","); try writeCell(out, cell, md); }
    try out.appendSlice(if (md) " |\n" : "\n");
}
pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();
    const stdout = std.io.getStdOut().writer();
    const script = "CREATE TABLE t (id INT);  SELECT * FROM t WHERE note = 'a''b;c' ; ;DROP TABLE t";
    for (try splitStatements(alloc, script)) |s|
        try stdout.print("[{s}] {s}\n", .{ if (isReadOnlyStatement(s)) "read-only" else "write", s });
    const columns = [_]?[]const u8{ "name", "note" };
    const rows = [_][]const ?[]const u8{ &.{ "O'Brien", "has, \"quotes\"" }, &.{ null, "pipe|inside" } };
    try stdout.print("\nCSV:\n{s}\nMarkdown:\n{s}", .{
        try writeTable(alloc, &columns, &rows, false),
        try writeTable(alloc, &columns, &rows, true),
    });
}

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 →