SQLite Driver

Luma includes a built-in SQLite driver that reads SQLite3 database files directly using random-access file I/O and byte manipulation. Like the MySQL driver, it is written entirely in Luma with no external dependencies.

The SQLite driver is read-only. It supports SELECT queries with WHERE and LIMIT clauses. Write operations (INSERT, UPDATE, DELETE, CREATE) will panic with a clear error message.

Quick Start

sqlite = import "sqlite"

conn = db.open(sqlite, "/path/to/database.db")

rows = conn.query("SELECT name, age FROM users WHERE age > 20")
rows.walk(row) -> {
    name: str = row.get("name")
    age: int = row.get("age")
    print("${name}: ${age}")
}

conn.close()

DSN Format

The DSN is simply a file path to an existing SQLite database file:

conn = db.open(sqlite, "/tmp/mydata.db")
conn = db.open(sqlite, "data/local.db")

Supported SQL

SELECT *

rows = conn.query("SELECT * FROM users")

Column Projection

rows = conn.query("SELECT name, score FROM users")

WHERE Clause

Supports =, !=, >, <, >=, <= operators with AND for combining conditions:

rows = conn.query("SELECT * FROM users WHERE age > 28")
rows = conn.query("SELECT * FROM users WHERE name = 'Alice'")
rows = conn.query("SELECT * FROM users WHERE age >= 25 AND score > 90")

Numeric comparisons work with both integers and floats.

LIMIT

rows = conn.query("SELECT * FROM users LIMIT 10")

Combining Clauses

rows = conn.query("SELECT name, score FROM users WHERE age > 25 LIMIT 5")

Transactions

Transaction stubs are supported as no-ops for compatibility:

conn.beginTransaction()    // no-op
conn.commit()              // no-op
conn.rollback()            // no-op

Limitations

Limitation Details
Read-only No INSERT, UPDATE, DELETE, or CREATE support
UTF-8 only Panics on UTF-16 encoded databases
No WITHOUT ROWID tables These use a different B-tree structure
No index usage Always does full table scan
Basic SQL only No JOIN, GROUP BY, ORDER BY, subqueries, or aggregates
No WAL mode Only reads rollback-journal mode databases
No named parameters Use string interpolation for dynamic values

Supported Data Types

The driver reads all SQLite serial types and converts them to strings (matching the driver contract):

SQLite Type Serial Types Conversion
NULL 0 Empty string ""
INTEGER 1-6, 8, 9 Decimal string (e.g., "42")
REAL 7 Float string (e.g., "3.14")
TEXT N >= 13, odd UTF-8 string
BLOB N >= 12, even Hex-encoded string

INTEGER PRIMARY KEY columns are automatically mapped to the row’s rowid value.

How It Works

The SQLite driver implements a pure-Luma SQLite3 file format parser:

  1. File header – reads the first 100 bytes to get page size, encoding, and reserved bytes
  2. Schema loading – reads sqlite_master on page 1 to discover tables and their column names
  3. B-tree traversal – recursively walks interior and leaf pages to find all rows
  4. Record decoding – decodes SQLite varint-encoded record headers and field data
  5. Overflow handling – follows overflow page chains for records larger than a single page

All I/O uses file.read_at() for random access. Each read opens, reads, and closes the file, matching Luma’s stateless file I/O model.

Example

sqlite = import "sqlite"

conn = db.open(sqlite, "/tmp/analytics.db")

// Get all records
rows = conn.query("SELECT * FROM events")
print("Total events: " + to_str(rows.count()))

// Filter and project
recent = conn.query("SELECT name, timestamp FROM events WHERE type = 'click' LIMIT 100")
recent.walk(row) -> {
    print(row.get("name") + " at " + row.get("timestamp"))
}

conn.close()
Last updated on