CI / test (push) Successful in 3m11s
A path is still SQLite and a postgres:// URL is PostgreSQL, so every existing configuration keeps working untouched and moving is one line changed rather than a new setting to find. An address of the form @/path/to/file reads the real one out of that file, which is how a DSN carrying a password stays out of a configuration that gets copied, diffed and pasted into issues. The driver is pgx, which is pure Go. That is not a preference: the whole binary is built with CGO_ENABLED=0 so it runs on a node whose libc is older than the builder's, and a cgo driver would take that away from every deployment, including the ones still on SQLite. Tested against a real PostgreSQL, which found two things nothing else could. The schema_migrations table is created in Go rather than in a migration file, so it escaped the port and still said INTEGER — 64 bits in SQLite, 32 in PostgreSQL, and a millisecond timestamp does not fit, so the very first migration failed to record that it had run. And the memory migration uses an FTS5 virtual table with three triggers keeping it in step, which has no counterpart: PostgreSQL derives a tsvector from the row, so there is nothing to keep in step and no trigger to forget. That leaves exactly one query that is not the same sentence in both dialects — the note search, MATCH against @@ — and it is written out twice rather than hidden behind a shared spelling that would mean something subtly different in each. Everything else is one set of queries. The text search uses the 'simple' configuration rather than English. These are an agent's own notes, in whatever language it chose, and stemming English rules over Turkish would quietly stop finding things. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
58 lines
2.8 KiB
SQL
58 lines
2.8 KiB
SQL
-- Ported from the SQLite migration of the same number. Same version, same
|
|
-- meaning, different spelling: SQLite's storage classes and PostgreSQL's types
|
|
-- do not line up, and one shared file would be a promise neither could keep.
|
|
--
|
|
-- Times are milliseconds since the epoch in BIGINT, exactly as in SQLite,
|
|
-- rather than TIMESTAMPTZ. The Go code reads and writes int64 milliseconds
|
|
-- everywhere; converting here would mean two time representations in one
|
|
-- codebase, and the conversion would be the thing that is wrong at 2am.
|
|
|
|
-- Server-side conversations, so a thread can continue on a different model
|
|
-- than it started on.
|
|
--
|
|
-- Off unless a project turns it on. Until this migration ran, llmgw stored no
|
|
-- message content anywhere — usage rows carry token counts and costs, never
|
|
-- text — and that stays true for every project that does not ask. An on-prem
|
|
-- gateway keeping nothing anyone typed is much easier to put in front of a
|
|
-- team than one keeping everything, so storing is a choice a project makes
|
|
-- rather than a default it discovers.
|
|
|
|
-- retention_hours is the switch and the limit at once. NULL means this project
|
|
-- stores nothing, which is every project that existed before this and every
|
|
-- one created since without asking.
|
|
--
|
|
-- One column rather than a boolean and a duration: a thread store with no
|
|
-- expiry is a transcript of everything a team ever typed, kept forever by
|
|
-- omission. Turning it on therefore requires saying for how long.
|
|
ALTER TABLE projects ADD COLUMN thread_retention_hours BIGINT;
|
|
|
|
CREATE TABLE threads (
|
|
id TEXT PRIMARY KEY,
|
|
-- Scoped to a project, and checked on every read. Thread ids are opaque, but
|
|
-- an unguessable id is a secret and secrets leak into logs, shell history
|
|
-- and bug reports; the scope is what makes a leaked id useless to someone
|
|
-- outside the project.
|
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
created_at BIGINT NOT NULL,
|
|
-- Touched on every append, so retention measures idleness rather than age:
|
|
-- expiring a conversation someone is still having would be a strange way to
|
|
-- keep a promise about storage.
|
|
updated_at BIGINT NOT NULL
|
|
);
|
|
CREATE INDEX threads_by_project ON threads(project_id);
|
|
CREATE INDEX threads_by_updated ON threads(updated_at);
|
|
|
|
CREATE TABLE thread_messages (
|
|
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
|
|
-- Position rather than a timestamp, because order is the point and two
|
|
-- messages can share a millisecond.
|
|
seq BIGINT NOT NULL,
|
|
role TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
-- Which model produced an assistant turn. A thread that moved between models
|
|
-- otherwise reads as one voice, and when the answers disagree that is the
|
|
-- first thing anyone wants to know.
|
|
model TEXT,
|
|
PRIMARY KEY (thread_id, seq)
|
|
);
|