Skip to content

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

// sql-playground — SQL statement splitting, read-only classification, CSV / Markdown serialization (Kotlin).
import java.util.regex.Pattern

private val READ_ONLY = Pattern.compile("^(SELECT|WITH|VALUES|EXPLAIN|PRAGMA)\\b", Pattern.CASE_INSENSITIVE)

// Split on ';' outside '...' literals; a doubled '' inside a string is an escaped quote.
fun splitStatements(sql: String): List<String> {
    val stmts = mutableListOf<String>()
    val current = StringBuilder()
    var inString = false
    var i = 0
    while (i < sql.length) {
        val ch = sql[i]
        when {
            inString -> {
                current.append(ch)
                if (ch == '\'') {
                    if (i + 1 < sql.length && sql[i + 1] == '\'') current.append(sql[++i]) // '' escape
                    else inString = false
                }
            }
            ch == '\'' -> { inString = true; current.append(ch) }
            ch == ';' -> {
                val t = current.toString().trim()
                if (t.isNotEmpty()) stmts += t
                current.setLength(0)
            }
            else -> current.append(ch)
        }
        i++
    }
    val t = current.toString().trim()
    if (t.isNotEmpty()) stmts += t
    return stmts
}

// A leading SELECT / WITH / VALUES / EXPLAIN / PRAGMA marks the statement read-only.
fun isReadOnlyStatement(stmt: String) = READ_ONLY.matcher(stmt.trim()).find()

// RFC-4180-ish CSV field: quote when it holds , " CR or LF; double embedded quotes.
// A null cell is SQL NULL and renders as that text.
private fun csvField(cell: String?): String {
    val v = cell ?: "NULL"
    return if (Regex("[,\"\r\n]").containsMatchIn(v)) "\"${v.replace("\"", "\"\"")}\"" else v
}

fun rowsToCsv(columns: List<String>, rows: List<List<String?>>): String {
    val lines = mutableListOf(columns.joinToString(",") { csvField(it) })
    rows.forEach { row -> lines += row.joinToString(",") { csvField(it) } }
    return lines.joinToString("\n") + "\n"
}

// GitHub-flavored Markdown table; pipes inside cells are backslash-escaped.
fun rowsToMarkdown(columns: List<String>, rows: List<List<String?>>): String {
    fun cell(v: String?) = (v ?: "NULL").replace("|", "\\|")
    val header = "| " + columns.joinToString(" | ") + " |"
    val sep = "| " + columns.joinToString(" | ") { "---" } + " |"
    val body = rows.joinToString("\n") { row -> "| " + row.joinToString(" | ") { cell(it) } + " |" }
    return listOf(header, sep, body).joinToString("\n") + "\n"
}

fun main() {
    val script = "CREATE TABLE t (id INT);  SELECT * FROM t WHERE note = 'a''b;c' ; ;DROP TABLE t"
    for (s in splitStatements(script)) {
        println("[${if (isReadOnlyStatement(s)) "read-only" else "write"}] $s")
    }
    val cols = listOf("name", "note")
    val rows = listOf(listOf("O'Brien", "has, \"quotes\""), listOf(null, "pipe|inside"))
    println()
    print(rowsToCsv(cols, rows))
    print(rowsToMarkdown(cols, rows))
}

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 →