adding skills and site
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
---
|
||||
name: how-to-store-sqlite-as-nosql-store
|
||||
description: Discover how to leverage SQLite's JSON support to build a NoSQL-like document store, complete with TTL-based expiration, within this powerful embedded database.
|
||||
metadata:
|
||||
url: https://rodydavis.com/posts/sqlite/no-sql
|
||||
last_modified: Tue, 03 Feb 2026 20:04:37 GMT
|
||||
---
|
||||
|
||||
# How to store SQLite as NoSQL Store
|
||||
|
||||
|
||||
[SQLite](https://www.sqlite.org/) is a very capable edge database that can store various shapes of data.
|
||||
|
||||
[NoSQL databases](https://www.mongodb.com/nosql-explained#:~:text=Some%20say%20the%20term%20%E2%80%9CNoSQL,format%20other%20than%20relational%20tables.) are very popular due to the schema-less nature of storing of the data but it is totally possible to store these documents in SQLite.
|
||||
|
||||
SQLite actually has great [JSON support](https://www.sqlite.org/json1.html) and even supports [JSONB](https://sqlite.org/draft/jsonb.html).
|
||||
|
||||
## Create the table
|
||||
|
||||
To store JSON documents we need to create a table to store the values as strings.
|
||||
|
||||
```
|
||||
CREATE TABLE documents (
|
||||
path TEXT NOT NULL PRIMARY KEY,
|
||||
data TEXT,
|
||||
ttl INTEGER,
|
||||
created INTEGER NOT NULL,
|
||||
updated INTEGER NOT NULL,
|
||||
UNIQUE(path)
|
||||
);
|
||||
```
|
||||
|
||||
path
|
||||
|
||||
data
|
||||
|
||||
ttl
|
||||
|
||||
created
|
||||
|
||||
updated
|
||||
|
||||
/posts/1
|
||||
|
||||
{"id":1}
|
||||
|
||||
NULL
|
||||
|
||||
0
|
||||
|
||||
0
|
||||
|
||||
/posts/2
|
||||
|
||||
{"id":2}
|
||||
|
||||
NULL
|
||||
|
||||
0
|
||||
|
||||
0
|
||||
|
||||
/users/1
|
||||
|
||||
{"id":1}
|
||||
|
||||
NULL
|
||||
|
||||
0
|
||||
|
||||
0
|
||||
|
||||
The basic idea is to store a JSON object and an unique path.
|
||||
|
||||
There is an optional [TTL](https://www.cloudflare.com/learning/cdn/glossary/time-to-live-ttl/#:~:text=What%20is%20time%2Dto%2Dlive%20\(TTL\)%20in%20networking,CDN%20caching%20and%20DNS%20caching.) to automatically delete rows when they reach the stale date.
|
||||
|
||||
## Save a document
|
||||
|
||||
To save a document we can encode our JSON as a string or binary and save in in the table with a unique path.
|
||||
|
||||
```
|
||||
INSERT OR REPLACE
|
||||
INTO documents (path, data, ttl, created, updated)
|
||||
VALUES (:path, :data, :ttl, :created, :updated)
|
||||
RETURNING *;
|
||||
```
|
||||
|
||||
You can also use JSON functions to save the Object to a valid JSON string.
|
||||
|
||||
```
|
||||
INSERT OR REPLACE
|
||||
INTO documents (path, data, ttl, created, updated)
|
||||
VALUES ("/posts/1", json('{"id" 1}'), NULL, 0, 0)
|
||||
RETURNING *;
|
||||
```
|
||||
|
||||
path
|
||||
|
||||
data
|
||||
|
||||
ttl
|
||||
|
||||
created
|
||||
|
||||
updated
|
||||
|
||||
/posts/1
|
||||
|
||||
{"id":1}
|
||||
|
||||
NULL
|
||||
|
||||
0
|
||||
|
||||
0
|
||||
|
||||
## Reading a document
|
||||
|
||||
To read a document we just need the path. If a TTL is set we can [calculate if the current date](https://www.sqlite.org/lang_datefunc.html) is greater than the offset and not return the document.
|
||||
|
||||
```
|
||||
SELECT * FROM documents
|
||||
WHERE path = :path
|
||||
AND (
|
||||
(ttl IS NOT NULL AND ttl + updated < unixepoch())
|
||||
OR
|
||||
ttl IS NULL
|
||||
);
|
||||
```
|
||||
|
||||
path
|
||||
|
||||
data
|
||||
|
||||
ttl
|
||||
|
||||
created
|
||||
|
||||
updated
|
||||
|
||||
/posts/1
|
||||
|
||||
{"id":1}
|
||||
|
||||
NULL
|
||||
|
||||
0
|
||||
|
||||
0
|
||||
|
||||
## Get documents for a collection
|
||||
|
||||
We can query all the docs for a given collection using some built-in functions and a path prefix:
|
||||
|
||||
```
|
||||
SELECT *
|
||||
FROM documents
|
||||
WHERE (
|
||||
path LIKE :prefix
|
||||
AND
|
||||
(LENGTH(path) - LENGTH(REPLACE(path, '/', ''))) = (LENGTH(:prefix) - LENGTH(REPLACE(:prefix, '/', '')))
|
||||
)
|
||||
AND (
|
||||
(ttl IS NOT NULL AND ttl + updated < unixepoch())
|
||||
OR
|
||||
ttl IS NULL
|
||||
)
|
||||
ORDER BY created;
|
||||
```
|
||||
|
||||
It is expected to search for a :prefix with the `/%` at the end:
|
||||
|
||||
`"/my/path/%" // search for /my/path`
|
||||
|
||||
## Deleting expired documents
|
||||
|
||||
Using the TTL field we can delete all expired documents:
|
||||
|
||||
```
|
||||
DELETE FROM documents
|
||||
WHERE ttl IS NOT NULL
|
||||
AND ttl + updated < unixepoch();
|
||||
```
|
||||
|
||||
## Demo
|
||||
Reference in New Issue
Block a user