Some checks failed
CI / check (push) Failing after 6m14s
The schema's comment still called it the field last-write-wins compares. A write is decided by change_seq alone; updated_at is for display. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
48 lines
2.0 KiB
PL/PgSQL
48 lines
2.0 KiB
PL/PgSQL
-- Hankan sync schema.
|
|
--
|
|
-- Mirrors the client's syncable tables, plus the two columns the client does
|
|
-- not have: change_seq, which the server assigns and clients use as a
|
|
-- cursor, and user_id, which is one value today but keeps a second device or
|
|
-- person from being a migration.
|
|
--
|
|
-- Rows are stored generically: the primary key as text, the row body as
|
|
-- JSONB. The alternative — six typed tables kept in lockstep with the
|
|
-- client's migrations — buys nothing here, because the server never reads
|
|
-- inside a row. It stores and orders them; the client interprets them.
|
|
|
|
CREATE TABLE IF NOT EXISTS sync_row (
|
|
user_id TEXT NOT NULL,
|
|
tbl TEXT NOT NULL,
|
|
pk TEXT NOT NULL,
|
|
data JSONB NOT NULL,
|
|
-- The client's wall clock, when the row was last edited. Kept for display
|
|
-- only: since protocol 2 (002-protocol-2.sql) nothing compares it — a
|
|
-- write is decided by change_seq alone.
|
|
updated_at BIGINT NOT NULL,
|
|
-- A delete. Kept as a row so it can be handed to a device that was
|
|
-- offline when it happened.
|
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
|
-- Server-assigned and monotonic. The cursor a client pages from.
|
|
change_seq BIGSERIAL NOT NULL,
|
|
PRIMARY KEY (user_id, tbl, pk)
|
|
);
|
|
|
|
-- The pull query is exactly this: everything newer than the client's cursor,
|
|
-- in assignment order.
|
|
CREATE INDEX IF NOT EXISTS sync_row_cursor ON sync_row (user_id, change_seq);
|
|
|
|
-- change_seq must advance on every update, or a row edited after a client
|
|
-- last pulled would sit below that client's cursor and never be delivered.
|
|
-- Doing it in a trigger means no write path can forget.
|
|
CREATE OR REPLACE FUNCTION sync_row_bump() RETURNS trigger AS $$
|
|
BEGIN
|
|
NEW.change_seq := nextval('sync_row_change_seq_seq');
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS sync_row_bump_trg ON sync_row;
|
|
CREATE TRIGGER sync_row_bump_trg
|
|
BEFORE UPDATE ON sync_row
|
|
FOR EACH ROW EXECUTE FUNCTION sync_row_bump();
|