|
|
3 месяцев назад | |
|---|---|---|
| benchmarks | 4 месяцев назад | |
| bin | 3 месяцев назад | |
| cmd | 4 месяцев назад | |
| pkg | 3 месяцев назад | |
| .gitignore | 4 месяцев назад | |
| Makefile | 3 месяцев назад | |
| README.md | 3 месяцев назад | |
| go.mod | 3 месяцев назад | |
| go.sum | 3 месяцев назад | |
| main.go | 3 месяцев назад | |
| pprof_enabled.go | 4 месяцев назад | |
| sql-92.bnf | 7 месяцев назад |
PizzaSQL is a SQL database engine built from the ground up in Go. It features a hand-written recursive descent parser, SQLite-compatible SQL syntax, and multiple access methods.
PizzaSQL passes 100% of the SQLite SQLLogicTest suite — over 5 million individual SQL tests covering edge cases, type coercion, complex queries, and SQLite compatibility.
psql, any PostgreSQL client library (psycopg2, node-postgres, etc.)PostgreSQL Wire Protocol
# Start server
./pizzasql -kv -pg
# Connect with psql
psql -h localhost -p 5432 -d pizzasql
# Or any PostgreSQL client library
postgresql://localhost:5432/pizzasql
HTTP/JSON API
# Start HTTP server
./pizzasql -kv -http
# Query via REST
curl -X POST http://localhost:8080/query \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT * FROM users WHERE age > ?", "params": [25]}'
CLI/REPL
# Interactive mode with storage
./pizzasql -kv
# Single statement
./pizzasql "SELECT * FROM users LIMIT 10"
Prerequisites:
-kv flag)Build and install:
git clone https://github.com/danfragoso/pizzasql.git
cd pizzasql
make install # installs to /usr/local/bin
# or
make install PREFIX=~/.local # user install, no sudo
Build only:
make build # output: ./bin/pizzasql
Option 1: HTTP API
pizzasql -kv -http
# Listening on http://localhost:8080
Option 2: PostgreSQL wire protocol
pizzasql -kv -pg
# psql -h localhost -p 5432 -d pizzasql
Option 3: Both at once
pizzasql -kv -http -pg
Option 4: Interactive REPL
pizzasql -kv
The -kv flag auto-launches a PizzaKV storage process connected via Unix socket (.pizzakv.sock in the working directory). PizzaSQL writes its runtime state to /tmp/pizzasql/<pid>/runtime.json and cleans up on exit.
Using psql:
psql -h localhost -p 5432 -d pizzasql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
);
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com');
SELECT * FROM users;
Using HTTP API:
curl -X POST http://localhost:8080/query \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT * FROM users"}'
Response:
{
"columns": [
{"name": "id", "type": "INTEGER"},
{"name": "name", "type": "TEXT"},
{"name": "email", "type": "TEXT"}
],
"rows": [
[1, "Alice", "alice@example.com"],
[2, "Bob", "bob@example.com"]
],
"rowsAffected": 0,
"executionTimeMicro": 108
}
PizzaSQL uses a per-instance runtime directory at /tmp/pizzasql/<pid>/ to track process state. Each directory contains a runtime.json with the PizzaSQL and PizzaKV PIDs and connection info.
/tmp/pizzasql/
12345/
runtime.json ← { "pizzasql": { "pid": 12345, ... }, "pizzakv": { "pid": 12346, ... } }
67890/
runtime.json
Multiple instances are supported as long as each runs from a different working directory (each needs its own .db and .pizzakv.sock file). If you try to launch -kv in a directory that already has a .db file and another instance is running, PizzaSQL will refuse and tell you the conflicting PID.
Stale entries (from crashed processes) are cleaned up automatically on the next startup.
Startup prompt — if another live pizzasql instance is detected you'll see:
Warning: 1 pizzasql instance(s) already running:
PID 12345 http=:8080 kv=unix:.pizzakv.sock
Continue anyway? [y/N]
Connecting to an external PizzaKV (without -kv):
pizzasql -kvaddr localhost:8085 -http
Performance on an M2 MacBook Air, 10,000-row table, 200 repetitions.
| Workload | SQLite | PostgreSQL | PizzaSQL |
|---|---|---|---|
| Point lookup by PK | 0.003 ms | 0.092 ms | 15.628 ms |
| Category scan (no index) | 0.666 ms | 0.604 ms | 16.080 ms |
| Value range (no index) | 1.531 ms | 1.116 ms | 17.909 ms |
| COUNT(*) | 0.004 ms | 0.305 ms | 15.651 ms |
| Aggregate by category | 2.419 ms | 1.070 ms | 16.667 ms |
| Top-10 ORDER BY DESC | 0.861 ms | 1.020 ms | 21.735 ms |
| Category scan (indexed) | 0.537 ms | 0.279 ms | 0.108 ms |
| Value range (indexed) | 2.456 ms | 0.828 ms | 17.854 ms |
Raw PizzaKV single-key read: 0.024 ms. Full-table prefix scan (10k rows): 0.791 ms. The dominant cost for full-scan queries is JSON deserialization (~15 ms for 10k rows).
Indexed equality lookups are faster than both SQLite and PostgreSQL because PizzaKV's radix trie resolves the index directly to rowids with no B-tree traversal overhead.
# Requires PizzaSQL running at :8080 and PostgreSQL at :5432
go run ./cmd/bench/
# Include raw KV benchmark (find the KV port in /tmp/pizzasql/<pid>/runtime.json)
go run ./cmd/bench/ -kvaddr localhost:<port>
PizzaSQL implements the PostgreSQL wire protocol, allowing you to use any PostgreSQL client with SQLite-compatible SQL syntax.
Python (psycopg2):
import psycopg2
conn = psycopg2.connect(host="localhost", port=5432, database="pizzasql")
cur = conn.cursor()
cur.execute("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
cur.execute("INSERT INTO users (name) VALUES (%s)", ("Alice",))
cur.execute("SELECT * FROM users")
print(cur.fetchall())
conn.commit()
conn.close()
Node.js (node-postgres):
const { Client } = require('pg');
const client = new Client({ host: 'localhost', port: 5432, database: 'pizzasql' });
await client.connect();
await client.query("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)");
await client.query("INSERT INTO users (name) VALUES ($1)", ['Alice']);
const res = await client.query("SELECT * FROM users");
console.log(res.rows);
await client.end();
Go (lib/pq):
db, _ := sql.Open("postgres", "host=localhost port=5432 dbname=pizzasql sslmode=disable")
db.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
db.Exec("INSERT INTO users (name) VALUES ($1)", "Alice")
rows, _ := db.Query("SELECT * FROM users")
# Interactive
psql -h localhost -p 5432 -d pizzasql
# Single command
psql -h localhost -p 5432 -d pizzasql -c "SELECT * FROM users"
# SQL file
psql -h localhost -p 5432 -d pizzasql -f schema.sql
# pgcli
pgcli postgresql://localhost:5432/pizzasql
DBeaver / DataGrip / pgAdmin: connection type PostgreSQL, host localhost, port 5432, database pizzasql, no credentials.
INTEGER PRIMARY KEY AUTOINCREMENT, not SERIALTEXT, not VARCHAR with enforced length$1, $2... (PostgreSQL style) or ? (SQLite style)ARRAY, JSONB, etc.graph TD
Client[Client Applications<br/>CLI · HTTP API · PostgreSQL Protocol]
Client --> Lexer
subgraph PizzaSQL Core
Lexer[Lexer - SQL Tokenizer<br/>227,000 ops/sec]
Parser[Parser - AST Builder<br/>176,000 statements/sec]
Analyzer[Analyzer - Semantic Analysis<br/>Type checking · Scope resolution]
Executor[Executor - Query Engine<br/>Index optimization · Transactions]
Lexer --> Parser --> Analyzer --> Executor
end
Executor --> Storage[PizzaKV Storage<br/>Unix socket · Radix trie · Persistent]
style Client fill:#e1f5ff,stroke:#0288d1,stroke-width:2px
style PizzaSQL Core fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Storage fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
# Storage
-kv Launch PizzaKV automatically (Unix socket)
-kvaddr string Connect to existing PizzaKV (e.g. localhost:8085)
-kvflags string Extra flags forwarded to pizzakv (e.g. "-iwal")
-db string Database name (default "pizzasql")
-pool int KV connection pool size (default 5)
# HTTP server
-http Enable HTTP server
-http-host string Host (default "localhost")
-http-port int Port (default 8080)
-http-cors Enable CORS headers (default true)
-http-compression Enable gzip compression (default true)
-http-auth Enable API key authentication
-api-keys string Comma-separated API keys
# PostgreSQL wire protocol
-pg Enable PostgreSQL server
-pg-host string Host (default "localhost")
-pg-port int Port (default 5432)
# Export / Import
-o string Output file (export)
-i string Input file (import; .db/.sqlite/.sqlite3 auto-imports from SQLite)
-table string Table name (required for CSV)
-format string Format: sql, csv, sqlite (auto-detected from extension)
-drop Include DROP TABLE in SQL export
-create-table Create table from CSV schema on import
-ignore-errors Continue import on row/table errors
# Misc
-quiet Suppress request/query logging
| Method | Path | Description |
|---|---|---|
| POST | /query |
Execute SQL, return rows |
| POST | /execute |
Batch statements |
| GET | /schema/tables |
List all tables |
| GET | /schema/tables/{name} |
Table schema |
| GET | /health |
Health check |
| GET | /stats |
Runtime statistics |
| GET | /metrics |
Prometheus metrics |
| POST | /transaction/begin |
Begin transaction |
| POST | /transaction/commit |
Commit |
| POST | /transaction/rollback |
Rollback |
Multi-database: pass X-Database: <name> header to route queries to a specific database. Databases are created on first access.
# Export full database
pizzasql -db mydb -o backup.sql
# Export with DROP TABLE
pizzasql -db mydb -o backup.sql -drop
# Export single table
pizzasql -db mydb -table users -o users.sql
# Export to CSV
pizzasql -db mydb -table users -o users.csv
# Import SQL
pizzasql -db mydb -i backup.sql
# Import CSV (create table from header)
pizzasql -db mydb -table users -i users.csv -create-table
.db importPizzaSQL can import a SQLite database file directly. Tables, indexes, and row data are all imported. Pragmas, views, and triggers are skipped.
CLI — auto-detected from .db / .sqlite / .sqlite3 extension:
pizzasql -kv -db mydb -i source.db
Keep going on errors (e.g. duplicate rows or unsupported DDL):
pizzasql -kv -db mydb -i source.db -ignore-errors
Insert into an existing database (skip CREATE TABLE, only insert rows):
# Not yet exposed as a CLI flag — use the HTTP API's create_tables=false parameter
HTTP API — multipart upload:
curl -X POST http://localhost:8080/import \
-H "X-Database: mydb" \
-F "file=@source.db"
HTTP API — raw body with explicit format:
curl -X POST "http://localhost:8080/import?format=sqlite" \
-H "X-Database: mydb" \
-H "Content-Type: application/octet-stream" \
--data-binary @source.db
HTTP API options:
| Query param | Default | Description |
|---|---|---|
format |
auto | sqlite forces binary SQLite mode |
create_tables |
true |
false skips CREATE TABLE, only inserts rows |
ignore_errors |
false |
Continue past individual row/table errors |
Response:
{
"tablesCreated": ["users", "albums", "tracks"],
"tablesImported": ["users", "albums", "tracks"],
"rowsInserted": 27754,
"indexesCreated": 61,
"errors": []
}
What gets imported:
CREATE INDEX)What is silently skipped:
CREATE INDEX ON t(COALESCE(a, b)))FOREIGN KEY / CHECK constraints (schema is imported without them)AUTOINCREMENT keyword (not needed — PizzaSQL handles PK generation)Supported file detection (format auto-selection in order):
?format=sqlite query param.db, .sqlite, .sqlite3application/x-sqlite3 or application/octet-streamSQLite format 3Data Types: INTEGER (INT, BIGINT, BOOLEAN) · REAL (FLOAT, DOUBLE, DECIMAL) · TEXT (VARCHAR, CHAR) · BLOB · NUMERIC
Joins: INNER · LEFT · RIGHT · FULL OUTER · CROSS
Aggregates: COUNT · COUNT(DISTINCT) · SUM · AVG · MIN · MAX
Functions: UPPER, LOWER, LENGTH, SUBSTR, TRIM, REPLACE, CONCAT, ABS, ROUND, CEIL, FLOOR, MOD, COALESCE, NULLIF, IFNULL, printf, hex, random, glob, instr, zeroblob
Transactions:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE name = 'Alice';
UPDATE accounts SET balance = balance + 100 WHERE name = 'Bob';
COMMIT;
Indexes:
CREATE INDEX idx_users_email ON users(email);
CREATE UNIQUE INDEX idx_users_email ON users(email);
DROP INDEX idx_users_email;
PRAGMA:
PRAGMA table_info(users);
PRAGMA table_list;
PRAGMA database_list;
make test # all tests
make test-v # verbose
make test-cover # coverage report → coverage.html
make test-race # race detector
make bench # benchmarks
# Component tests
go test ./pkg/lexer/...
go test ./pkg/parser/...
go test ./pkg/executor/...
# Run against a live server
make sqllogictest URL=http://localhost:8080
# Quick smoke test
make sqllogictest-basic
# Download full SQLite corpus (~5M tests)
make sqllogictest-download
make sqllogictest