Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 2 из 25 ~20 мин +50 XP

Установка и настройка sqlc.yaml

sqlc поставляется одной командой (разбирали в [первом уроке](/courses/sqlc/lessons/01-znakomstvo/)), но запустить его сразу после установки не выйдет: без конфига генератор не знает ни движка, ни путей к SQL, ни куда писать Go-код. Настройка sqlc живёт в одном файле — sqlc.yaml. Соберём структуру проекта трекера задач, пройдём по каждому ключу sqlc yaml: version "2", движки, папки schema и queries, блок gen.go — и заглянем в сгенерированные db.go и models.go, чтобы понимать, за что отвечает каждая строка конфига.

Содержание урока · 9 разделов

sqlc — инструмент конфигурационный до мозга костей. У него нет флагов вида --schema или --out, которые приходилось бы передавать при каждом запуске: и генерация, и проверки читают один и тот же файл sqlc.yaml из корня проекта. Это решение со следствиями, и полезно их осознать сразу. Во-первых, конфиг — единственная карта проекта: если что-то в слое данных выглядит не так, чинится оно почти всегда в sqlc.yaml или в файлах, на которые он указывает. Во-вторых, конфиг коммитится в репозиторий и у всей команды становится одинаковым: у соседа по проекту генерация не может тихо работать «с другими настройками». В-третьих, история изменений слоя данных лежит в git рядом с историей запросов — diff по sqlc.yaml читается как описание того, что изменилось в проекте.

Проверьте, что сам бинарник на месте: команда sqlc version печатает версию установленного генератора. Если команда не найдена — вернитесь к установке из первого урока. Дальше речь про содержимое конфига.

Структура проекта под sqlc

GO
tracker/
├── go.mod
├── sqlc.yaml
├── db/
│   ├── migrations/
│   │   ├── 001_init.sql
│   │   └── 002_priority.sql
│   ├── queries/
│   │   └── todos.sql
│   └── sqlc/
│       ├── db.go
│       ├── models.go
│       └── todos.sql.go
└── main.go

Три зоны. db/migrations — миграции, источник схемы: файл 001 создаёт таблицу todos, 002 добавляет priority из домашки первого урока. db/queries — аннотированные SQL-запросы. db/sqlc — сгенерированный вывод, и эту папку никто не создаёт руками: её пишет sqlc generate. Приложение импортирует db/sqlc как обычный пакет Go, а главный файл main.go живёт выше и о генерации ничего не знает. Ничто не мешает назвать папки иначе — имена задаются в конфиге, дальше увидим как.

Файл sqlc.yaml: как sqlc его находит и читает#

При запуске sqlc generate генератор ищет файл конфига в текущем каталоге: основное имя — sqlc.yaml, допустим и вариант sqlc.json, если JSON вам ближе. Отсюда первое правило гигиены: команды sqlc запускают из корня проекта, там же, где лежит go.mod. Все пути внутри конфига — относительные, и отсчитываются они от папки, в которой лежит сам sqlc.yaml, а не от того, где вы стояли в терминале. Пока конфиг один и живёт в корне, разницы не видно; когда захотите выделить генерацию в подкаталог, это знание спасёт вечер.

Формат — обычный YAML: отступы значимы, комментарии начинаются с решётки, строки-значения я всегда беру в кавычки. Кавычки необязательны, но дешёвы: они гарантируют, что yaml прочитает значение строкой, а не числом или логическим значением. Мелочь, которая раз в жизни сэкономит час: случается она ровно с одним ключом, и о нём — через минуту.

sqlc init: стартовая заготовка

GO
version: "2"
sql:
  - schema: "query.sql"
    queries: "query.sql"
    engine: "postgresql"
    gen:
      go:
        package: "tutorial"
        out: "tutorial"

Команда sqlc init из первого урока пишет в текущем каталоге заготовку конфига, похожую на эту: schema и queries указывают на один файл query.sql, генерация — в пакет tutorial. Это шаблон для быстрого старта, а не готовый проект: первой деловой правкой обычно становится разнос схемы, запросов и вывода по папкам, как в дереве выше. Обратите внимание на первую строку: version: "2" — в кавычках. Уберёте кавычки — yaml превратит значение в число, а sqlc ждёт строку, и конфиг не примется: ошибка будет выглядеть загадочно, хотя виновата одна пара кавычек. Версия "1" — легаси старого формата, новые проекты начинаются с "2".

