Skip to content

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

// sql-playground — SQL statement splitting, read-only classification, CSV / Markdown serialization (Swift).
import Foundation

// Split on ';' outside '...' literals; a doubled '' inside a string is an escaped quote.
func splitStatements(_ sql: String) -> [String] {
    var stmts: [String] = []
    var current = ""
    var inString = false
    let chars = Array(sql)
    var i = 0
    while i < chars.count {
        let ch = chars[i]
        if inString {
            current.append(ch)
            if ch == "'" {
                if i + 1 < chars.count && chars[i + 1] == "'" {
                    current.append("'")
                    i += 1 // '' escape — consume both characters
                } else {
                    inString = false
                }
            }
        } else if ch == "'" {
            inString = true
            current.append(ch)
        } else if ch == ";" {
            let t = current.trimmingCharacters(in: .whitespacesAndNewlines)
            if !t.isEmpty { stmts.append(t) }
            current = ""
        } else {
            current.append(ch)
        }
        i += 1
    }
    let t = current.trimmingCharacters(in: .whitespacesAndNewlines)
    if !t.isEmpty { stmts.append(t) }
    return stmts
}

// A leading SELECT / WITH / VALUES / EXPLAIN / PRAGMA marks the statement read-only.
func isReadOnlyStatement(_ stmt: String) -> Bool {
    let keywords = ["SELECT", "WITH", "VALUES", "EXPLAIN", "PRAGMA"]
    let t = stmt.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
    return keywords.contains { kw in
        guard t.hasPrefix(kw) else { return false }
        guard let next = t.dropFirst(kw.count).first else { return true }
        return !(next.isLetter || next.isNumber || next == "_") // word boundary: "SELECTOR" is not SELECT
    }
}
// RFC-4180-ish CSV field: quote when it holds , " CR or LF; double embedded quotes. nil = SQL NULL.
func csvField(_ cell: String?) -> String {
    guard let v = cell else { return "NULL" }
    if v.rangeOfCharacter(from: CharacterSet(charactersIn: ",\"\r\n")) == nil { return v }
    return "\"" + v.replacingOccurrences(of: "\"", with: "\"\"") + "\""
}

func rowsToCsv(_ columns: [String], _ rows: [[String?]]) -> String {
    let lines = ([columns.map { $0 as String? }] + rows).map { $0.map(csvField).joined(separator: ",") }
    return lines.joined(separator: "\n") + "\n"
}

// GitHub-flavored Markdown table; pipes inside cells are backslash-escaped.
func rowsToMarkdown(_ columns: [String], _ rows: [[String?]]) -> String {
    func cell(_ v: String?) -> String { (v ?? "NULL").replacingOccurrences(of: "|", with: "\\|") }
    let header = "| " + columns.joined(separator: " | ") + " |"
    let sep = "| " + columns.map { _ in "---" }.joined(separator: " | ") + " |"
    let body = rows.map { "| " + $0.map(cell).joined(separator: " | ") + " |" }
    return ([header, sep] + body).joined(separator: "\n") + "\n"
}
let script = "CREATE TABLE t (id INT);  SELECT * FROM t WHERE note = 'a''b;c' ; ;DROP TABLE t"
for s in splitStatements(script) {
    print("[\(isReadOnlyStatement(s) ? "read-only" : "write")] \(s)")
}
let cols = ["name", "note"]
let rows: [[String?]] = [["O'Brien", "has, \"quotes\""], [nil, "pipe|inside"]]
print()
print(rowsToCsv(cols, rows), terminator: "")
print(rowsToMarkdown(cols, rows), terminator: "")

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 →