Appearance
C
From C you can either load the extension at runtime or compile it in (static) — the latter is the most robust option for a service, since there's no loader to enable and nothing on disk to point at.
Dynamic load
For an app linking -lsqlite3:
c
#include <sqlite3.h>
#include <stdio.h>
int main(void) {
sqlite3 *db;
char *err = 0;
sqlite3_open("app.db", &db);
/* Enable the C-API loader just long enough to load ours, then disable it. */
sqlite3_enable_load_extension(db, 1);
if (sqlite3_load_extension(db, "dist/rls.so", 0, &err)) {
fprintf(stderr, "load failed: %s\n", err);
return 1;
}
sqlite3_enable_load_extension(db, 0);
sqlite3_exec(db,
"CREATE TABLE documents(id INTEGER PRIMARY KEY, owner_id INT, title TEXT);"
"INSERT INTO documents(owner_id,title) VALUES (1,'Alice'),(2,'Bob');",
0, 0, &err);
/* '' is an escaped single quote inside a C string literal. */
sqlite3_exec(db,
"SELECT rls_protect('{\"table\":\"documents\","
"\"select\":\"owner_id = rls_ctx(''user_id'')\"}')", 0, 0, &err);
sqlite3_exec(db, "SELECT rls_seal('CHANGE_ME'); SELECT rls_guard();", 0, 0, &err);
/* Per query — use a prepared statement + sqlite3_bind_text in real code so
the JSON is a bound parameter, not concatenated into the SQL. */
sqlite3_exec(db, "SELECT rls_set_context('{\"user_id\":1}')", 0, 0, &err);
sqlite3_exec(db, "SELECT id, title FROM documents", /* callback */ 0, 0, &err);
sqlite3_close(db);
return 0;
}sh
cc app.c -lsqlite3 -o appStatic linking (recommended for services)
If you compile SQLite into your binary (the amalgamation), skip load_extension entirely and register the init function before opening connections. It then applies to every new connection automatically:
c
extern int sqlite3_rls_init(sqlite3 *, char **, const sqlite3_api_routines *);
int main(void) {
sqlite3_auto_extension((void (*)(void)) sqlite3_rls_init);
/* every sqlite3_open() from here on has rls_* available */
...
}Build the extension's src/rls.c into your program alongside the SQLite amalgamation. There's no loader to enable and no .so to ship or locate.
Binding parameters (do this in real code)
The examples inline JSON for brevity. In production, bind it:
c
sqlite3_stmt *st;
sqlite3_prepare_v2(db, "SELECT rls_set_context(?1)", -1, &st, 0);
sqlite3_bind_text(st, 1, "{\"user_id\":42}", -1, SQLITE_TRANSIENT);
sqlite3_step(st);
sqlite3_finalize(st);Notes
- Entry point. SQLite derives the init symbol from the filename:
rls.so→sqlite3_rls_init. If you rename the file, pass the entry point as the third argument tosqlite3_load_extension. - Return codes. A denied base-table read returns
SQLITE_AUTH(23); a policy violation on write returnsSQLITE_CONSTRAINTviaRAISE(ABORT). Checksqlite3_exec/sqlite3_stepreturn codes. - Re-disabling
sqlite3_enable_load_extensionafter loading closes the C-API path; the authorizer additionally blocks theload_extension()SQL function once guarded.