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 →