Skip to content

SQLite Extension ​

The SQLite extension exposes RelationalText document operations as native SQL functions. Documents stored as JSON text blobs in any SQLite database can be queried and manipulated without leaving SQL — no application code, no external process, no round-trip to a language runtime.

Typical use cases: log analytics over stored posts, CMS content search and migration scripts, batch lens transforms across an entire table, ad-hoc inspection of document structure during development.

Building ​

bash
cargo build \
  --example relationaltext_sqlite \
  --features loadable_extension \
  -p relationaltext-sqlite

The shared library is written to:

PlatformPath
macOStarget/debug/examples/librelationaltext_sqlite.dylib
Linuxtarget/debug/examples/librelationaltext_sqlite.so
Windowstarget/debug/examples/relationaltext_sqlite.dll

For a release build, add --release; the output lands under target/release/examples/.

Loading ​

In the sqlite3 shell:

sql
.load ./target/debug/examples/librelationaltext_sqlite
SELECT rt_version();

From application code via the C API:

c
sqlite3_load_extension(db, "./librelationaltext_sqlite", NULL, &errmsg);

TIP

SQLite requires SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION (or the -enable-load-extension compile flag) before sqlite3_load_extension will succeed. The sqlite3 shell enables it automatically.

Scalar Functions ​

FunctionArgumentsReturnsDescription
rt_version()—TEXTCrate version string
rt_text(doc)TEXTTEXTPlain text of the document
rt_char_length(doc)TEXTINTEGERUnicode code-point count (not byte length)
rt_feature_count(doc)TEXTINTEGERTotal number of features across all facets
rt_has_mark(doc, type_id)TEXT, TEXT0 or 1Whether the document contains any feature with the given type ID
rt_has_mark(doc, type_id, name)TEXT, TEXT, TEXT0 or 1Whether the document contains a feature with the given type ID and name
rt_apply_lens(doc, lens_json)TEXT, TEXTTEXTApply a LensSpec (JSON) and return the transformed document
rt_insert_text(doc, byte_pos, text)TEXT, INTEGER, TEXTTEXTInsert text at a byte position, adjusting all facet ranges
rt_delete_text(doc, byte_start, byte_end)TEXT, INTEGER, INTEGERTEXTDelete the byte range [byte_start, byte_end), adjusting facets
rt_remove_mark(doc, byte_start, byte_end, type_key)TEXT, INTEGER, INTEGER, TEXTTEXTRemove a specific mark from a byte range

All functions that return a document blob return it as a JSON TEXT value in the same wire format they received. rt_char_length counts Unicode code points — the same unit used by rt_facets byte coordinates after UTF-8 encoding, not the same as length() in SQLite which counts bytes or UTF-16 code units depending on the build.

Table-Valued Functions ​

rt_facets(doc) ​

Returns one row per feature in the document. Use this to query or join on the full facet structure.

ColumnTypeDescription
byte_startINTEGERStart of the byte range (inclusive)
byte_endINTEGEREnd of the byte range (exclusive)
type_idTEXTFeature $type field
nameTEXTFeature name from data["name"], or NULL
dataTEXTFull feature data as a JSON string

rt_blocks(doc) ​

Returns one row per block feature in the document. Block detection uses the presence of a "parents" key in the feature data. Blocks are returned in document order.

ColumnTypeDescription
block_indexINTEGERZero-based position in the document
nameTEXTBlock name (e.g. paragraph, heading)
attrsTEXTBlock attributes as a JSON object
parentsTEXTParent chain as a JSON array of strings
textTEXTPlain text content of this block

Examples ​

Count documents that contain bold text ​

sql
SELECT COUNT(*)
FROM articles
WHERE rt_has_mark(doc, 'org.relationaltext.facet', 'bold');
sql
SELECT
  articles.id,
  articles.title,
  json_extract(f.data, '$.url') AS url,
  substr(rt_text(articles.doc), f.byte_start + 1, f.byte_end - f.byte_start) AS link_text
FROM articles, rt_facets(articles.doc) AS f
WHERE f.name = 'link'
ORDER BY articles.id;

Extract the first heading from each document ​

sql
SELECT
  articles.id,
  b.text AS heading
FROM articles
JOIN rt_blocks(articles.doc) AS b
  ON  b.name = 'heading'
  AND b.block_index = (
    SELECT MIN(b2.block_index)
    FROM rt_blocks(articles.doc) AS b2
    WHERE b2.name = 'heading'
  )
ORDER BY articles.id;

Apply a lens to every document in a table ​

sql
UPDATE articles
SET doc = rt_apply_lens(
  doc,
  json('{
    "$type": "org.relationaltext.lens",
    "id": "migrate-to-rt",
    "source": "org.commonmark.facet",
    "target": "org.relationaltext.facet",
    "rules": [],
    "invertible": true
  }')
);

For non-trivial lenses, store the lens JSON in a separate table or application variable and pass it as a bound parameter rather than inlining it.

Join documents with their block structure ​

sql
SELECT
  a.id,
  a.title,
  b.block_index,
  b.name   AS block_type,
  b.text   AS block_text,
  b.attrs  AS block_attrs
FROM articles AS a, rt_blocks(a.doc) AS b
ORDER BY a.id, b.block_index;

This is the standard pattern for shredding a document corpus into its block structure for reporting or migration — each row is one block, and the block_index column lets you reconstruct document order.

Relationship to the WASM SQL Engine ​

The scalar rt_apply_lens function runs the same lens compiler and SQL execution backend as applyFacetSQL in the TypeScript package. The difference is the host environment: WASM executes inside a JavaScript runtime against an in-memory virtual table; the SQLite extension executes inside SQLite's query engine against a document column. Both paths use the same Rust core.

See SQL Engine for the virtual table schema and the full set of SQL operations supported by applyFacetSQL.