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>
42 lines
2.0 KiB
SQL
42 lines
2.0 KiB
SQL
-- Ported from the SQLite migration of the same number, and the only one that
|
|
-- is not a mechanical translation.
|
|
--
|
|
-- SQLite gets full-text search from an FTS5 virtual table with an external
|
|
-- content table and three triggers keeping it in step. PostgreSQL derives a
|
|
-- tsvector from the row itself, so there is nothing to keep in step and no
|
|
-- trigger to forget: the column cannot disagree with the row it is computed
|
|
-- from.
|
|
--
|
|
-- The search query differs too, and that difference lives in the Go code
|
|
-- rather than being hidden here — a query that means "match these words" is
|
|
-- spelled MATCH in one and @@ in the other, and pretending otherwise would
|
|
-- cost more than admitting it.
|
|
|
|
ALTER TABLE projects ADD COLUMN memory_retention_hours BIGINT;
|
|
|
|
CREATE TABLE memory_notes (
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
-- Scoped to a project and checked in SQL on every read, like threads.
|
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
-- A namespace inside the project, so two agents sharing a key do not read
|
|
-- each other's notes.
|
|
agent_id TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
text TEXT NOT NULL,
|
|
-- Normalised to ",a,b," so a tag matches whole, never as part of another.
|
|
tags TEXT NOT NULL DEFAULT '',
|
|
created_at BIGINT NOT NULL,
|
|
-- Touched on every write, so retention measures how long a note has gone
|
|
-- unrefreshed rather than how old it is.
|
|
updated_at BIGINT NOT NULL,
|
|
-- 'simple' rather than a language configuration: these are an agent's own
|
|
-- notes, in whatever language it chose, and stemming English rules over
|
|
-- Turkish text would quietly stop finding things.
|
|
search tsvector GENERATED ALWAYS AS (
|
|
to_tsvector('simple', coalesce(key, '') || ' ' || coalesce(text, '') || ' ' || coalesce(tags, ''))
|
|
) STORED,
|
|
UNIQUE (project_id, agent_id, key)
|
|
);
|
|
CREATE INDEX memory_notes_by_updated ON memory_notes(updated_at);
|
|
CREATE INDEX memory_notes_search ON memory_notes USING GIN (search);
|