Schema & migrations

Every module lives in its own schema. No foreign key crosses schemas: owner_id is referenced by UUID, without REFERENCES core.users.

First migration #

-- migrations/000001_memo_schema.up.sql
CREATE SCHEMA IF NOT EXISTS memo;

CREATE TABLE memo.notes (
    id         UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    owner_id   UUID NOT NULL,                 -- core.users.id, PAS de FK inter-schéma
    title      VARCHAR(500) NOT NULL,
    body       TEXT NOT NULL DEFAULT '',
    color      VARCHAR(7) NOT NULL DEFAULT '#1a73e8',
    pinned     BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_memo_notes_owner ON memo.notes(owner_id);

The twin .down.sql file undoes the migration (DROP TABLE memo.notes;).

Tip

Since migrations run with search_path = "memo,public", you can write notes without the prefix — but spelling out memo.notes is more explicit.

Offline SQLx #

To build without a database (CI, packaging): ship the .sqlx cache and compile with SQLX_OFFLINE=true. Regenerate it after any query change:

DATABASE_URL=postgres://… cargo sqlx prepare   # puis commitez .sqlx
Note

The SQL above is identical whatever the language of the module. Only the tool applying the migrations changes (sqlx in Rust; Alembic/Phinx, golang-migrate, etc. elsewhere) — remember to pin the search_path to your schema.