Skip to content

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

// sql-playground — SQL statement splitting, read-only classification, CSV / Markdown serialization (C++).
#include <iostream>
#include <regex>
#include <string>
#include <vector>

// Split on ';' outside '...' literals; a doubled '' inside a string is an escaped quote.
std::vector<std::string> splitStatements(const std::string& sql) {
    const auto trim = [](std::string s) {
        size_t b = s.find_first_not_of(" \t\r\n"), e = s.find_last_not_of(" \t\r\n");
        return b == std::string::npos ? "" : s.substr(b, e - b + 1);
    };
    std::vector<std::string> stmts;
    std::string current;
    bool inString = false;
    for (size_t i = 0; i < sql.size(); ++i) {
        const char ch = sql[i];
        if (inString) {
            current += ch;
            if (ch == '\'') {
                if (i + 1 < sql.size() && sql[i + 1] == '\'') current += sql[++i]; // '' escape
                else inString = false;
            }
        } else if (ch == '\'') { inString = true; current += ch; }
        else if (ch == ';') {
            const auto t = trim(current);
            if (!t.empty()) stmts.push_back(t);
            current.clear();
        } else current += ch;
    }
    const auto t = trim(current);
    if (!t.empty()) stmts.push_back(t);
    return stmts;
}

// A leading SELECT / WITH / VALUES / EXPLAIN / PRAGMA marks the statement read-only.
bool isReadOnlyStatement(const std::string& stmt) {
    static const std::regex re(R"(^\s*(SELECT|WITH|VALUES|EXPLAIN|PRAGMA)\b)", std::regex::icase);
    return std::regex_search(stmt, re);
}

using Cells = std::vector<const char*>;  // a nullptr cell is SQL NULL
using Rows = std::vector<Cells>;        // one row of cells per record
// One cell: CSV mode quotes fields holding , " CR or LF (doubling quotes); Markdown escapes pipes.
static void writeCell(std::string& out, const char* cell, bool md) {
    const std::string v = cell ? cell : "NULL";
    const bool plain = md || v.find_first_of(",\"\r\n") == std::string::npos;
    out += !plain ? "\"" : "";
    for (const char c : v) {
        if (!plain && c == '"') out += '"';
        if (md && c == '|') out += '\\';
        out += c;
    }
    out += !plain ? "\"" : "";
}

// Header row, then (Markdown only) a --- separator, then one row per record.
static std::string writeTable(const Cells& columns, const Rows& rows, bool md) { // false = CSV, true = Markdown
    std::string out;
    auto row = [&](const Cells& cells) {
        if (md) out += "| ";
        for (size_t c = 0; c < cells.size(); ++c) {
            out += c > 0 ? (md ? " | " : ",") : "";
            writeCell(out, cells[c], md);
        }
        out += md ? " |\n" : "\n";
    };
    row(columns);
    if (md) { out += "| "; for (size_t c = 0; c < columns.size(); ++c) { out += c > 0 ? " | " : ""; out += "---"; } out += " |\n"; }
    for (const auto& r : rows) row(r);
    return out;
}
int main() {
    const std::string script = "CREATE TABLE t (id INT);  SELECT * FROM t WHERE note = 'a''b;c' ; ;DROP TABLE t";
    for (const auto& s : splitStatements(script))
        std::cout << "[" << (isReadOnlyStatement(s) ? "read-only" : "write") << "] " << s << "\n";
    const Cells cols = {"name", "note"};
    const Rows rows = {{"O'Brien", "has, \"quotes\""}, {nullptr, "pipe|inside"}};
    std::cout << "\nCSV:\n" << writeTable(cols, rows, false) << "\nMarkdown:\n" << writeTable(cols, 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 →