Рабочий sqlc.yaml трекера задач

GO
version: "2"
sql:
  - engine: "postgresql"
    schema: "db/migrations"
    queries: "db/queries"
    gen:
      go:
        package: "db"
        out: "db/sqlc"
        sql_package: "database/sql"

Ключ version: "2" — формат конфига. Под ключом sql прячется список конфигураций генерации: пока их одна. Внутри записи: engine — диалект SQL; schema — где лежит схема, у нас папка миграций; queries — где лежат запросы; gen.go — что и куда генерировать: package — имя Go-пакета, out — папка вывода, sql_package — API, под который писать код. sql_package: "database/sql" — значение по умолчанию, строку можно было и не писать; я пишу её явно, чтобы выбор драйвера был виден в конфиге, а не в голове.

schema, queries и engine: что читает генератор#

schema и queries гибче, чем кажется: значение может быть одним файлом, папкой или списком путей. Мы в первом уроке писали схему в один schema.sql — это тоже законный вариант для маленького проекта. Когда проект растёт, на место файла приходит папка миграций, и sqlc читает её файлы в алфавитном порядке, складывая итоговую схему: поэтому миграции нумеруют (001_init.sql, 002_priority.sql). Встроена поддержка файлов популярных инструментов миграций — golang-migrate, goose, sql-migrate, tern: служебные комментарии этих инструментов sqlc распознаёт и пропускает, беря из файлов только SQL. Со схемой из живой базы работаем ближе к концу курса.

Каждый файл запросов из папки queries превращается в одноимённый файл генерации: todos.sql даст todos.sql.go с методами по всем аннотациям из него. Дописали в проекте сущность «проекты» — добавили db/queries/projects.sql и получили projects.sql.go, не трогая существующее.

engine выбирает диалект, по которому парсится и схема, и запросы: postgresql, mysql или sqlite. От движка зависит всё, что связано с языком SQL: типы колонок (BIGSERIAL знает только postgresql), плейсхолдеры параметров ($1 у PostgreSQL, вопрос у MySQL и SQLite — факт из первого урока), допустимые функции. Драйвер pgx имеет смысл только рядом с postgresql-движком.

Проверка: engine и диалект

В sqlc.yaml по ошибке указан engine: "mysql", а schema.sql написан по правилам PostgreSQL: CREATE TABLE с BIGSERIAL и плейсхолдерами $1. Что произойдёт при sqlc generate?

gen.go: package, out и sql_package#

Блок gen.go отвечает на два вопроса: какой Go-код писать и куда его класть. package — имя генерируемого пакета, оно попадает в первую строку каждого файла. По конвенции его делают равным последнему элементу out: пакет db в папке db/sqlc — вариант понятно, но частая практика. out — папка вывода, sqlc создаст её сам и затрёт содержимое при каждой генерации. Отсюда эргономический совет: вывод держите изолированно (db/sqlc, internal/db), чтобы рука не тянулась что-то подправить в сгенерированном файле.

sql_package выбирает API, под который пишет генератор. По умолчанию — database/sql: код работает с любым драйвером, зарегистрированным в стандартном интерфейсе. Для PostgreSQL можно указать pgx/v5 (или pgx/v4) — тогда код пишется поверх нативного API pgx. Это тот самый выбор «одной строкой» из первого урока. Важно понимать границу ответственности: конфиг меняет только сгенерированный код, а сам драйвер в проект ставится отдельно через go get — с подключением и DSN это делается в приложении, как разбирали в уроке про подключение базы в GORM — там та же система: код приложения и драйвер настраиваются вне зависимости от того, кто написал SQL.

Один конфиг, два API

GO
# вариант 1: стандартная библиотека (по умолчанию)
gen:
  go:
    package: "db"
    out: "db/sqlc"
    sql_package: "database/sql"

# вариант 2: нативный pgx
gen:
  go:
    package: "db"
    out: "db/sqlc"
    sql_package: "pgx/v5"

Разница в одной строке, но сгенерированный код получится разным: другой интерфейс DBTX, другой набор импортов, другой тип для NULL (sql.NullString у database/sql против pgtype у pgx — эту пару упоминали в первом уроке, детали в девятом). Меняется и то, что приложение передаёт в db.New: sql.DB для первого варианта, pgxpool.Pool для второго. Поэтому переключение sql_package — не только правка конфига, но и правка точки подключения.

Сгенерированный db.go: что внутри

GO
// db/sqlc/db.go
// Code generated by sqlc. DO NOT EDIT.

package db

import (
        "context"
        "database/sql"
)

type DBTX interface {
        ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
        PrepareContext(context.Context, string) (*sql.Stmt, error)
        QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
        QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}

func New(db DBTX) *Queries {
        return &Queries{db: db}
}

type Queries struct {
        db DBTX
}

func (q *Queries) WithTx(tx *sql.Tx) *Queries {
        return &Queries{
                db: tx,
        }
}

Вот где живёт DBTX — интерфейс, о котором говорил первый урок: четыре метода, и под него подходит и sql.DB, и sql.Tx, и соединение из пула. db.New оборачивает соединение в структуру Queries, WithTx подменяет его транзакцией (транзакциям посвящён одиннадцатый урок). Точная картина может слегка отличаться между версиями sqlc, но состав файла стабилен: интерфейс, конструктор, хранилище соединения. Шапка DO NOT EDIT — не украшение, а предупреждение с последствиями, о них ниже.

Сгенерированный models.go: таблица todos

GO
// db/sqlc/models.go
// Code generated by sqlc. DO NOT EDIT.

package db

type Todo struct {
        ID       int64
        Title    string
        Done     bool
        Priority int32
}

Структура по нашей таблице, поля — в порядке колонок итоговой схемы: id, title, done, priority. Соответствия типов мы уже читали в первом уроке: BIGSERIAL стал int64, TEXT — string, BOOLEAN — bool; новая колонка priority с типом INTEGER превратилась в int32. Никаких json-тегов и методов здесь нет — их добавляют опции генерации, о них чуть ниже. Пока конфиг не меняется, перегенерация даёт байт-в-байт тот же файл: детерминизм — часть контракта.

Типичные ошибки конфига#

Три классических падения генерации, и все три лечатся за минуту, если знать их в лицо. Первая — неверный путь: опечатка в schema или queries, или путь записан от другого места. sqlc ищет пути относительно папки с sqlc.yaml, и ошибка приходит в виде сообщения о том, что каталог или файл не найден, с указанием пути, — сверьте его с деревом проекта. На Windows пишите обычные прямые слэши: YAML внутри кавычек трактует обратный слэш как начало спецпоследовательности. Вторая — не тот движок: схема в диалекте одной СУБД, engine — другой (тест выше). Третья — незаполненный gen.go: без out или package sqlc откажется генерировать, требуя явно сказать, куда и под каким именем писать пакет.

Четвёртая ошибка — поведенческая, не конфигурационная: править сгенерированное вместо источника. Запомните направление потока: sqlc.yaml и .sql-файлы — это вход, db/sqlc — выход, и всё, что вы хотите изменить в выходе, начинается со входа.

emit-опции: тонкая настройка генерации#

Рядом с package, out и sql_package в gen.go живут переключатели с префиксом emit — они досыпают в генерацию дополнительные детали. Три самых ходовых. emit_json_tags: true добавляет json-теги в структуры models.go — если модели уезжают в JSON-ответы API, я включаю эту опцию всегда: писать теги руками рядом со сгенерированным файлом — путь к расхождению. emit_interface: true просит ещё один файл, querier.go, — интерфейс со всеми сгенерированными методами, который *Queries реализует целиком; в тестах такой интерфейс подменяется фейком, поэтому в сервисах с приличным тестовым слоем опция нужна. emit_prepared_queries: true генерирует код с подготовленными выражениями — но я её не включаю: выигрыш на повторном парсинге запросов редко окупает усложнение кода; передумал бы при тысячах однотипных запросов в секунду на постоянных соединениях. Есть и другие переключатели — от пустых срезов вместо nil до суффиксов имён файлов — полный список в документации sqlc; принцип тот же: включать только то, что действительно нужно, иначе генерация обрастает деталями, которые никто не использует.

Впишите emit-опции

Два переключателя генерации: json-теги в моделях и интерфейс всех методов в querier.go. Восстановите их имена.

gen: go: package: "db" out: "db/sqlc" : true # json-теги в структурах models.go : true # querier.go с интерфейсом всех методов

Предскажите генерацию: что появится в db.go?

Прочитайте код и выберите, что он выведет:

version: "2"
sql:
  - engine: "postgresql"
    schema: "db/migrations"
    queries: "db/queries"
    gen:
      go:
        package: "db"
        out: "db/sqlc"
        sql_package: "pgx/v5"

Соберите цикл изменения схемы

Добавляем колонку priority в трекер. Расставьте шаги в рабочем порядке.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Проверка: диагностика ошибки

sqlc generate падает, в сообщении фигурирует путь db/queris: no such file or directory. Папка с запросами называется db/queries. Где проблема?

Карточки: конфиг sqlc

Термин

version: "2"

нажмите, чтобы перевернуть
Определение

Формат конфига sqlc. Значение — строка в кавычках: без них yaml прочтёт число, и конфиг не примется. Версия "1" — легаси.

1 / 6

Что дальше по курсу#

Конфиг собран и проверен, папки проекта разложены, сгенерированный код перестал быть чёрным ящиком. Третий урок — первые запросы и генерация целиком: пишем выборки для трекера, запускаем sqlc generate и читаем выход построчно. Полный план из 25 уроков — на странице курса.

Пока слой данных не собран, HTTP-слогу есть где подождать: когда генерация заработает, сгенерированные методы естественно лягут в обработчики веб-фреймворка — например, в курсе Gin мы писали ровно такие обработчики, только слой данных там держала рука автора.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • sqlc.yaml — единственная карта проекта: и generate, и vet читают один конфиг из корня репозитория.
  • version: "2" — строкой в кавычках; версия "1" — легаси для старых проектов.
  • Запись в списке sql: engine задаёт диалект, schema и queries — источники (файл, папка миграций или список путей), gen.go — вывод.
  • gen.go: package — имя пакета, out — папка генерации, sql_package — database/sql по умолчанию или pgx/v5 одной строкой.
  • db.go содержит DBTX, New и WithTx; models.go — структуры таблиц; оба файла перезаписываются при каждой генерации — руками их не правят.
  • emit-опции добавляют детали: emit_json_tags (json-теги), emit_interface (querier.go), emit_prepared_queries.
  • Типичные падения конфига: опечатка в пути, чужой диалект в engine, незаполненный gen.go.

Домашнее задание

Соберите конфиг с нуля для трекера задач: сделайте папки db/migrations и db/queries, перенесите схему todos из первого урока в миграцию 001_init.sql, добавьте 002_priority.sql с колонкой priority, запустите sqlc init и доведите заготовку до движка postgresql с папками из урока, генерацией в db/sqlc. После успешного sqlc generate уроните его трижды специально: уберите кавычки у version, смените engine на mysql, сделайте опечатку в queries. Прочитайте каждое сообщение об ошибке — и откатите поломки, вернув генерацию в зелёное состояние.

Личные заметкиПусто
Хранятся только в вашем браузере (sqlc · Установка и настройка sqlc.yaml)

Вопросы и ответы по уроку

Где sqlc ищет файл sqlc.yaml?

В каталоге, из которого запускается команда, — поэтому sqlc generate гоняют из корня проекта. Допустимо и имя sqlc.json. Все пути внутри конфига относительны папке, где лежит сам файл конфига.

sql_package: database/sql или pgx/v5 — что выбрать?

database/sql — стандартная библиотека и портабельность: код работает с любым зарегистрированным драйвером. pgx/v5 — нативный API PostgreSQL: скорость и pgtype-типы. Выбор — одна строка в gen.go, но вместе с ним меняется тип соединения, который приложение передаёт в db.New.

Можно ли держать в одном конфиге две базы?

Да: список sql принимает несколько записей, у каждой свои engine, schema, queries и gen.go с собственным out. Так репозиторий генерирует пакеты и для PostgreSQL, и, скажем, для SQLite со статистикой — не мешая друг другу.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

ent25 уроков~13 чпрактика25 уроков уже доступны

ent: графовая ORM с кодогенерацией

Курс ent: схемы, поля, связи, запросы, хуки, privacy, миграции и GraphQL. 25 уроков с примерами и проектом микроблога.

  • Проектировать схемы и связи в ent
  • Генерировать код и жить с кодогенерацией
  • Писать запросы builder-стилем с типами
  • Строить хуки, интерсепторы и privacy-правила