simonw / sqlite-chronicle

Use triggers to track when rows in a SQLite table were updated or deleted
27 stars 0 forks source link

sqlite-chronicle

PyPI Changelog Tests License

Use triggers to track when rows in a SQLite table were updated or deleted

Installation

pip install sqlite-chronicle

enable_chronicle(conn, table_name)

This module provides a function: sqlite_chronicle.enable_chronicle(conn, table_name), which does the following:

  1. Checks if a _chronicle_{table_name} table exists already. If so, it does nothing. Otherwise...
  2. Creates that table, with the same primary key columns as the original table plus integer columns added_ms, updated_ms, version and deleted
  3. Creates a new row in the chronicle table corresponding to every row in the original table, setting added_ms and updated_ms to the current timestamp in milliseconds, and version column that starts at 1 and increments for each subsequent row
  4. Sets up three triggers on the table:
    • An after insert trigger, which creates a new row in the chronicle table, sets added_ms and updated_ms to the current time and increments the version
    • An after update trigger, which updates the updated_ms timestamp and also updates any primary keys if they have changed (likely extremely rare) plus increments the version
    • An after delete trigger, which updates the updated_ms, increments the version and places a 1 in the deleted column

The function will raise a sqlite_chronicle.ChronicleError exception if the table does not have a single or compound primary key.

Note that the version for a table is a globally incrementing number, so every time it is set it will be set to the current max(version) + 1 for that entire table.

The end result is a chronicle table that looks something like this:

id added_ms updated_ms version deleted
47 1694408890954 1694408890954 2 0
48 1694408874863 1694408874863 3 1
1 1694408825192 1694408825192 4 0
2 1694408825192 1694408825192 5 0
3 1694408825192 1694408825192 6 0

updates_since(conn, table_name, since=None, batch_size=1000)

The sqlite_chronicle.updates_since() function returns a generator over a list of Change objects.

These objects represent changes that have occurred to rows in the table since the since version number, or since the beginning of time if since is not provided.

Each Change returned from the generator looks something like this:

Change(
    pks=(5,),
    added_ms=1701836971223,
    updated_ms=1701836971223,
    version=5,
    row={'id': 5, 'name': 'Simon'},
    deleted=0
)

A Change is a dataclass with the following properties:

Any time you call this you should track the last version number that you see, so you can pass it as the since value in future calls to get changes that occurred since that point.

Note that if a row had multiple updates in between calls to this function you will still only see one Change object for that row - the updated_ms and version will reflect the most recent update.

Potential applications

Chronicle tables can be used to efficiently answer the question "what rows have been inserted, updated or deleted since I last checked" - by looking at the version column which has an index to make it fast to answer that question.

This has numerous potential applications, including: