Содержание урока · 12 разделов
- sqlc схемы: домены внутри одной базы
- search_path: как sqlc видит неквалифицированные имена
- sqlc.yaml multiple: список sql, пакет на запись
- Разделение доменов: свои queries и свой пакет
- Модели на стыке: кто получит какие структуры
- Общие типы: глобальные overrides
- emit-опции: короткое повторение перед разветвлением
- Два пакета рядом: кто что получает
- Что дальше
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
К семнадцатому уроку у трекера три таблицы и десятки методов. Задачи с их фильтрами и страницами — сердце продукта; пользователи — исполнители, с которыми задачи соединяются джойном; журнал списаний — бухгалтерский домен, куда одиннадцатый урок положил суммы за закрытые задачи. Всё это десятилетиями (то есть шестнадцатью уроками) жило в одном пакете db: одна папка db/queries с файлами по темам, один models.go, один Querier, в котором CreateSpending стоит соседом с SearchTodos. Пока доменов было ноль, а тем — много, это работало.
Потом появляются боли, каждая из которых знакома любому, кто обслуживал выросший проект. Первая: Querier — это список всех методов проекта сразу, и на ревью вопроса «кто владеет этим запросом» отвечает только комментарий. Вторая: файлы в db/queries перемешаны по доменам — spendings.sql лежит рядом с pagination.sql, и ничто, кроме конвенции, не мешает новому коллеге добавить бухгалтерский запрос в файл задач. Третья: изменения домена не видны на границе — из сигнатур не видно, что панель фильтров не имеет права трогать журнал списаний.
У Postgres для этого есть встроенный инструмент — схемы: пространства имён внутри одной базы. У sqlc — своя часть ответа: несколько записей в sqlc.yaml, каждая со своей папкой queries и собственным Go-пакетом на выходе. Урок соединяет оба уровня: сначала схема в базе и то, как генератор её читает, потом пакеты в конфиге и то, как они делят код по доменам.
Честно о формате, как и во всём курсе: тема требует живой базы и локального генератора, поэтому уроки не выполняются в песочнице — runner-ов здесь нет. Понимание проверяют quiz, predict, fill, order и флешкарты: они гоняют те же факты, что вы повторите командами CREATE SCHEMA и sqlc generate у себя. Поведение генератора сверено с документацией sqlc (раздел Configuration: список sql, ключи gen.go, omit_unused_structs, output_models_file_name, глобальные overrides) и с исходниками генератора: разбор CREATE SCHEMA, префикс схемы в Go-именах, разрешение неквалифицированных имён только через схему по умолчанию.
sqlc схемы: домены внутри одной базы#
Схема в Postgres — это пространство имён внутри одной базы: в ней живут таблицы, перечисления, представления и функции. Иерархия выглядит так: кластер содержит базы, база содержит схемы, схема содержит таблицы. В каждой базе есть схема по умолчанию — public, и шестнадцать уроков трекер честно прожил в ней: CREATE TABLE без квалификатора создаёт таблицу именно там. Схема — не база: это самая частая путаница темы. Разделение доменов по схемам не разводит данные по разным серверам и даже по разным файлам на диске — оно разводит имена и правила внутри одной базы.
Зачем домену собственное пространство имён. Первое — границы видны в самом SQL: запрос к billing.spendings сам объявляет, что трогает бухгалтерию, и в ревью это видно без знания контекста. Второе — права: Postgres выдаёт доступы на уровне схемы, и можно завести роль, которой разрешено читать billing и запрещено менять public — segregated доступ без единой строчки кода приложения. Третье — имена: у схемы свой словарь, и таблица журнала может называться просто spendings, не споря с будущими сущностями задач. Четвёртое — жизненный цикл: домен можно выгрузить одной командой DROP SCHEMA, не выписывая по таблице.
Применяем к трекеру: журнал списаний из одиннадцатого урока переезжает в схему billing — выделенное пространство бухгалтерии. Важная деталь, о которой часто спотыкаются: внешний ключ через схемы законен. billing.spendings может ссылаться на todos из public — обе таблицы живут в одной базе, и ссылочная целостность не знает о наших доменных границах. Домены — договор людей; база продолжает видеть одно пространство данных.
Схема billing: шаг, которым домен получает имя
GO-- db/migrations/000004_billing.sql: домен списаний получает собственную схему
CREATE SCHEMA billing;
CREATE TABLE billing.spendings (
id BIGSERIAL PRIMARY KEY, -- int64 в Go
todo_id BIGINT NOT NULL REFERENCES todos (id), -- внешний ключ через схемы законен
amount BIGINT NOT NULL, -- сумма в копейках
created_at TIMESTAMPTZ NOT NULL DEFAULT now() -- время ставит база
);
-- db/queries/spendings/spendings.sql: имена квалифицированы схемой
-- name: CreateSpending :exec
INSERT INTO billing.spendings (todo_id, amount)
VALUES ($1, $2);Два движения в одном шаге. Первое: CREATE SCHEMA billing — таблица создаётся сразу с префиксом, потому что «текущей схемы» у sqlc не существует: генератор не исполняет SET, и после CREATE SCHEMA ничего не переключается — в живой базе тоже лучше не полагаться на переключение, а писать полные имена. Второе: запрос уже квалифицирован — INSERT INTO billing.spendings; это и есть тот стиль, который переживёт разветвление конфига ниже. Миграция 000004 продолжает нумерацию канона: базовая схема, приоритет, мягкое удаление — и теперь домен бухгалтерии.
search_path: как sqlc видит неквалифицированные имена#
В живой базе за короткие имена отвечает search_path — список схем, в которых Postgres ищет неквалифицированные ссылки: SET search_path TO billing, public, и запрос FROM spendings найдёт таблицу в billing, если там она есть. Удобно, но именно на этом месте генератор и база расходятся в поведении, и урок обязан сказать это прямо: search_path sqlc не эмулирует. Каталог генератора собирается из schema-файлов, и неквалифицированное имя в запросе разрешается только в схеме по умолчанию — public. Настройки search_path в конфиге не существует: вопрос о ней в репозитории sqlc открыт как enhancement (Configure search_path), а оператор SET генератор не исполняет вовсе.
Практическое следствие одно, но железное: в проекте с несколькими схемами всё, что лежит вне public, пишется квалифицированно — и в DDL, и в запросах. Короткое FROM spendings при таблице billing.spendings не «не найдётся тихо» — генерация честно упадёт с ошибкой вида relation "spendings" (код 42P01, неопределённая таблица); падение здесь — лучший исход, потому что оно ловится на ноутбуке.
Есть у схем и подарочная сторона — имена в Go. Для таблиц вне схемы по умолчанию sqlc строит имя по шаблону схема_таблица: billing.spendings превращается в структуру BillingSpending, а перечисление billing.payment_status — в тип BillingPaymentStatus с константами вида BillingPaymentStatusCaptured. В public всё остаётся как прежде — канонический Todo без префиксов. Границы домена таким образом читаются прямо в коде приложения: один взгляд на сигнатуру, и видно, чью таблицу вы держите в руках.
Предскажите имена структур
Прочитайте код и выберите, что он выведет:
-- db/migrations/000004_billing.sql: схема и домен
CREATE SCHEMA billing;
CREATE TYPE billing.payment_status AS ENUM ('captured', 'refunded');
CREATE TABLE billing.spendings (
id BIGSERIAL PRIMARY KEY,
todo_id BIGINT NOT NULL REFERENCES todos (id),
amount BIGINT NOT NULL,
status billing.payment_status NOT NULL DEFAULT 'captured'
);
-- db/queries/spendings/spendings.sql
-- name: CreateSpending :exec
INSERT INTO billing.spendings (todo_id, amount, status)
VALUES ($1, $2, $3);Проверка: короткое имя против квалифицированного
Таблица создана как billing.spendings, в каталоге sqlc схема billing существует. Коллега пишет запрос SELECT * FROM spendings без квалификатора и запускает sqlc generate. Что произойдёт?
sqlc.yaml multiple: список sql, пакет на запись#
Теперь сторона конфига. Ключ sql в sqlc.yaml — не объект, а список: каждая запись — самостоятельный набор генерации со своим движком, своей схемой, своими запросами и своим генератором кода. Доки прямо показывают конфиг из двух записей с разными движками; у каждой записи набор ключей один: name — необязательный человекочитаемый идентификатор набора, engine, schema — папка миграций или файл, queries — папка запросов или список путей, gen — генератор, плюс database и rules для проверок. Ключ name при одном пакете бесполезен, при двух-трёх уже помогает читать конфиг: записи перестают быть безымянными блоками yaml.
Главное следствие списка: на каждую запись — свой Go-пакет. В gen.go пишутся package — имя пакета на языке Go, и out — каталог, куда ляжет сгенерированный код; разные out — разные каталоги — разные пакеты с независимыми импортами. Кто-то ищет это под словами sqlc.yaml multiple packages, и поисковик приводит ровно сюда: список sql — штатный, документированный способ держать в одном файле конфигурации сколько угодно наборов генерации.
И второе следствие, менее очевидное: записей можно сколько угодно, а команда одна. sqlc generate обходит весь список за один запуск — каждый пакет обновляется сам. Так прячется ещё и ответ на вопрос об организации sqlc: один файл конфигурации, несколько точек выхода, границы домена видны и в конфиге, и в импортах. Соседняя запись может отличаться даже движком: в доках это postgresql рядом с mysql; для трекера обе записи — postgresql, но дорога к восемнадцатому уроку про MySQL уже лежит в этой конструкции — конфиг к смене движка готов, останется выучить отличия синтаксиса.
Канон урока: один конфиг, два пакета
GO# sqlc.yaml: список sql — по записи на домен
version: "2"
sql:
- name: "todos"
engine: "postgresql"
schema: "db/migrations"
queries: "db/queries/todos"
gen:
go:
package: "tododb"
out: "internal/store/tododb"
sql_package: "pgx/v5"
- name: "spendings"
engine: "postgresql"
schema: "db/migrations"
queries: "db/queries/spendings"
gen:
go:
package: "spendingdb"
out: "internal/store/spendingdb"
sql_package: "pgx/v5"Разберём канон построчно. sql — список из двух записей, у каждой своё имя: todos и spendings. schema у обеих записей один — db/migrations: папка миграций остаётся единственным источником правды о схеме, как договорились в шестнадцатом уроке; обе записи собирают один и тот же каталог базы и потому видят одни и те же таблицы — включая чужой домен. Различаются записи queries (собственная папка домена) и gen.go: tododb едет в internal/store/tododb, spendingdb — в internal/store/spendingdb. Один sqlc generate обработает обе записи и обновит оба каталога. sql_package: pgx/v5 наследуется из канона второго урока — у обоих пакетов один драйвер, поэтому транзакционные сценарии через два домена остаются совместимыми.
Проверка: сколько запусков генерации
В sqlc.yaml две записи sql: tododb и spendingdb. Разработчик поправил запрос в db/queries/spendings и хочет обновить сгенерированный код. Сколько раз нужно запустить sqlc generate?
Разделение доменов: свои queries и свой пакет#
Конвенция курса «одна тема выборок — один файл» выросла в правило уровнем выше: один домен — папка. В db/queries/todos живут todos.sql — списки, фильтры и джойны задач — и users.sql — домен исполнителей, который задачами пользуется, но владеет своими таблицами; сюда же переезжают панель фильтров и страницы. В db/queries/spendings — spendings.sql, журнал списаний со своей схемой billing. Ключ queries у записи принимает папку — или список путей, если домену нужно собрать файлы из нескольких мест; ни один файл не принадлежит двум пакетам, и это не ограничение, а граница: запрос можно найти по домену, домен — по пакету.
Каждый пакет получает полный набор сгенерированных файлов: свой db.go с конструктором и интерфейсом соединения, свои модели, свой Querier — интерфейс, в котором только методы этого домена. Граница становится компиляторной: сервис задач принимает *tododb.Queries и физически не может вызвать CreateSpending — такого метода в его типе нет.
Где жить запросам на стыке? Правило владельца: запрос принадлежит домену главного данных. ListTodosWithUser — джойн задач с исполнителями — живёт в todos.sql: задачу показывает пользовательский интерфейс, пользователь здесь деталь задачи. Транзакция из одиннадцатого урока — закрыть задачу и записать списание — затрагивает оба домена, и с двумя пакетами сценарий не ломается: оба пакета построены над одним контрактом соединения, на одном транзакционном соединении можно держать по объекту Queries от каждого домена; подробности механики — в том уроке, здесь важна фактура: разветвление пакетов не ломает транзакции, потому что делит методы, а не соединения.
Дерево проекта и два импорта вместо одного
GO# дерево после разветвления: домены получили папки и пакеты
db/
migrations/ # одна папка схемы на все пакеты
000001_init.up.sql
000002_add_priority.up.sql
000003_soft_delete.up.sql
000004_billing.sql
queries/
todos/ # запись "todos" -> пакет tododb
todos.sql # задачи: списки, фильтры, джойны
users.sql # исполнители: свой домен в том же пакете
spendings/ # запись "spendings" -> пакет spendingdb
spendings.sql # журнал: схема billing
internal/
store/
tododb/ # сгенерировано: db.go, models.go, querier.go
spendingdb/ # сгенерировано: db.go, billing_models.go, querier.go
// internal/service/dashboard.go: два домена — два импорта
import (
"tracker/internal/store/spendingdb"
"tracker/internal/store/tododb"
)
// Dashboard держит по объекту Queries на домен: задачи в tododb,
// суммы — в spendingdb, где таблицы живут в схеме billing
type Dashboard struct {
Todos *tododb.Queries
Spendings *spendingdb.Queries
}Как читать дерево. Миграции не разветвились: сумма up-файлов по-прежнему описывает всю базу, и оба пакета смотрят на неё как на источник схемы. Запросы разветвились строго по доменам. Путь импорта складывается из модуля и out — tracker/internal/store/tododb, и в коде приложения теперь два импорта вместо одного. Имя billing_models.go в spendingdb — не опечатка: имя файла моделей настраивается, и на этом месте урока мы его настроим; по умолчанию файл называется models.go. Каждая из структур Queries компилируется независимо — правка домена задач не трогает журнал списаний.
Модели на стыке: кто получит какие структуры#
Раз оба пакета смотрят на одну папку миграций, честный вопрос — что окажется в моделях каждого. Ответ генератора прост до грубости: models.go каждого пакета строится по всему входу схемы. Раз в db/migrations описаны todos, users и billing.spendings, то и Todo, и User, и BillingSpending появятся в обоих пакетах — независимо от того, чьи запросы лежат в папке. Это не баг, а следствие устройства: генератор печатает модели по каталогу, а не по упоминаниям в запросах.
Дубль — не всегда зло: пакеты независимы, их можно версировать и переписывать порознь, и для среднего проекта две копии структур — приемлемая плата за простоту. Но у конструкции есть три ручки настройки, и урок перечисляет все три. Первая — omit_unused_structs: включённая, она оставляет в пакете только те структуры таблиц и перечислений, которые реально встречаются в его запросах; spendingdb перестаёт таскать Todo, если его запросы не касаются задач. Вторая — имена файлов: output_models_file_name меняет models.go на что угодно — например, billing_models.go; рядом живут output_db_file_name и output_querier_file_name для db.go и querier.go. Третья — само входное множество: schema может быть списком путей, и можно было бы развести миграции по доменам; но тогда внешний ключ billing.spendings → todos перестанет проверяться генератором в пакете spendings, потому что todos уйдёт из его поля зрения. Общая папка — компромисс, при котором границы проверяются целиком; усечение моделей дешевле усечения схемы.
Предскажите содержимое models.go
Прочитайте код и выберите, что он выведет:
# sqlc.yaml — обе записи смотрят на одну схему:
# schema: "db/migrations" (там: todos, users, billing.spendings)
# omit_unused_structs не включён — настройки по умолчанию
-- db/queries/spendings/spendings.sql — единственный файл записи "spendings"
-- name: CreateSpending :exec
INSERT INTO billing.spendings (todo_id, amount) VALUES ($1, $2);
-- name: TotalSpent :one
SELECT sum(amount)::bigint FROM billing.spendings WHERE todo_id = $1;Усечение моделей и имя файла
GO# sqlc.yaml: обе записи целиком — ручки настройки на месте
version: "2"
sql:
- name: "todos"
engine: "postgresql"
schema: "db/migrations"
queries: "db/queries/todos"
gen:
go:
package: "tododb"
out: "internal/store/tododb"
sql_package: "pgx/v5"
omit_unused_structs: false # фасад задач: пусть несёт все модели
- name: "spendings"
engine: "postgresql"
schema: "db/migrations"
queries: "db/queries/spendings"
gen:
go:
package: "spendingdb"
out: "internal/store/spendingdb"
sql_package: "pgx/v5"
omit_unused_structs: true # только то, что трогают запросы пакета
output_models_file_name: "billing_models.go"Канон с ручками. omit_unused_structs выключен в tododb — пакет задач остаётся общим фасадом, где модели всех таблиц под рукой; включён в spendingdb — бухгалтерский пакет держит только собственное. Усечение касается именно табличных моделей и перечислений: структуры параметров и результатов вроде CreateSpendingParams и TotalSpentRow рождаются из запросов и живут всегда. output_models_file_name задаёт имя файла моделей вместо models.go по умолчанию — в дереве выше spendingdb уже показан с billing_models.go; соседние ключи тем же способом переименовывают db.go и querier.go, если в одном каталоге генерируется несколько наборов. Один запуск sqlc generate обновит оба пакета с новыми настройками — ручки не требуют отдельной команды.
Общие типы: глобальные overrides#
Остаётся стык, который не решается ни схемами, ни пакетами: один и тот же смысл в двух доменах. Суммы в копейках хранятся BIGINT и в задачах (стоимость), и в журнале (списания); в Go хочется видеть их не голым int64, а типом Money из общего пакета types, с методами и гарантиями. Если настраивать переопределение в каждой записи отдельно, рано или поздно записи разъедутся: одна получит обновление, вторая — забудет.
Для этого у конфига есть верхний этаж — глобальные overrides. Блок overrides размещается на верхнем уровне sqlc.yaml, вне списка sql, и документируется фразой, которая почти дословно описывает наш случай: иногда одна и та же конфигурация нужна сразу нескольким наборам генерации. Внутри — два раздела: rename переименовывает поля структур по имени колонки во всех пакетах разом (классика — id в Identifier), а overrides задаёт отображение типов: db_type указывает тип базы, go_type — куда его спроецировать, вплоть до import, package и type в чужом модуле. У каждого правила глобального блока есть и ключ engine — он нужен только в конфигах, где определения различаются движками; в однодвижковом проекте его можно не писать, значение возьмётся из текущей записи.
Правило выбора этажа простое. Одинаковое для всех пакетов — наверх, в глобальный overrides: один источник, ноль расхождений. Различающееся — внутрь gen.go конкретной записи, где у каждого пакета свои локальные настройки. И аккуратность с go_type: тип из вашего модуля должен быть совместим со сканированием колонки — это уже ваш контракт с драйвером, генератор только вставляет имя.
Верхний этаж конфига: правила для всех пакетов разом
GO# sqlc.yaml: глобальные overrides — до списка sql
version: "2"
overrides:
go:
rename:
id: "Identifier" # каждое поле id во всех пакетах станет Identifier
overrides:
- db_type: "pg_catalog.int8" # BIGINT — суммы в копейках
engine: "postgresql"
go_type:
import: "tracker/internal/types"
package: "types"
type: "Money" # int64 заменяется на types.Money везде
sql:
# ... две записи пакетов из канона выше — без измененийБлок overrides стоит рядом с sql, на верхнем уровне, и действует на каждую запись списка. rename говорит: колонка id в любом пакете станет полем Identifier. overrides проецирует BIGINT (внутреннее имя pg_catalog.int8) на types.Money из вашего модуля — и tododb, и spendingdb ссылаются на один и тот же тип, никакого дубля смысла. Ключ engine в правиле — атрибут именно глобального этажа: он различает правила в многодвижковых конфигах, а в однодвижковом его можно опустить. После правки — sqlc generate, один запуск, и оба пакета переезжают на Money. Локальные переопределения внутри gen.go никуда не деваются: этажи складываются, локальное уточняет глобальное.
Проверка: какой этаж конфига
Команда решила: поле id во всех структурах всех пакетов должно называться Identifier. Где место этой настройки в sqlc.yaml с двумя записями?
emit-опции: короткое повторение перед разветвлением#
Настройки gen.go, которые второй и четвёртый уроки разбирали по одному, живут на пакет — и теперь это перестаёт быть деталью. Краткий словарь тем урока. emit_json_tags добавляет структурам json-теги: включайте там, где структуры едут в HTTP-ответы, а не во всём подряд. emit_interface печатает интерфейс Querier поверх структуры — граница для подмены реализации; пакет задач с панелью фильтров выигрывает от неё больше, чем журнал. emit_exact_table_names заставляет имена структур точно повторять имена таблиц без единения множественного числа. emit_empty_slices возвращает пустой срез вместо nil из выборок-списков. emit_pointers_for_null_types проецирует nullable-колонки в указатели — для pgx/v5 это текущий канон курса. Здесь важен не словарь, а принцип: значение каждой настройки по умолчанию одинаково во всех пакетах, но включать их можно по-разному — домены настраиваются порознь, и это осознанное решение, а не расхождение версий конфига.
Два пакета рядом: кто что получает#
| Аспект | Пакет tododb | Пакет spendingdb |
|---|---|---|
| Запись в sqlc.yaml | name: todos, queries: db/queries/todos | name: spendings, queries: db/queries/spendings |
| Вход схемы | db/migrations целиком | db/migrations целиком |
| Таблицы в SQL | public: todos, users | billing: spendings |
| package и out | tododb → internal/store/tododb | spendingdb → internal/store/spendingdb |
| Модели | Todo, User, BillingSpending | только затронутое запросами: BillingSpending (omit_unused_structs) |
| Файл моделей | models.go | billing_models.go (output_models_file_name) |
Восстановите шаг домена бухгалтерии
Три пропуска: создание схемы и два квалифицированных имени — в таблице и в запросе.
Соберите разветвление трекера на домены
Девять строк: проект с одним пакетом db превращается в два пакета — задачи и бухгалтерия. Шаги идут от папок к конфигу, от генерации к коду приложения.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: несколько схем и организация
Что дальше#
Трекер разветвился по всем этажам сразу. В базе журнал списаний получил собственную схему billing, и SQL-запросы читаются как объявления домена. В конфиге ключ sql стал списком из двух записей: schema у них общая — папка миграций остаётся единственной правдой о структуре, — queries делятся по доменам, а gen.go выдаёт два независимых пакета с собственными моделями и интерфейсами. Дубль моделей усекается omit_unused_structs, имена файлов настраиваются, а смыслы, общие для доменов, живут на верхнем этаже глобальных overrides. Так и собирается организация sqlc: граница домена — в схеме, в папке queries и в импорте пакета одновременно.
Следующий шаг — другой движок: восемнадцатый урок расскажет про MySQL — чем отличается синтаксис, как меняются типы и что из привычных конструкций не переносится; конструкция «две записи в одном конфиге» — та самая дверь, в которую MySQL входит без переписывания проекта. Конфиг с путями и базовыми настройками — во втором уроке; папка миграций как источник схемы — в шестнадцатом; план остальных тем — на странице курса sqlc.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Схема Postgres — пространство имён внутри одной базы; умолчание — public. Домен в собственной схеме даёт границы в SQL, права на уровне схемы и свободный словарь имён; внешний ключ через схемы законен.
- sqlc разбирает CREATE SCHEMA, но search_path не эмулирует: неквалифицированные имена ищутся только в public, ключа настройки нет, SET не исполняется. Всё вне public — квалифицированные имена в DDL и запросах.
- Таблицам и перечислениям вне public генератор добавляет префикс схемы: billing.spendings → BillingSpending, billing.payment_status → BillingPaymentStatus. В public — прежние простые имена.
- sqlc.yaml multiple: ключ sql — список, каждая запись — name, engine, schema, queries и gen.go со своим package/out. Один sqlc generate обходит все записи; на домен — свой Go-пакет с собственными моделями и Querier.
- Разделение доменов: один домен — папка queries и запись конфига; запрос на стыке принадлежит домену главного данных; транзакционные сценарии через два домена не ломаются — пакеты делят методы, а не соединения.
- Модели строятся по всему входу схемы обоими пакетами: дубль усекается omit_unused_structs, имя файла меняет output_models_file_name; одинаковое для всех пакетов — в глобальные overrides (go.rename, go_type с import), различное — в локальные настройки gen.go.
- emit-настройки живут на пакет и включаются порознь: emit_json_tags, emit_interface, emit_exact_table_names, emit_empty_slices, emit_pointers_for_null_types — словарь из второго и четвёртого уроков, применяемый на границах домена.
Домашнее задание
Разведите трекер на домены. Первое: примените к локальной базе шаг 000004_billing.sql — CREATE SCHEMA billing и таблицу billing.spendings со внешним ключом на todos; убедитесь в psql командой \dn, что схема появилась, и что INSERT в billing.spendings со ссылками на реальные задачи проходит. Второе: попробуйте создать таблицу без префикса сразу после CREATE SCHEMA и найдите, в какой схеме она оказалась; объясните письменно, почему sqlc не увидел бы её в billing. Третье: сломайте генерацию коротким именем — напишите SELECT * FROM spendings без квалификатора и прочитайте ошибку несуществующей таблицы; почините до FROM billing.spendings. Четвёртое: создайте папки db/queries/todos и db/queries/spendings, разнесите файлы по доменам и соберите канонический конфиг урока с двумя записями; запустите sqlc generate один раз и проверьте, что оба каталога internal/store обновились. Пятое: найдите в spendingdb структуру BillingSpending и объясните себе префикс по формуле схема_таблица; создайте в схеме billing перечисление payment_status и сверните его в BillingPaymentStatus. Шестое: включите omit_unused_structs в spendingdb, перегенерируйте и убедитесь, что Todo исчез из бухгалтерского пакета, а CreateSpendingParams остался; затем переименуйте файл моделей через output_models_file_name. Седьмое: вынесите общий тип Money в internal/types и подключите его глобальным overrides с db_type: pg_catalog.int8; проверьте, что поле Amount в обоих пакетах стало Money после одного запуска генерации. Восьмое: перепишите сводку Dashboard из урока на два импорта и два объекта Queries; опишите в README проекта, где живут границы доменов — в схеме, в папке queries или в импорте, и почему все три ответа правильные.
Личные заметкиПусто
Вопросы и ответы по уроку
Почему структура называется BillingSpending, а не просто Spending?
Для таблиц вне схемы по умолчанию генератор строит имя по шаблону схема_таблица и приводит его к Go-стилю: billing_spendings → BillingSpending. Префикс сохраняет связь с квалифицированными именами SQL — в коде так же видно, чья таблица, как и в запросе. Настройки, отключающей префикс, нет: в схеме public префикс просто не нужен, там генерация остаётся прежней.
Как sqlc разрешает короткие имена таблиц, если схем несколько?
Никакой search_path-логики у генератора нет: неквалифицированное имя ищется только в схеме по умолчанию — public. Ключа search_path в конфиге не существует, а оператор SET он не исполняет. Если таблица живёт в другой схеме, ссылайтесь на неё квалифицированно — billing.spendings; короткое имя мимо public упадёт с ошибкой несуществующей таблицы на этапе генерации.
Стоит ли делить проект на пакеты, если таблиц пока мало?
Не обязательно: один пакет с файлами по темам — честный формат небольшого проекта. Пакеты стоит заводить, когда появился второй домен с собственными правилами жизни: свои права в базе, свой темп изменений, своя команда. Разветвление обратимо и дёшево: папки queries, записи в списке sql, один запуск генерации — а граница, которую охраняет компилятор, окупается с первого конфликта на ревью.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
ent: графовая ORM с кодогенерацией
Курс ent: схемы, поля, связи, запросы, хуки, privacy, миграции и GraphQL. 25 уроков с примерами и проектом микроблога.
- Проектировать схемы и связи в ent
- Генерировать код и жить с кодогенерацией
- Писать запросы builder-стилем с типами
- Строить хуки, интерсепторы и privacy-правила