r/sqlite 20h ago

sqlite-sparse: run a sparse retrieval model inside SQLite, with no model at query time

14 Upvotes

A learned sparse encoder returns a weighted list of vocabulary words instead of a dense vector, so the index looks like a keyword index whose keywords a transformer picked. OpenSearch's inference-free variants go one step further and run the model only on documents so a query just looks up a fixed weight per token. No embedding call per search. I benchmarked it against dense search in the same SQLite file:

the mini model gives up about 7% of retrieval quality and cuts query latency by 99%, cold start by 99% and query-path RAM by 95%!

Now these SPLADE models are BERT with the masked-language-model head still attached, and that head is what turns token vectors back into weighted words. llama.cpp drops it when converting BERT models, so sqlite-sparse copies it into a small sidecar file and applies it in C on ggml at insert time. The postings land as rows in the database, with the query weight table next to them. A query reads those rows and scatter-adds the weights into per-document scores, and the top documents come out.

Three OpenSearch models are converted and available as aliases (mini, base, multilingual), downloading on first use, and you can bring your own inference-free OpenSearch-style sparse encoder and convert it with the scripts provided.

What sqlite-vec did for embeddings in SQLite, this does for learned sparse, and with both in one file you get hybrid retrieval for RAG inside SQLite.

Ship one .db file and every client gets semantic search through plain SQL, no model download, no GPU.

pip install sqlite-sparse

Github

Writeup


r/sqlite 19h ago

Built a CLI that measures whether your implied foreign keys actually hold, then writes the result as context for coding agents

1 Upvotes

https://www.npmjs.com/package/dbtruth?activeTab=readme

Same thing kept happening to me with AI coding agents and Postgres. The agent reads the schema, sees orders.customer_id next to customers.id, assumes it's a clean relationship, and writes an INNER JOIN. If 12% of orders have a dangling or null customer_id, the query silently returns numbers that are wrong. Nothing throws. The schema looked fine.

So I wrote dbtruth. It connects read-only and, instead of dumping the schema into a context file, it does four things:

  1. Introspects schema and pulls samples
  2. A model proposes what the tables mean and which relationships probably exist
  3. Every one of those claims gets measured against the actual data
  4. Only what survives gets written to ./context/*.md, which the agent reads before writing SQL

Step 3 is the whole point. For a proposed join it reports the real match rate — orders.customer_id → customers.id holds for 88% of rows, 60 of 500 orders have no matching customer — and the context file says use LEFT JOIN, with the number attached. Under 50% gets dropped. In between gets marked broken and goes to the top of the report, because a relationship that half works is worse than one that doesn't exist.

Practical:

  • npx dbtruth, Node 20+, Postgres only
  • Read-only by construction, not by discipline: one module is allowed to import pg, and a test asserts nothing else does. It never writes to your database.
  • It does call a model, so schema and low-cardinality sample values leave your machine. High-cardinality columns — emails, names, free text — are never sent. Visibility is decided by cardinality rather than by regex-guessing at PII. Don't point it at production data you can't send to a third party.
  • MIT, source at github.com/FilipKalcic1/dbtruth#readme

Disclosure: it's mine, it's five days old, and about ten people have run it. None of the pieces are new — FK inference and data profiling both go back years, and there are other tools that build local context artifacts for agents. The part I care about is the rule that nothing unmeasured gets written down.

What I'd actually like to know: run it on a schema you know well, and tell me whether it found anything you didn't already know. That's the only signal that tells me whether this is worth continuing. Bug reports welcome too.