Adding type-safe SQL queries
Owncast uses sqlc to turn hand-written SQL into type-safe Go. You write the SQL, sqlc generates the Go. Two pieces work together:
- Schema lives in
persistence/migrations/as numbered goose SQL migrations. These are the source of truth for the table shapes, and they run automatically on startup. - Queries live in
db/query.sql. sqlc reads them along with the schema (seesqlc.yaml) and generatesdb/query.sql.goanddb/models.go.
There is no schema.sql. sqlc derives the schema from the migrations.
Changing the schema or adding a queryโ
- Write a migration. Create the next numbered file in
persistence/migrations/, for example00005_add_widget_color.sql. Put theALTER TABLEorCREATE TABLEbetween the gooseUpmarkers and write the reverse underDown:
-- +goose Up
-- +goose StatementBegin
ALTER TABLE users ADD COLUMN widget_color TEXT NOT NULL DEFAULT '';
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE users DROP COLUMN widget_color;
-- +goose StatementEnd
Never edit a migration that has already shipped. Add a new one instead, keep the numbers sequential, and prefer IF NOT EXISTS where it makes sense.
- Update
db/query.sqlif you need to read or write the new column. - Regenerate the Go code with
make sqlc. This installs sqlc into./binon first run. You only need it to change SQL, not to build Owncast. - Build with
go build ./.... The migration applies on the next startup, with no manual step. - Commit both your application code and the generated files.
Don't hand-write raw SQL in Go for new work. Add it to db/query.sql and regenerate.
Repositories that aren't sqlc-managedโ
A few older repositories still use hand-written SQL and aren't wired into sqlc, such as persistence/webhookrepository/. For those, make sqlc generates nothing. The schema change still goes in a goose migration, but you edit the raw SQL and the matching struct fields by hand. Watch for SELECT * paired with a positional rows.Scan(...): a column added by ALTER TABLE lands last, so add it to the end of the Scan argument list or the scan fails at runtime.
For the full version, see db/README.md in the repository.