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 →