Skip to content

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

# sql-playground — SQL statement splitting, read-only classification, CSV / Markdown serialization (Ruby).

READ_ONLY = /\A(SELECT|WITH|VALUES|EXPLAIN|PRAGMA)\b/i

# Split on ';' outside '...' literals; a doubled '' inside a string is an escaped quote.
def split_statements(sql)
  stmts = []
  current = +''
  in_string = false
  i = 0
  while i < sql.length
    ch = sql[i]
    if in_string
      current << ch
      if ch == "'"
        if sql[i + 1] == "'"
          current << sql[i + 1]
          i += 1 # '' escape — consume both characters
        else
          in_string = false
        end
      end
    elsif ch == "'"
      in_string = true
      current << ch
    elsif ch == ';'
      stmts << current.strip unless current.strip.empty?
      current = +''
    else
      current << ch
    end
    i += 1
  end
  stmts << current.strip unless current.strip.empty?
  stmts
end

# A leading SELECT / WITH / VALUES / EXPLAIN / PRAGMA marks the statement read-only.
def read_only?(stmt)
  stmt.strip.match?(READ_ONLY)
end

# RFC-4180-ish CSV field: quote when it holds , " CR or LF; double embedded quotes.
# A nil cell is SQL NULL and renders as that text.
def csv_field(cell)
  v = cell || 'NULL'
  return v unless v.match?(/[,\"\r\n]/) # quote only , " CR LF

  '"' + v.gsub('"', '""') + '"'
end

def rows_to_csv(columns, rows)
  lines = [columns.map { |c| csv_field(c) }.join(',')]
  rows.each { |row| lines << row.map { |cell| csv_field(cell) }.join(',') }
  lines.join("\n") + "\n"
end

# GitHub-flavored Markdown table; pipes inside cells are backslash-escaped.
def rows_to_markdown(columns, rows)
  esc = ->(v) { (v || 'NULL').gsub('|', '\|') }
  lines = [
    "| #{columns.map { |c| esc.call(c) }.join(' | ')} |",
    "| #{columns.map { '---' }.join(' | ')} |"
  ]
  rows.each { |row| lines << "| #{row.map { |cell| esc.call(cell) }.join(' | ')} |" }
  lines.join("\n") + "\n"
end

script = "CREATE TABLE t (id INT);  SELECT * FROM t WHERE note = 'a''b;c' ; ;DROP TABLE t"
split_statements(script).each { |s| puts "[#{read_only?(s) ? 'read-only' : 'write'}] #{s}" }
puts
print rows_to_csv(%w[name note], [['O\'Brien', 'has, "quotes"'], [nil, 'pipe|inside']])
print rows_to_markdown(%w[name note], [['O\'Brien', 'has, "quotes"'], [nil, 'pipe|inside']])

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 →