DecentDB vs SQLite: When to Choose Which¶
This document helps developers decide between DecentDB and SQLite for embedded database workloads. Both are single-file, embedded relational databases with ACID transactions, but they make different design trade-offs.
Versions compared: DecentDB 2.0.0 vs SQLite 3.45+ (as of 2024).
See also: SQL Feature Matrix for a per-feature support grid, and SQL Reference for DecentDB's full SQL surface.
At a Glance¶
| Dimension | DecentDB | SQLite |
|---|---|---|
| Design priority | Durability-first, then performance | Portability-first, then breadth |
| Concurrency model | One writer, many concurrent reader threads (single process) | One writer, many readers (process-safe, file-locking) |
| Default durability | WAL + fsync-on-commit, always | WAL or rollback journal, configurable via PRAGMA |
| Crash safety testing | Built-in fault-injection hooks (FaultyVFS, WAL failpoints) | Relies on external testing |
| Extension ecosystem | None (extend via core contribution) | Rich (loadable extensions, virtual tables, FTS5, JSON1, etc.) |
| SQL breadth | Deliberate Postgres-like subset | Very broad, plus extensions |
| Bindings | C ABI, Rust, Python, .NET, Go, Java, Node.js, Dart | Ubiquitous (every language has mature SQLite bindings) |
| File format stability | Stable from 2.0.0 | Decades-stable, specification published |
| License | MIT or Apache-2.0 | Public domain |
| Binary size | ~2-3 MB (Rust release build) | ~600 KB amalgamation |
| Platform support | Tier 1 Rust platforms | Virtually everywhere |
When DecentDB Is the Better Fit¶
1. You need guaranteed durability without tuning¶
DecentDB fsyncs on every commit by default. There is no PRAGMA synchronous to misconfigure. If your application cannot tolerate data loss on power failure and you don't want to reason about journal modes or sync levels, DecentDB makes the safe thing the default.
-- DecentDB: every COMMIT is durable. No PRAGMA needed.
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 29.99);
COMMIT; -- fsync'd to disk before returning
With SQLite, the equivalent safety requires explicit configuration:
2. You want crash-safety verification built in¶
DecentDB ships with FaultyVFS and WAL failpoint hooks that let you deterministically inject I/O errors and simulate torn writes in tests. If you're building a system where you need to prove crash correctness, this is a first-class feature rather than something you bolt on externally.
3. Your workload is OLTP with concurrent readers¶
DecentDB is designed for one writer with many concurrent reader threads in a single process. Readers get lock-free snapshot isolation -- they never block the writer and never block each other. SQLite's concurrency model relies on file-level locking, which is process-safe but introduces contention under heavy concurrent read workloads within the same process.
// DecentDB: readers and writer share one Database handle.
// Readers get snapshots; the writer holds the commit lock.
let db = Database::open("app.ddb")?;
// Multiple reader threads can execute concurrently
// without any locking protocol on the application side.
4. You need INSERT ... RETURNING or UPDATE ... RETURNING¶
Both DecentDB and SQLite 3.35.0+ support RETURNING on INSERT, UPDATE, and DELETE. This is common in API backends that need the generated ID or computed values back immediately.
-- Both DecentDB and SQLite 3.35.0+
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com') RETURNING id, name;
UPDATE users SET name = 'Bob' WHERE id = 1 RETURNING id, name, email;
DELETE FROM users WHERE id = 1 RETURNING id;
If you need to support older SQLite versions (pre-3.35.0), you would need a workaround:
-- SQLite pre-3.35.0 workaround (two statements)
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
SELECT last_insert_rowid(); -- only gives the id, not the full row
5. You need TRUNCATE TABLE¶
DecentDB supports TRUNCATE TABLE for fast bulk deletion. Unlike DELETE FROM, TRUNCATE: - Does not scan rows individually - Does not fire DELETE triggers - Resets auto-increment counters - Is minimally logged (faster on large tables)
SQLite requires DELETE FROM table; which scans row-by-row and fires triggers.
6. You want DISTINCT ON (Postgres-style)¶
DecentDB supports DISTINCT ON for "first row per group" queries without a window function + CTE workaround.
-- DecentDB: get the most recent order per user
SELECT DISTINCT ON (user_id) user_id, id, created_at, amount
FROM orders
ORDER BY user_id, created_at DESC;
SQLite equivalent (verbose):
WITH ranked AS (
SELECT user_id, id, created_at, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
)
SELECT user_id, id, created_at, amount FROM ranked WHERE rn = 1;
7. You need built-in substring search (trigram indexes)¶
DecentDB offers a native trigram index type designed for fast LIKE '%pattern%' queries. SQLite requires FTS5 or a full table scan.
-- DecentDB: create a trigram index for substring search
CREATE INDEX idx_users_name_trgm ON users USING trigram(name);
-- This query uses the trigram index instead of a full scan
SELECT * FROM users WHERE name LIKE '%alice%';
Without a trigram index, LIKE '%pattern%' requires a full table scan in both databases. SQLite's FTS5 can help with full-text search but is not optimized for arbitrary substring matching.
8. You want a richer set of aggregate and window functions out of the box¶
DecentDB includes several analytical functions that SQLite lacks without extensions:
-- These all work in DecentDB, none work in stock SQLite:
SELECT STDDEV(amount), VARIANCE(amount) FROM orders;
SELECT BOOL_AND(active), BOOL_OR(admin) FROM users;
SELECT MEDIAN(salary) FROM employees;
SELECT PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY response_time) FROM requests;
SELECT DATE_DIFF('day', start_date, end_date) FROM projects;
SELECT LAST_DAY('2024-02-15'); -- '2024-02-29'
9. You want native UUID support¶
DecentDB has a first-class UUID type. SQLite stores UUIDs as text or blobs and lacks built-in UUID generation.
-- DecentDB
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(),
user_id INT NOT NULL
);
INSERT INTO sessions (user_id) VALUES (1) RETURNING id;
-- Returns a proper UUID, not a string
10. You prefer Postgres-style SQL syntax¶
DecentDB targets Postgres compatibility for its SQL surface. If your application also talks to Postgres, DecentDB reduces the dialect gap. Features like $1 positional parameters, ILIKE, RETURNING, DISTINCT ON, and STRING_AGG all follow Postgres conventions.
-- DecentDB: Postgres-style positional parameters
SELECT * FROM users WHERE email = $1 AND active = $2;
-- Postgres-style case-insensitive LIKE
SELECT * FROM users WHERE name ILIKE '%alice%';
-- Postgres-style string aggregation
SELECT department, STRING_AGG(name, ', ' ORDER BY name) FROM employees GROUP BY department;
When SQLite Is the Better Fit¶
1. You need a battle-tested, decades-stable file format¶
SQLite is one of the most tested pieces of software ever written. Its file format has been stable for over 20 years. If you need to write a .sqlite file today and read it in 2040, SQLite is the safe bet. DecentDB is pre-1.0 with an evolving on-disk format.
2. You need broad SQL coverage or extensions¶
SQLite's SQL surface is far wider than DecentDB's, and its extension ecosystem (FTS5, R-Tree, JSON1, virtual tables, custom functions via C) is unmatched in the embedded space.
-- SQLite: FTS5 full-text search (no equivalent in DecentDB)
CREATE VIRTUAL TABLE docs USING fts5(title, body);
INSERT INTO docs (title, body) VALUES ('Guide', 'How to use the database');
SELECT * FROM docs WHERE docs MATCH 'database';
-- SQLite: virtual tables, custom collations, loadable extensions
.load ./my_extension
DecentDB has no extension/plugin mechanism. If you need a SQL feature DecentDB doesn't have, you contribute to the core or work around it in application code.
3. You need cross-process access¶
SQLite uses file-level locking to allow multiple processes to safely share one database file. DecentDB is single-process. If you have multiple processes (e.g., separate worker processes, CLI tools, and a web server) that need to read/write the same database, SQLite is the right choice.
# SQLite: multiple processes can share the file
sqlite3 app.db "INSERT INTO jobs VALUES (1, 'pending')" # process A
sqlite3 app.db "SELECT * FROM jobs" # process B
4. You need an ecosystem of mature language bindings¶
SQLite has the most ubiquitous bindings of any database in history. Every language -- from C to Zig -- has multiple well-maintained SQLite libraries. DecentDB's bindings are growing (Rust, Python, .NET, Go, Java, Node.js, Dart) but SQLite's ecosystem is decades ahead in maturity and coverage.
5. You need ATTACH DATABASE¶
SQLite can query multiple database files in a single statement via ATTACH DATABASE. DecentDB does not support this. If your application needs to join across separate .db files, SQLite handles this natively.
-- SQLite: query across two database files
ATTACH DATABASE 'analytics.db' AS analytics;
SELECT u.name, a.revenue
FROM main.users u JOIN analytics.revenue a ON u.id = a.user_id;
6. You need WITHOUT ROWID tables or rowid access¶
SQLite's WITHOUT ROWID tables and implicit rowid pseudo-column are useful for certain schema designs. DecentDB does not expose rowid to SQL and has no WITHOUT ROWID concept.
-- SQLite
SELECT rowid, * FROM users WHERE rowid BETWEEN 100 AND 200;
CREATE TABLE lookup (key TEXT PRIMARY KEY, val TEXT) WITHOUT ROWID;
DecentDB alternative: use an explicit INT PRIMARY KEY (which auto-assigns on INSERT).
7. You need window frame clauses (ROWS BETWEEN ...)¶
SQLite supports ROWS BETWEEN and RANGE BETWEEN frame specifications in window functions. DecentDB does not currently support frame clauses.
-- SQLite: rolling 3-row average
SELECT date, amount,
AVG(amount) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS rolling_avg
FROM sales;
-- DecentDB: frame clauses are not supported
-- You would need to compute this in application code
8. You need a small, portable, zero-dependency library¶
SQLite compiles to a single C file (sqlite3.c) with no external dependencies. It runs on virtually every platform ever made -- from embedded microcontrollers to mainframes. DecentDB is a Rust crate with a more conventional build system and a larger binary footprint.
9. You need PRAGMA configuration¶
SQLite offers dozens of PRAGMAs for tuning cache size, page size, journal mode, locking mode, foreign key enforcement, and more. DecentDB exposes a small subset (page_size, cache_size, integrity_check, database_list, table_info) and intentionally limits configurability to reduce misconfiguration risk.
-- SQLite: extensive runtime tuning
PRAGMA cache_size = -64000; -- 64 MB cache
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
If your application depends on these tuning knobs, SQLite is the right tool.
10. You need INTERSECT ALL or EXCEPT ALL (multiset operations)¶
DecentDB does not support INTERSECT ALL or EXCEPT ALL (as noted in the SQL Reference). SQLite does.
-- SQLite: multiset difference (preserves duplicate counts)
SELECT item_id FROM inventory EXCEPT ALL SELECT item_id FROM sold;
-- DecentDB: must rewrite as a grouped query
Side-by-Side SQL Examples¶
UPSERT (both supported, different syntax feel)¶
-- DecentDB (Postgres-style)
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@new.com')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email;
-- SQLite (same feature, compatible syntax)
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@new.com')
ON CONFLICT (id) DO UPDATE SET name = excluded.name, email = excluded.email;
String aggregation¶
-- DecentDB: both styles
SELECT STRING_AGG(name, ', ' ORDER BY name) FROM employees;
SELECT GROUP_CONCAT(name, ', ') FROM employees;
-- SQLite: GROUP_CONCAT only
SELECT GROUP_CONCAT(name, ', ') FROM employees;
Date/time arithmetic¶
-- DecentDB: rich date/time functions
SELECT DATE_TRUNC('month', '2024-03-15 14:30:45'); -- '2024-03-01 00:00:00'
SELECT DATE_DIFF('day', '2024-03-10', '2024-03-15'); -- 5
SELECT LAST_DAY('2024-02-11'); -- '2024-02-29'
SELECT '2024-03-15'::timestamp + INTERVAL '1 month'; -- '2024-04-15'
-- SQLite: limited date/time, no INTERVAL arithmetic
SELECT date('2024-03-15', '+1 month'); -- '2024-04-15'
SELECT strftime('%Y-%m', '2024-03-15'); -- '2024-03'
Statistical aggregates¶
-- DecentDB: built-in statistics
SELECT STDDEV(salary), VARIANCE(salary), MEDIAN(salary),
PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY salary)
FROM employees;
-- SQLite: not available without extensions
-- Requires custom C extension or application-level computation
Summary Decision Matrix¶
| Your situation | Recommendation |
|---|---|
| Durability is non-negotiable, no PRAGMA tuning wanted | DecentDB |
| Single-process, multi-threaded reader workload | DecentDB |
Need TRUNCATE, DISTINCT ON | DecentDB |
| Need built-in trigram substring search | DecentDB |
| Need rich statistical/window aggregates out of the box | DecentDB |
| Need Postgres-like SQL to reduce dialect drift | DecentDB |
| Need crash-injection testing hooks | DecentDB |
| Decades-stable file format, maximum compatibility | SQLite |
| Need loadable extensions, FTS5, R-Tree, virtual tables | SQLite |
| Multiple processes sharing one database file | SQLite |
| Embedded on exotic platforms (microcontrollers, etc.) | SQLite |
Need extensive PRAGMA runtime tuning | SQLite |
Need window frame clauses (ROWS BETWEEN) | SQLite |
Need ATTACH DATABASE across files | SQLite |
| Largest possible language binding ecosystem | SQLite |
| Absolute smallest binary with zero dependencies | SQLite |
Both databases are solid choices for embedded workloads. Pick the one whose defaults and constraints align with your application's requirements.