Skip to content

SQLite Compatibility

SQLite Compatibility

HeliosDB Nano accepts a deliberate subset of SQLite-flavoured SQL so that existing sqlite3-driven applications can be retargeted with a one-line import swap. The table below is the authoritative reference for what is supported, where the rewriting happens, and what to expect when a feature isn’t covered.

Feature matrix

FeatureStatusWhere it’s handledNotes
? positional placeholderssrc/sql/sqlite_compat.rs::rewrite_question_placeholdersQuote-, comment- and dollar-quote-aware rewrite to $1, $2, …. Mixing ? with $N in the same statement is rejected.
:name, @name, $name placeholders✅ (via SDK)heliosdb_sqlite/main.py::_bind_parametersThe SDK pre-binds named parameters by string substitution before the SQL reaches the engine.
INSERT OR REPLACE INTO t (cols) VALUES …src/sql/sqlite_compat.rs::rewrite_insert_or_replaceRewritten to INSERT … ON CONFLICT DO UPDATE SET col = EXCLUDED.col for every named column. Falls back to a plain INSERT if no column list is given.
INSERT OR IGNORE INTO …src/sql/sqlite_compat.rs::rewrite_insert_or_ignoreRewritten to INSERT … ON CONFLICT DO NOTHING.
INTEGER PRIMARY KEY AUTOINCREMENTsrc/sql/sqlite_compat.rs::rewrite_autoincrementMapped to BIGSERIAL PRIMARY KEY.
DATETIME('now')src/sql/sqlite_compat.rs::rewrite_datetime_nowMapped to CURRENT_TIMESTAMP. Other DATETIME(arg) forms pass through unchanged.
sqlite_master introspectionsrc/sql/system_views.rs, src/sql/phase3/system_views.rsReturns one row per user table / materialised view, columns: type, name, tbl_name, rootpage, sql. The sql column is best-effort and intended for WHERE name = ? filtering, not faithful DDL.
PRAGMA table_info(t)src/lib.rs::handle_pragma_query, src/protocol/postgres/handler.rs::pragma_table_info, src/repl/shell.rsReturns SQLite-shaped rows (cid, name, type, notnull, dflt_value, pk).
PRAGMA foreign_keys = …, journal_mode = …, synchronous = …, busy_timeout = …✅ no-opsame as aboveParsed and acknowledged silently. Foreign keys are always on; journal mode is RocksDB-managed.
Other PRAGMA name [= value]✅ no-opparse_pragmaUnknown PRAGMAs return an empty result instead of raising.
executescript("a; b; c;")PostgreSQL simple-query protocol + SDK split-on-;Multi-statement strings work over the wire and via the SDK shim.
Multi-column CREATE INDEX (B-tree)⚠️ partialsrc/sql/planner.rsAccepted; only the leading column is indexed today. Vector indexes (`USING hnsw
INTEGER type affinityparser type mappingMaps to INT4 (or BIGSERIAL when paired with PRIMARY KEY AUTOINCREMENT).
BLOB / BOOLEAN / REAL / TEXT typesparser type mappingMap to BYTEA / BOOL / FLOAT4 / TEXT respectively.
|| string concat, IFNULL, COALESCE, LENGTH, IFNULL, LIKEevaluatorSame semantics as PostgreSQL.
cursor.lastrowid✅ (v3.21+)SDK auto-rewriteCursor.execute() detects INSERT statements, appends RETURNING <pk> (PK column cached per table via PRAGMA table_info), captures the returned value. Tables with TEXT PKs return None, matching sqlite3 semantics.
STRFTIME(fmt, …) / JULIANDAY(d)❌ engine; ⚠️ SDK rewrite optionalNot added to the engine — SQLite-specific names would be drift. Use the PG surface: TO_CHAR(d, fmt), EXTRACT(EPOCH FROM d) / 86400, DATE_TRUNC, DATE_PART, AGE, MAKE_DATE, MAKE_TIMESTAMP. All shipped in v3.21.
User-defined Python functions, register_adapter/register_converterSDK raises NotSupportedErrorEmbedded mode runs in a separate process; in-process callbacks are not bridged.
Custom collations, loadable extensionsSDK raises NotSupportedError

How rewriting works

Parser::parse() (in src/sql/parser.rs) runs SQLite-compat preprocessing before any other rewrite, so downstream stages — including time-travel rewriting, STORAGE clause stripping and the sqlparser front end — operate on canonical PostgreSQL syntax. The PostgreSQL wire handler short-circuits PRAGMA before the parser sees it; EmbeddedDatabase short-circuits inside query() and execute(); the REPL formats PRAGMA results with the SQLite-shaped schema directly.

Embedded vs daemon mode

Embedded (mode='embedded')Daemon (mode='daemon', HELIOSDB_DSN=…)
How it runsOne heliosdb-nano repl subprocess per ConnectionMany psycopg2 connections to one running heliosdb-nano start
Per-query latency~10–50 ms (subprocess RPC)Sub-millisecond (PG wire)
Cross-connection consistency✅ as of v3.21 — every new process re-registers PK / UNIQUE / FK indexes and replays existing rows through on_insert on open. Cross-connection upserts and uniqueness checks behave correctly.Single shared process — full consistency.
SetupZero — only the binary on PATH (or HELIOSDB_BIN=)Run heliosdb-nano start --port … --auth=trust (or with passwords)
Recommended forDev, CI, single-process scriptsProduction, multi-user, long-lived dashboards

Honest gaps

  • Persistent ART pages — the v3.21 fix rebuilds indexes from data on open (O(rows + indexes) one-time cost). For multi-million-row data directories, that startup cost can be material. v3.22+ tracks moving to a RocksDB column family per index so opens are O(small).
  • STRFTIME / JULIANDAY engine functions — intentionally not added. The PostgreSQL surface (TO_CHAR, EXTRACT, DATE_TRUNC, DATE_PART, AGE, MAKE_DATE, MAKE_TIMESTAMP, etc.) is the canonical home. SDKs can rewrite SQLite-specific names if desired.
  • Numeric TO_CHAR(123.45, '9,999.00') — engine support is date/timestamp only today. Numeric formatting can be done at the client layer.

See also

  • docs/guides/sqlite_drop_in_tutorial.md — runnable end-to-end port.
  • tests/sqlite_compat_tests.rs — every item in this matrix has at least one assertion against a live EmbeddedDatabase.
  • src/sql/sqlite_compat.rs — pre-parser rewrites + 16 unit tests.