TinySQL Developer Integration Guide
May 10, 2026 ยท View on GitHub
This guide explains how to embed TinySQL in three common setups:
- Native Go applications
- Browser/WASM applications
- Custom web frontends that talk to a WASM-backed TinySQL runtime
It is based on the repo examples in example_test.go, import_example_test.go, cmd/demo, cmd/query_files_wasm, and cmd/wasm_browser.
Deutsch
1. Ueberblick
TinySQL kann auf drei Ebenen integriert werden:
- Direkt in Go: Du arbeitest mit dem Paket
github.com/SimonWaldherr/tinySQLund rufst Parser, Ausfuehrung und Importfunktionen direkt auf. - Ueber
database/sql: Wenn du bereits ein SQL-typisches Projekt hast, importiere das oeffentliche Paketgithub.com/SimonWaldherr/tinySQL/driverund sprich TinySQL wie eine Datenbank per DSN an. - Im Browser via WASM: Der Build
cmd/query_files_wasmzeigt, wie TinySQL als WebAssembly-Modul laeuft und aus JavaScript angesprochen wird.
Die Beispiele in diesem Repository sind bewusst unterschiedlich aufgebaut:
example_test.gozeigt die direkte Go-Nutzung mitNewDB,NewParserundExecute.import_example_test.gozeigt CSV-, JSON-, Datei- und Auto-Importe.cmd/demo/main.gozeigt die Nutzung ueberdatabase/sql.cmd/query_files_wasm/main.goundcmd/query_files_wasm/app.jszeigen die Browser-Integration.example_showcase.sqlzeigt typische SQL-Funktionen, die TinySQL bereits abdeckt.
2. Integration in Go-Projekte
Direktes API-Embedding
Wenn du TinySQL als Engine in deinem Go-Projekt verwenden willst, ist das die direkteste Variante:
package main
import (
"context"
"fmt"
tinysql "github.com/SimonWaldherr/tinySQL"
)
func main() {
db := tinysql.NewDB()
stmt, err := tinysql.ParseSQL(`CREATE TABLE users (id INT, name TEXT)`)
if err != nil {
panic(err)
}
_, err = tinysql.Execute(context.Background(), db, "default", stmt)
if err != nil {
panic(err)
}
stmt, _ = tinysql.ParseSQL(`INSERT INTO users VALUES (1, 'Alice')`)
_, _ = tinysql.Execute(context.Background(), db, "default", stmt)
stmt, _ = tinysql.ParseSQL(`SELECT id, name FROM users`)
rs, err := tinysql.Execute(context.Background(), db, "default", stmt)
if err != nil {
panic(err)
}
for _, row := range rs.Rows {
id, _ := tinysql.GetVal(row, "id")
name, _ := tinysql.GetVal(row, "name")
fmt.Println(id, name)
}
}
Wichtige Punkte:
tinysql.NewDB()erzeugt eine neue In-Memory-Datenbank.- Der Tenant-Name ist im Beispiel
default. Das ist auch der Standard in den Browser- und CLI-Beispielen. tinysql.ParseSQL(...)undtinysql.Execute(...)sind die niedrigste gemeinsame Ebene, wenn du volle Kontrolle willst.- Wenn du mehrere Statements ausfuehren willst, kannst du den SQL-Text vorher splitten oder die vorhandenen Helfer aus dem WASM-Tool als Vorbild nehmen.
database/sql verwenden
Wenn du schon mit database/sql arbeitest, ist das oeffentliche Paket github.com/SimonWaldherr/tinySQL/driver die sauberste Integration.
Wichtig fuer Go-Projekte: internal/ ist ein Sprachfeature. Pakete unter internal/ duerfen nur aus demselben Modul importiert werden. Deshalb koennen die Beispiele unter cmd/ zwar github.com/SimonWaldherr/tinySQL/internal/driver nutzen, externe Tools und Anwendungen aber nicht. Fuer andere Module ist github.com/SimonWaldherr/tinySQL/driver der stabile Einstiegspunkt.
package main
import (
"context"
"fmt"
"time"
tsqldriver "github.com/SimonWaldherr/tinySQL/driver"
)
func main() {
cfg := tsqldriver.DefaultOpenConfig()
cfg.Tenant = "default"
cfg.BusyTimeout = 500 * time.Millisecond
db, err := tsqldriver.OpenWithConfig(context.Background(), cfg)
if err != nil {
panic(err)
}
defer db.Close()
_, _ = db.Exec(`CREATE TABLE users (id INT, name TEXT)`)
_, _ = db.Exec(`INSERT INTO users VALUES (?, ?)`, 1, "Alice")
row := db.QueryRow(`SELECT name FROM users WHERE id = ?`, 1)
var name string
if err := row.Scan(&name); err != nil {
panic(err)
}
fmt.Println(name)
}
DSN-Muster aus dem Repo:
- In-Memory:
mem://?tenant=default - Datei-basiert:
file:/pfad/zur/db.dat?tenant=default&autosave=1 - Mit Pooling/Busy-Timeout:
mem://?tenant=default&pool_readers=4&pool_writers=1&busy_timeout=250ms
Nuetzliche Helfer aus dem oeffentlichen Driver-Paket:
driver.Open(dsn)fuer den direktendatabase/sql-Einstiegdriver.DefaultOpenConfig()+driver.OpenWithConfig(ctx, cfg)fuer klar getrennte Settings (DSN, Pooling, Ping-Timeout)driver.OpenInMemory("default")fuer kurzlebige Tests oder Werkzeugedriver.OpenFile("/pfad/zur/db.dat")fuer dateibasierte Tools
Best Practice fuer Timeouts und Settings:
- DSN fuer tinySQL-spezifische Optionen (
tenant,autosave,pool_readers,pool_writers,busy_timeout) database/sqlfuer Pool-Parameter (MaxOpenConns,MaxIdleConns,ConnMaxLifetime,ConnMaxIdleTime)- pro Anfrage/Query immer
context.WithTimeout(...)+ExecContext/QueryContext/PingContextnutzen
Eigene Werkzeuge und Erweiterungen bauen
Wenn du ein eigenes Tool auf tinySQL aufsetzt, halte deine Imports auf der oeffentlichen API:
github.com/SimonWaldherr/tinySQLfuer Engine, Parser, Importer und stabile Typengithub.com/SimonWaldherr/tinySQL/driverfuerdatabase/sql
Die Root-API re-exportiert bewusst wichtige Typen aus internen Paketen, damit andere Projekte keine internal/...-Imports brauchen. Wenn dein Tool eigene tabellenwertige Funktionen bereitstellen soll, nutze tinysql.RegisterExternalTableFunc(...) als Erweiterungspunkt statt direkt gegen internal/engine zu entwickeln.
Dateien importieren
Die Import-Helfer sind ideal, wenn deine Anwendung CSV, JSON oder XML nach tinySQL laden soll. Die Beispiele in import_example_test.go sind die beste Referenz.
result, err := tinysql.ImportCSV(ctx, db, "default", "users",
strings.NewReader(csvData), &tinysql.ImportOptions{
CreateTable: true,
TypeInference: true,
HeaderMode: "present",
})
Typische Helfer:
ImportCSV(...)ImportJSON(...)ImportFile(...)OpenFile(...)
Empfehlungen:
- Nutze
CreateTable: true, wenn du aus externen Dateien direkt Tabellen erzeugen willst. - Nutze
TypeInference: true, wenn Spalten automatisch typisiert werden sollen. - Nutze
HeaderMode: "present", wenn das Format garantiert Kopfzeilen hat. - Nutze die Options aus den Beispielen als Startpunkt, statt die Importlogik neu zu schreiben.
3. Integration in WASM-Projekte
Der Browser-Build cmd/query_files_wasm zeigt das komplette Muster:
- Go wird mit
GOOS=js GOARCH=wasmkompiliert. wasm_exec.jswird mit ausgeliefert.- Eine kleine HTML/JS-App laedt das WASM-Modul.
- TinySQL exportiert globale Funktionen an
window.
Build:
cd cmd/query_files_wasm
./build.sh --build-only
Oder lokal mit Server:
cd cmd/query_files_wasm
./build.sh --serve
Wichtig: Das Modul muss ueber HTTP oder HTTPS laufen. file:// funktioniert fuer Browser-WASM in der Regel nicht sauber, weil fetch() und MIME-Typen benoetigt werden.
Exportierte JS-Funktionen
cmd/query_files_wasm/main.go bindet diese Funktionen an window:
importFile(fileName, fileContent, tableName)executeQuery(sql)executeMulti(sql)clearDatabase()dropTable(tableName)listTables()exportResults(format)getTableSchema(tableName)
Die Rueckgaben sind JSON-objektartige Strukturen. Beispiele aus dem Code:
- Erfolg bei Query:
success,columns,rows,durationMs, optionalstatementsRun - Fehler:
success: false,error - Import:
tableName,rowsImported,rowsSkipped,columns,warnings,delimiter,hadHeader - Export:
data,mimeType,ext
Minimaler Browser-Start
<script src="wasm_exec.js"></script>
<script>
async function bootTinySQL() {
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch("query_files.wasm"),
go.importObject
);
go.run(result.instance);
const queryResult = window.executeQuery("SELECT 1 AS one");
console.log(queryResult);
}
bootTinySQL();
</script>
Praktisches Frontend-Muster
Die UI in cmd/query_files_wasm/app.js nutzt denselben Ablauf wie ein eigenes Projekt:
- Erst warten, bis die WASM-Funktionen bereit sind.
- Dann Datei-Uploads als JSON- oder Textinhalt an
importFile(...)uebergeben. - Fuer SQL mit mehreren Statements
executeMulti(...)verwenden. - Fuer Schema-Ansichten
getTableSchema(...)aufrufen. - Fuer Export-Buttons
exportResults("csv" | "json" | "xml")verwenden.
Das Load Demo + Large Tables-Beispiel in der UI zeigt ausserdem einen guten Integrationsfall fuer groessere Datensaetze: erst Demo-Tabellen laden, dann eine Abfrage ausfuehren, die JOINs, Aggregationen und Gruppierungen kombiniert.
4. Integration in eigene Web-Frontends
Wenn du ein eigenes React-, Vue-, Svelte- oder Vanilla-JS-Frontend baust, ist die Architektur einfach:
- Baue ein kleines WASM-Init-Modul.
- Speichere die exportierten Funktionen in einem Wrapper-Objekt.
- Trenne UI-State und DB-State klar.
- Reagiere auf Ladezustand, Fehler und leere Resultate.
Empfohlene Struktur:
src/
wasm/
boot.js
components/
QueryEditor.tsx
ResultTable.tsx
services/
tinySqlClient.ts
Ein robuster Wrapper kann so aussehen:
export async function initTinySql() {
const go = new Go();
const wasm = await WebAssembly.instantiateStreaming(
fetch("/tinySQL.wasm"),
go.importObject
);
go.run(wasm.instance);
return {
execute: (sql) => window.executeQuery(sql),
executeMulti: (sql) => window.executeMulti(sql),
importFile: (fileName, content, tableName) =>
window.importFile(fileName, content, tableName),
exportResults: (format) => window.exportResults(format),
schema: (tableName) => window.getTableSchema(tableName)
};
}
Was du im Frontend beachten solltest
- Warte mit UI-Aktionen, bis die WASM-Initialisierung abgeschlossen ist.
- Behandle grosse Ergebnisse bewusst, weil alles im Browser-Speicher liegt.
- Nutze
executeMulti, wenn dein Editor mehrere Statements erlauben soll. - Wenn du CSV/JSON/XML hochlaedst, kannst du die Browser-Datei zuerst lesen und den Text an
importFile(...)weiterreichen. - In der referenzierten UI werden Excel-Dateien im Browser mit SheetJS gelesen und pro Sheet als JSON importiert.
5. Was die Beispiele im Repo zeigen
example_showcase.sql zeigt, dass TinySQL im Projekt fuer mehr als nur einfache SELECTs genutzt wird:
- Datumsfunktionen wie
NOW,DATE_TRUNC,EOMONTH - Stringfunktionen wie
UPPER,LOWER,LENGTH,SPLIT - Regex- und Array-Funktionen
- JOINs, GROUP BY, HAVING und Temp Tables
- JSON-Ausdruecke und Aktualisierungen
Das ist wichtig fuer Entwickler: TinySQL ist nicht nur eine Lern-Demo, sondern kann als kleiner Embedded-SQL-Kern fuer realistische Arbeitsablaeufe dienen.
6. Praktische Grenzen und Empfehlungen
- Die WASM-Beispiele begrenzen SQL-Text auf eine groessere, aber endliche Maximalgroesse. Im Browser-Code liegt sie bei 256 KiB.
- Query-Timeouts sind im Browser-Tool standardmaessig aktiv.
- In Browser-Setups gibt es standardmaessig keine Dateisystem-Persistenz; wenn du Persistenz brauchst, musst du sie im Frontend oder im Go-Host selbst umsetzen.
- Fuehre echte Integrations-Tests gegen die jeweiligen Einstiegspunkte aus: Go-API,
database/sql, WASM-API und UI.
English
1. Overview
TinySQL can be integrated at three levels:
- Directly in Go: use the package
github.com/SimonWaldherr/tinySQLand call the parser, execution, and import helpers directly. - Through
database/sql: if your project already uses SQL-style APIs, import the public packagegithub.com/SimonWaldherr/tinySQL/driverand access TinySQL through a DSN. - In the browser via WASM:
cmd/query_files_wasmshows how TinySQL runs as a WebAssembly module and is controlled from JavaScript.
The repository examples are intentionally complementary:
example_test.goshows direct Go usage withNewDB,NewParser, andExecute.import_example_test.goshows CSV, JSON, file, and auto-detection imports.cmd/demo/main.goshows thedatabase/sqlintegration.cmd/query_files_wasm/main.goandcmd/query_files_wasm/app.jsshow browser integration.example_showcase.sqldemonstrates the SQL surface TinySQL already supports.
2. Integrating TinySQL into Go projects
Direct API embedding
Use this when you want full control over parsing and execution:
package main
import (
"context"
"fmt"
tinysql "github.com/SimonWaldherr/tinySQL"
)
func main() {
db := tinysql.NewDB()
stmt, err := tinysql.ParseSQL(`CREATE TABLE users (id INT, name TEXT)`)
if err != nil {
panic(err)
}
_, err = tinysql.Execute(context.Background(), db, "default", stmt)
if err != nil {
panic(err)
}
stmt, _ = tinysql.ParseSQL(`INSERT INTO users VALUES (1, 'Alice')`)
_, _ = tinysql.Execute(context.Background(), db, "default", stmt)
stmt, _ = tinysql.ParseSQL(`SELECT id, name FROM users`)
rs, err := tinysql.Execute(context.Background(), db, "default", stmt)
if err != nil {
panic(err)
}
for _, row := range rs.Rows {
id, _ := tinysql.GetVal(row, "id")
name, _ := tinysql.GetVal(row, "name")
fmt.Println(id, name)
}
}
Key points:
tinysql.NewDB()creates a fresh in-memory database.- The tenant name in the examples is
default, which is also the standard tenant in the browser and CLI code. tinysql.ParseSQL(...)andtinysql.Execute(...)are the lowest-level primitives if you want exact control.
Using database/sql
If your code already expects a database/sql handle, the public package github.com/SimonWaldherr/tinySQL/driver is usually the cleanest route.
Important Go detail: internal/ is one of Go's package visibility rules. Anything below internal/ can only be imported from within the same module tree. That is why the repository's own commands can use github.com/SimonWaldherr/tinySQL/internal/driver, while external tools cannot. For other modules, github.com/SimonWaldherr/tinySQL/driver is the supported entry point.
package main
import (
"database/sql"
"fmt"
tsqldriver "github.com/SimonWaldherr/tinySQL/driver"
)
func main() {
db, err := sql.Open(tsqldriver.DriverName, "mem://?tenant=default")
if err != nil {
panic(err)
}
defer db.Close()
_, _ = db.Exec(`CREATE TABLE users (id INT, name TEXT)`)
_, _ = db.Exec(`INSERT INTO users VALUES (?, ?)`, 1, "Alice")
row := db.QueryRow(`SELECT name FROM users WHERE id = ?`, 1)
var name string
if err := row.Scan(&name); err != nil {
panic(err)
}
fmt.Println(name)
}
DSN patterns from the repo:
- In-memory:
mem://?tenant=default - File-backed:
file:/path/to/db.dat?tenant=default&autosave=1
Useful helpers from the public driver package:
driver.Open(dsn)for directdatabase/sqlintegrationdriver.OpenInMemory("default")for tests and short-lived toolsdriver.OpenFile("/path/to/db.dat")for file-backed tools
Building your own tools and extensions
When you build tooling on top of tinySQL, keep your imports on the public surface:
github.com/SimonWaldherr/tinySQLfor the engine, parser, importers, and stable re-exported typesgithub.com/SimonWaldherr/tinySQL/driverfordatabase/sql
The root package deliberately re-exports the important types from internal packages so external projects do not need internal/... imports. If your tool needs custom table-valued functions, use tinysql.RegisterExternalTableFunc(...) instead of depending on internal/engine.
Importing files
Use the import helpers when your application needs to load CSV, JSON, or XML into TinySQL. The examples in import_example_test.go are the best reference.
result, err := tinysql.ImportCSV(ctx, db, "default", "users",
strings.NewReader(csvData), &tinysql.ImportOptions{
CreateTable: true,
TypeInference: true,
HeaderMode: "present",
})
Typical helpers:
ImportCSV(...)ImportJSON(...)ImportFile(...)OpenFile(...)
Recommended defaults:
CreateTable: truewhen you want tables created automatically.TypeInference: truewhen you want automatic column typing.HeaderMode: "present"when the input format definitely has a header row.
3. Integrating TinySQL into WASM projects
The browser build in cmd/query_files_wasm demonstrates the full pattern:
- Compile Go with
GOOS=js GOARCH=wasm. - Ship
wasm_exec.jswith the page. - Load the WASM module from a small HTML/JS app.
- Expose TinySQL functions on
window.
Build:
cd cmd/query_files_wasm
./build.sh --build-only
Or serve locally:
cd cmd/query_files_wasm
./build.sh --serve
The module should be served over HTTP or HTTPS. Browsers generally do not handle WASM initialization reliably from file:// URLs because fetch() and MIME types matter.
Exported JS functions
cmd/query_files_wasm/main.go binds these globals on window:
importFile(fileName, fileContent, tableName)executeQuery(sql)executeMulti(sql)clearDatabase()dropTable(tableName)listTables()exportResults(format)getTableSchema(tableName)
The returned values are JSON-like objects. From the code you can expect:
- Query success:
success,columns,rows,durationMs, optionalstatementsRun - Failure:
success: false,error - Import:
tableName,rowsImported,rowsSkipped,columns,warnings,delimiter,hadHeader - Export:
data,mimeType,ext
Minimal browser bootstrap
<script src="wasm_exec.js"></script>
<script>
async function bootTinySQL() {
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch("query_files.wasm"),
go.importObject
);
go.run(result.instance);
const queryResult = window.executeQuery("SELECT 1 AS one");
console.log(queryResult);
}
bootTinySQL();
</script>
Practical frontend pattern
The UI in cmd/query_files_wasm/app.js follows the same flow you would use in your own application:
- Wait for WASM initialization before enabling the editor.
- Send uploaded file contents to
importFile(...). - Use
executeMulti(...)when the SQL text contains multiple statements. - Call
getTableSchema(...)for schema panels or metadata views. - Use
exportResults("csv" | "json" | "xml")for download buttons.
The Load Demo + Large Tables button is a good integration example for larger datasets: seed tables first, then run a query that combines joins, grouping, and aggregation.
4. Integrating TinySQL into custom web frontends
If you build a React, Vue, Svelte, or vanilla JS frontend, the architecture stays simple:
- Create a small WASM bootstrap module.
- Wrap the exported functions in a client object.
- Keep UI state and database state separate.
- Handle loading, errors, and empty results explicitly.
Suggested structure:
src/
wasm/
boot.js
components/
QueryEditor.tsx
ResultTable.tsx
services/
tinySqlClient.ts
A robust client wrapper can look like this:
export async function initTinySql() {
const go = new Go();
const wasm = await WebAssembly.instantiateStreaming(
fetch("/tinySQL.wasm"),
go.importObject
);
go.run(wasm.instance);
return {
execute: (sql) => window.executeQuery(sql),
executeMulti: (sql) => window.executeMulti(sql),
importFile: (fileName, content, tableName) =>
window.importFile(fileName, content, tableName),
exportResults: (format) => window.exportResults(format),
schema: (tableName) => window.getTableSchema(tableName)
};
}
Frontend considerations
- Do not enable query actions before WASM is ready.
- Treat large result sets carefully because everything lives in browser memory.
- Use
executeMultiif your editor accepts multiple SQL statements. - For CSV/JSON/XML uploads, read the file in the browser and pass the text to
importFile(...). - In the reference UI, Excel files are read with SheetJS and each sheet is imported as JSON.
5. What the repository examples demonstrate
example_showcase.sql shows that TinySQL already covers much more than simple SELECT queries:
- date and time functions such as
NOW,DATE_TRUNC, andEOMONTH - string functions such as
UPPER,LOWER,LENGTH, andSPLIT - regex and array helpers
- joins, grouping,
HAVING, and temp tables - JSON expressions and updates
This matters for developers: TinySQL is not just a teaching demo; it can serve as a compact embedded SQL core for realistic workflows.
6. Practical limits and recommendations
- The WASM examples keep SQL input bounded; in
cmd/query_files_wasmthe limit is 256 KiB. - Query timeouts are enabled by default in the browser tool.
- Browser setups do not provide persistence by default; if you need persistence, implement it in the frontend or host Go process.
- Test against all relevant entry points: Go API,
database/sql, WASM API, and UI.
Summary
For Go-only apps, prefer the direct package API or the database/sql driver. For browser apps, follow the cmd/query_files_wasm pattern: compile to WASM, expose a small JS API, then let your frontend own the UI state while TinySQL owns the data and query execution.