How Alcuin protects your data
Alcuin is built so that a click never changes your data by surprise. Five ideas carry that: writes are staged, the review shows the SQL, a row changed under you is reported, production is read-only until you unlock it, and values are shown as the server sent them.
Nothing is written until you commit
Section titled “Nothing is written until you commit”An edit in the grid or the row panel, a new row or a deleted row is a staged change. It lives in Alcuin, not in the database. The table’s tab shows a dot, and a bar under the rows counts the changes: 1 change not saved yet · 1 edit.

A staged edit: four marks say it, and nothing has reached the database.
- The tab title has a dot.
- The cell is outlined.
- The field shows the new value, and the old one under it.
- The bar counts the staged changes.
Staged changes are kept while you work elsewhere, and across a quit or a crash: when you open the table again, the bar says they were restored from your last session.
Discard changes asks first. The dialog, Discard staged changes?, says how many changes it drops, that nothing has run, and that they can’t be brought back. Choose Discard changes and they are gone. Nothing reaches the server.
When you commit, the staged changes go to the server in one transaction. Either all of them land, or none do.
Edit rows shows the steps; make your first safe edit walks through one.
The review shows the SQL
Section titled “The review shows the SQL”Review SQL shows the transaction before it runs. For one edited field it reads:
BEGIN;UPDATE public.notesSET body = 'Book the dentist for Tuesday'WHERE id = '2' AND xmin = '757'::xid;COMMIT;The review inlines the values so you can read them. What Alcuin sends binds them as parameters instead, so a value can never be read as SQL:
UPDATE public.notesSET body = $1WHERE id = $2 AND xmin = $3::xidInside the transaction, Alcuin also sets how long a statement may wait for another session’s lock, with SET LOCAL lock_timeout: 5 seconds unless your Safety settings set a limit. A commit that would queue behind someone else’s lock stops instead.
Each statement must touch exactly one row. If one touches none, or more than one, Alcuin rolls the whole transaction back.
Every UPDATE and DELETE names the row by its key: the primary key, or else the smallest unique index over columns that can’t be NULL. A table with neither has no safe way to name a row, so its rows are read-only in Alcuin: the bar over them says Rows here are read-only. You can still add rows to it, and change existing ones with a query.
Columns the server fills in itself, generated columns and GENERATED ALWAYS identity columns, are left out of the statement, and the review says so.
A row changed under you is reported
Section titled “A row changed under you is reported”The xmin = … condition is how Alcuin notices that someone else changed a row. xmin is the row’s version: the id of the transaction that last wrote it. An update writes a new one. Alcuin reads it with the rows, and the UPDATE matches only the version you saw.
If another session changed the row after you loaded it, the UPDATE matches nothing. Alcuin rolls back, so none of your changes land, and the bar says A row changed under you. The other changes stay staged.
A row can also match nothing when nobody changed it: a row-level security policy or a BEFORE trigger made the server skip the statement. Alcuin checks the row’s version again, tells the two cases apart, and says which it was.
Review the stale row to see it three ways, column by column: as loaded, now on the server, and yours. Then choose:
- Keep mine writes only the columns you edited over the row as it is now.
- Take theirs drops your edit and keeps the server’s row.
If the row was deleted on the server, the review says Row no longer exists on the server. and offers Discard this change. Discard stale and reload, on the bar, drops every stale change at once and reads the rows again.
Where a row has no version to match, Alcuin matches every old value that can be compared instead.
If the connection drops while Alcuin is waiting for the server to answer COMMIT, Alcuin doesn’t guess. It records the transaction before sending COMMIT, and asks the server what became of it before anything is sent again:
SELECT pg_xact_status($1::xid8)A commit that landed is never applied twice.
Production is read-only until you unlock it
Section titled “Production is read-only until you unlock it”Every connection has an environment, Local, Development, Staging or Production, and each has its own safety rules. A production connection opens read-only on the server itself, with SET default_transaction_read_only = on, so even a write that slips past Alcuin’s own checks fails in Postgres until you unlock editing.
Safety rules per environment lists every rule and its default, Work on production covers unlocking and locking, and ALC-3001 covers the message you see when a change is refused.
Values exactly as the server sent them
Section titled “Values exactly as the server sent them”Postgres sends each value as text, and Alcuin keeps that text. Nothing is converted to a number or a date on the way, so nothing is rounded or shifted:
- A
numeric(30,10)or abigintshows every digit. - A
timestamptzkeeps the offset the server printed. Atimestampwithout a zone is marked(no zone)in the row panel. NULLand an empty string look different:NULL, and''.
A few values are laid out for reading, without changing what they are: booleans read true and false, JSON sits on one line in a cell and is indented in the row panel, and fractional seconds are padded to the column’s precision.
Values shows how each kind of value is displayed and edited.