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

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

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

Урок 23 из 25 ~18 мин +50 XP

sqlc vet и проверка запросов

Тема урока — sqlc vet: команда статического анализа sql, которая смотрит на ваши запросы глазами линтера, а не генератора. Двадцать два урока ставили sqlc diff и проверку генерации в CI — здесь мы достраиваем этаж выше: diff ловит расхождение кода и запросов, vet ловит сами запросы. Появился он в версии 1.19.0 как «linting queries» и вырос в полноценный конвейер: правила на CEL-выражениях, проверки по EXPLAIN-плану живой базы, встроенное правило sqlc/db-prepare, managed databases, которые поднимают эфемерный Postgres из конфига, и кольцо обратной связи с мониторингом вроде pganalyze. Логика простая: [генератор](/courses/sqlc/lessons/03-pervye-zaprosy/) гарантирует, что запрос скомпилируется в типобезопасный Go, но не гарантирует, что запрос — хорош; vet закрывает именно вторую половину. Урок отвечает на пять вопросов. Первый: как устроено правило — имя, сообщение и CEL-выражение, которое срабатывает при true. Второй: какие переменные доступны выражению — config и query, от движка до числа параметров. Третий: EXPLAIN-правила — когда vet подключает базу и что читает из плана. Четвёртый: managed databases и sqlc/db-prepare — подготовка каждого запроса против настоящей схемы. Пятый: как связать vet в CI с советчиком индексов и монитором, чтобы плохой план ловился дважды — до merge и после деплоя. План курса — на [странице курса sqlc](/courses/sqlc/); место vet в пайплайне — [двадцать второй урок](/courses/sqlc/lessons/22-ci/).

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

sqlc vet: недостающая ступень статического анализа sql#

К двадцать третьему уроку у трекера выстроена прочная вертикаль: схема в миграциях, запросы с аннотациями, сгенерированный пакет, проверка генерации в CI. И в этой вертикали есть слепая зона, которую стоит назвать вслух: всё, что мы проверяли, — это соответствие кода запросам. Статический анализ sql вводят поэтапно: сначала компиляция запросов против схемы, потом правила дисциплины, потом планы — sqlc vet кладёт все три ступени в одну команду. Ни одна машина до сих пор не отвечала на вопрос, хороши ли сами запросы. DELETE без WHERE, INSERT, который никто не должен вызывать, запрос с двадцатью параметрами, план, катящийся по таблице последовательным сканом, — компилятор Go пропускает это молча, потому что с точки зрения типов всё честно.

sqlc vet закрывает заново открывшуюся дыру. Команда появилась в версии 1.19.0 с формулировкой «runs queries through a set of lint rules» — прогоняет запросы через набор правил линта. Механика у неё двойная. Первая половина — без базы: vet собирает ваш конфиг, компилирует запросы по схеме, как это делает generate, а затем оценивает каждое правило-выражение против метаданных запроса. Вторая половина — с базой: если правило обращается к EXPLAIN-переменным, vet подключается к настроенному Postgres или MySQL, прогоняет каждый запрос через EXPLAIN и оценивает выражения уже по содержимому плана. Если выражение вычислилось в true — vet печатает сообщение правила и завершается с ненулевым кодом выхода. Это второе предложение — весь смысл команды в CI: ненулевой выход роняет шаг пайплайна, и плохой запрос доезжает до main только через осознанное решение человека, а не по инерции.

Слово «линт» здесь точнее слова «тест». Тест подаёт данные и смотрит на результат; линт смотрит на сам текст и метаданные и спрашивает: соответствует ли это нашей конвенции. sqlc vet — линт sql: правила живут в том же sqlc.yaml, что и весь остальной конфиг, ревьюятся в том же PR и действуют на каждый запрос пакета без единой строки тестового кода.

Канон урока: первые правила в sqlc.yaml

GO
version: "2"
sql:
  - engine: "postgresql"
    schema: "db/migrations"
    queries: "db/queries"
    gen:
      go:
        package: "db"
        out: "db/sqlc"
        sql_package: "pgx/v5"
    rules:                 # какие правила ВКЛЮЧЕНЫ для этого пакета
      - no-delete
      - no-exec
      - only-one-param

rules:                     # какие правила вообще ОПРЕДЕНЫ в проекте
  - name: no-delete
    message: "DELETE запрещён: мягкое удаление через deleted_at"
    rule: |
      query.sql.contains("DELETE")
  - name: no-exec
    message: ":exec у задач не нужен — возвращайте строку через :one"
    rule: |
      query.cmd == "exec"
  - name: only-one-param
    message: "больше одного параметра — повод вынести запрос на ревью"
    rule: |
      query.params.size() > 1

Два уровня rules — главная путаница новичков. На верхнем уровне перечислены определения: имя, сообщение и выражение. Внутри записи sql — список имён, включённых для этого пакета: правило определено, но не активно, пока его не назвали в пакете. Выражения написаны на CEL — Common Expression Language, исполняются движком cel-go; срабатывает правило тогда, когда выражение вычислилось в true. Запуск — sqlc vet без аргументов: он прочитает конфиг, скомпилирует запросы и оценит каждое включённое правило против каждого запроса.

Проверка: когда правило срабатывает

Правило no-delete определено с выражением query.sql.contains("DELETE") и сообщением «DELETE запрещён». При каком условии sqlc vet сообщит об ошибке и выйдет с ненулевым кодом?

Анатомия правила: CEL над метаданными запроса#

CEL выбран не случайно: это маленький язык выражений от Google с детерминированной оценкой, коротким замыканием и безопасным песочничным исполнением — в нём нет циклов и обращений к внешнему миру, поэтому правило невозможно превратить в программу, которая что-то делает, кроме как считает. Каждому правилу vet подаёт два корня, описанные в документации proto-сообщениями. Первый — config: version и engine конфига, списки schema и queries; через него ловят, например, случайный движок в записи. Второй — query: sql — полный текст запроса, name — имя из аннотации, cmd — тип аннотации без двоеточия: one, many, exec, execrows, execlastid, batchexec и родня, params — список параметров, у каждого из которых есть number.

Из этих кирпичей собираются все «дисциплинарные» правила. config.engine == "mysql" — просыпаешься утром и видишь, что кто-то поменял движок в конфиге. query.cmd == "exec" — бан на аннотацию, которую команда договорилась не использовать. query.params.size() > 3 — красный флаг на запросы с кучей аргументов: не запрет, а фильтр «посмотри на это на ревью». query.name.contains("debug") — все экспериментальные запросы обязаны носить префикс, и линтер следит за конвенцией вместо вас.

Обратите внимание на природу данных: выражения видят метаданные запроса, а не его исполнение. vet не знает, что вернёт SELECT, сколько строк затронет UPDATE и какой индекс выберет база — для этого у него отдельный механизм, EXPLAIN-переменные, о которых ниже. Это деление стоит держать в голове при проектировании собственных правил: всё, что про текст и структуру, — в CEL без базы; всё, что про план и стоимость, — в CEL с подключённой базой.

Карта CEL-переменных sqlc vet#

ВыражениеЧто даётПример правила
config.engineдвижок записи sql (postgresql, mysql, sqlite)config.engine == "postgresql"
config.versionверсия конфига — строка "2"config.version == "2"
config.schemaпути к схеме (список)config.schema.size() > 0
query.sqlполный текст запросаquery.sql.contains("DELETE")
query.nameимя из аннотации -- name:query.name.contains("debug")
query.cmdаннотация без двоеточия: one, many, exec…query.cmd == "exec"
query.paramsсписок параметров запросаquery.params.size() > 3
query.params[0].numberномер первого параметраquery.params[0].number == 1
postgresql.explainEXPLAIN-план (нужна база, v1.20.0+)postgresql.explain.plan.total_cost > 100.0
mysql.explainEXPLAIN-план MySQL (нужна база)has(mysql.explain.query_block.cost_info)

Предскажите, какой запрос уронит vet

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

-- db/queries/todos.sql — включены правила no-exec и only-one-param

-- name: ListDone :many
SELECT id, title FROM todos
WHERE done = true AND deleted_at IS NULL;

-- name: SoftDelete :execrows
UPDATE todos
SET deleted_at = now()
WHERE id = $1;

-- name: HardDelete :exec
DELETE FROM todos WHERE id = $1;

Границы строковых правил: contains и его ловушки#

Первые правила, которые заводит команда, почти всегда строковые — и у них есть свойство, о котором документация честно умалчивает: contains смотрит на подстроку, а не на смысл. query.sql.contains("DELETE") сработает не только на DELETE FROM, но и на комментарии «-- DELETE: не трогать», на имени колонки deleted_at, на строке-литерале «DELETE» в данных запроса. Ложные срабатывания — не аргумент против линтера, а инструкция по его заточке.

Приёмы заточки три. Первый — сузить шаблон: contains("DELETE FROM") ловит уже не комментарий, а настоящую команду; продолжить можно регистром — contains("DELETE FROM") без учёта регистра в CEL даёт пара .lower() — query.sql.lower().contains("delete from"). Второй — направить правило в имя: конвенция «жёстких удалений нет» надёжнее держится на cmd — комбинация query.cmd.contains("exec") && query.sql.lower().contains("delete") точнее любого поиска по тексту. Третий — принять неточность как фичу: линтер, который подозрителен, полезнее линтера, который спит; ложное срабатывание стоит вам минуты, пропущенный DELETE без WHERE — вечера восстановления.

И общее правило гигиены: правила — это код команды, у них должен быть владелец. Правило, которое все отключают через @sqlc-vet-disable — об этой аннотации ниже, — не дисциплина, а ритуал; его либо переписывают, либо удаляют. Живой набор из трёх-пяти точных правил стоит дороже скопированного списка из двадцати чужих.

EXPLAIN-правила: vet против плана#

С версии 1.20.0 у vet появился второй уровень: выражения получили доступ к EXPLAIN-плану каждого запроса. Переменных две, и каждая привязана к своему движку: postgresql.explain и mysql.explain. sqlc заполняет только переменную вашего движка и только при настроенном подключении к базе — ключ database в записи sql с адресом uri. Для PostgreSQL vet выполняет EXPLAIN (ANALYZE false, VERBOSE, COSTS, SETTINGS, BUFFERS, FORMAT JSON) с текстом вашего запроса и разбирает JSON-ответ в структуру; для MySQL — EXPLAIN FORMAT=JSON. Планировщик оценивает запрос, ничего не исполняя: ANALYZE false означает, что база не прогоняет запрос по-настоящему, — vet безопасен даже для тяжёлых INSERT.

Структура плана в выражениях читается по узлам. Классика из доков: postgresql.explain.plan.node_type == 'Seq Scan' — корневой узел плана оказался последовательным сканом; postgresql.explain.plan.total_cost > 100.0 — суммарная оценка стоимости выше порога. Для MySQL зеркальные проверки живут глубже: mysql.explain.query_block.cost_info.query_cost и mysql.explain.query_block.table.key != 'PRIMARY' — «запрос обязан ходить по первичному ключу», причём перед обращением к необязательным полям выражение защищают функцией has() — has(mysql.explain.query_block.cost_info) && double(...) > 2.0. Структуры большие и зависят от формы запроса, поэтому доки честно советуют: сначала посмотрите сырой JSON через SQLCDEBUG=dumpexplain=1, потом пишите правило.

EXPLAIN-правила и подключение базы

GO
version: "2"
sql:
  - engine: "postgresql"
    schema: "db/migrations"
    queries: "db/queries"
    database:
      uri: "postgres://postgres:postgres@localhost:5432/tracker?sslmode=disable"
    gen:
      go:
        package: "db"
        out: "db/sqlc"
        sql_package: "pgx/v5"
    rules:
      - no-seq-scan
      - not-too-costly

rules:
  - name: no-seq-scan
    message: "план уходит в Seq Scan — нужен индекс или запрос полегче"
    rule: |
      postgresql.explain.plan.node_type == 'Seq Scan'
  - name: not-too-costly
    message: "оценка стоимости планировщика слишком высока"
    rule: |
      postgresql.explain.plan.total_cost > 100.0

Ключ database.uri в записи sql — то, что включает EXPLAIN-переменные; без него vet просто не имеет базы, откуда взять план, и такие правила не оценить. Два классических правила из доков: проверка корневого узла плана на Seq Scan и порог на total_cost — суммарную оценку стоимости планировщика. Отладка правил — через переменную окружения SQLCDEBUG=dumpexplain=1: sqlc напечатает сырой JSON плана по каждому запросу, и вы увидите, где в структуре лежит нужное поле; временная dummy-правило вида !has(postgresql.explain) заставит vet прогнать EXPLAIN по всем запросам только ради дампа.

Проверка: почему vet упал на чистой базе

Команда подключила database.uri на свежую базу стенда и запустила vet с правилом no-seq-scan. vet упал с ошибкой про отсутствие таблиц. Кто виноват и что делать?

Managed databases: база для проверки из конфига#

Требование «живая база с актуальной схемой» рождает очевидную рутину: где-то нужно поднять Postgres, накатить миграции, дать vet адрес. С версии 1.22.0 sqlc забрал эту работу на себя через managed databases, а с версии 1.27.0 — научился делать это локально: sqlc создаёт эфемерную базу, накатывает вашу схему, прогоняет через неё что нужно и убирает за собой. Конфиг прост: запись в топ-уровневом ключе servers — движок и uri доступного сервера, — а в записи sql вместо database.uri пишется database.managed: true. Наличие сервера по-прежнему нужно: эфемерная база создаётся на нём, поэтому локально — ваш Docker с Postgres, в CI — сервис-контейнер.

Что это меняет на практике. Во-первых, EXPLAIN-правила перестают требовать ручной оркестрации: managed база рождается уже с вашей схемой, и vet оценивает планы сразу. Во-вторых, выигрывает сам generate: с подключённой базой sqlc анализирует сложные запросы через настоящий планировщик и точнее выводит типы — признание из доков: без базы sqlc does its best… with managed databases configured, sqlc generate will automatically create a hosted ephemeral database with your schema. В-третьих, конфиг становится самодостаточным для CI: сервер один и тот же для тестов и для vet.

Оговорка про историю, чтобы вы не тратили вечер на старые туториалы: хостинговые эфемерные базы sqlc Cloud, с которых начиналась фича, выключены с сентября 2024 года — рабочая схема сегодня это managed: true плюс ваш сервер из servers, а старые конфиги с cloud.project мигрируют на неё.

sqlc/db-prepare поверх managed database

GO
version: "2"
servers:
  - engine: postgresql
    uri: "postgres://localhost:5432/postgres?sslmode=disable"

sql:
  - engine: "postgresql"
    schema: "db/migrations"
    queries: "db/queries"
    database:
      managed: true        # sqlc сам создаст эфемерную базу с вашей схемой
    gen:
      go:
        package: "db"
        out: "db/sqlc"
        sql_package: "pgx/v5"
    rules:
      - sqlc/db-prepare    # встроенное правило: prepare каждого запроса

Три ключа складываются в конвейер: servers описывает доступный сервер Postgres, database.managed: true велит sqlc поднять на нём эфемерную базу с вашей схемой, правило sqlc/db-prepare прогоняет каждый запрос через prepare против этой базы. Это единственное встроенное правило — для всего остального пишете выражения сами. URI сервера можно отдать через окружение: uri: ${DATABASE_URI} — конфиг не тащит адреса стенда в git. Managed-режим работает и для generate, и для vet: одна настройка кормит обе команды.

Проверка: @sqlc-vet-disable

Один легаси-запрос закономерно нарушает новое правило, чинить его пока нельзя. Какой аннотацией вы выключаете vet для этого запроса?

pganalyze и кольцо обратной связи#

EXPLAIN-правила в CI — это первый круг обороны, и у него есть природа истины, которую важно понимать: план в CI считается на пустой или тестовой базе, где статистика таблиц — заглушка. Планировщик Postgres принимает решения по статистике, и на проде, где в todos миллион строк, тот же запрос может выбрать совсем другой план. Поэтому второй круг ставят на живые данные, и здесь на сцену выходят инструменты мониторинга: pganalyze — сервис наблюдения за Postgres, который собирает реальные планы и статистику с работающей базы и на их базе подсказывает индексы — его Index Advisor строит гипотетические индексы через расширение HypoPG и показывает, как изменится план.

Связка с sqlc vet — не официальный плагин, а кольцо обратной связи, которое собирается из штатных деталей. vet в CI ловит очевидное до merge: Seq Scan на большом справочнике, стоимость выше порога. pganalyze на проде подтверждает и уточняет: вот запрос, который в бою пошёл по Seq Scan, вот рекомендуемый индекс. Рекомендация доезжает в проект как обычная миграция с CREATE INDEX — и на следующем прогоне vet с правилом no-seq-scan вы видите, что план исправился. В экосистеме есть и офлайн-вариант того же приёма: открытое расширение index_advisor от Supabase даёт функцию, которой скармливаешь текст запроса — она возвращает рекомендованные CREATE INDEX по стоимости до и после; его можно гонять на той же CI-базе, что и vet.

Итоговая дисциплина звучит так: lint-правила держат стиль и структуру, EXPLAIN-правила держат план на тестовых данных, мониторинг подтверждает план на настоящей нагрузке, а всё найденное возвращается в схему через миграции. Каждый инструмент делает своё, и вместе они закрывают вопрос «почему тормозит», который один vet ответить не может.

линт sql в CI: культура правил#

Технически всё готово: правила определены, база управляется из конфига, аннотации отключения под контролем. Осталась организационная часть — как внедрить vet так, чтобы он не превратился в источник бесконечных красных пайплайнов. Практика, которая работает: правило заходит в проект только после того, как вся кодовая база ему уже удовлетворяет. Заводите правила волнами — первая чистка, потом включение в CI, потом следующая волна. Включать пять правил разом на репозиторий с двумя сотнями запросов — способ получить кнопку «перезапустить с --force» вместо линтера.

Порядок для sqlc-проекта, проверенный многими командами: первый шаг — sqlc/db-prepare против managed базы: он не про стиль, а про честность — каждый запрос обязан готовиться против настоящей схемы. Вторая волна — структурные правила: бан на :exec там, где команда договорилась возвращать строки, ограничение на число параметров, конвенция имён. Третья — EXPLAIN-правила, когда есть CI-база с близкой к продовой схемой и наполнением: Seq Scan-сторож и стоимостной порог. Каждая волна — один PR, одно обсуждение, один список исключений.

В пайплайне vet живёт рядом с sqlc diff из двадцать второго урока: diff гарантирует, что сгенерированный код соответствует запросам, vet — что запросы соответствуют договорённостям команды. Обе команды быстры, обе не требуют прод-доступа, и обе должны ронять PR одинаково безжалостно. А чтобы правила не протухали, у vet есть невидимый союзник — ревью: изменение правила едет в PR, как и любой код, и история git отвечает на вопрос, почему с 14 марта DELETE без WHERE считается ошибкой.

Первая пятёрка правил, с которых стоит начать#

  • sqlc/db-prepare — каждый запрос готовится против живой схемы: ловит опечатки в колонках и несоответствие типов
  • no-delete — DELETE FROM запрещён в пользу мягкого удаления через deleted_at (трекер живёт по этому закону с шестого урока)
  • only-one-param / лимит параметров — запросы с пятью и больше аргументами обязаны показаться на ревью
  • no-exec — в пакете разрешены только :one и :many: каждая запись возвращает строку через RETURNING (пятый урок)
  • no-seq-scan — правило по EXPLAIN-плану: справочники и выборки по id не имеют права ходить последовательным сканом
  • конвенция имён — префикс debug_ для экспериментальных запросов, чтобы лишние не жили в проде без спроса

Соберите правило и подключение базы

Заполните пропуски: ключ определения правил, поле выражения, переменная плана и ключ включения правила в пакете.

version: "2" sql: - engine: "postgresql" schema: "db/migrations" queries: "db/queries" : managed: true gen: go: package: "db" out: "db/sqlc" rules: - guard-cost : - name: guard-cost message: "стоимость выше порога" rule: | postgresql..plan.total_cost > 100.0

Порядок внедрения sqlc vet

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

Строки

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

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

Границы vet и мост к следующему уроку#

Честные границы, чтобы линтер не казался волшебной палкой. vet не исполняет запросы: EXPLAIN даёт оценку планировщика, а не реальность — ANALYZE-прогон с настоящими числами остаётся делом мониторинга. Правила на CEL видят метаданные и структуру плана, но не знают вашего домена: почему этот запрос обязан быть :exec — знает команда, поэтому правила — кодифицированные договорённости, а не истина в последней инстанции. И vet не заменяет тесты: он проверяет запросы против схемы и конвенций, но не против бизнес-логики.

В масштабе курса vet — последний технический инструмент перед итоговыми развилками. Мы двадцать два урока строили SQL-first-мир: конфиг, типы, транзакции, пагинация, миграции, CI — и вот контрольная проверка над всем этим хозяйством. Осталось ответить на главный стратегический вопрос: sqlc — правильный выбор вообще? Рядом живут GORM с его тегами и Preload, ent с графом и генерацией, sqlx с тонкой надстройкой над database/sql, squirrel с билдером запросов — и у каждого есть дни, когда он честно лучше. Двадцать четвёртый урок — sqlc vs gorm vs ent: сравнение по типобезопасности, динамике, миграциям и команде, чтобы выбор слоя данных был решением, а не привычкой.

Карточки: sqlc vet

Термин

Что такое sqlc vet и с какой версии он существует?

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

Линт запросов: прогоняет их через правила и падает с ненулевым кодом при срабатывании; появился в v1.19.0, EXPLAIN-переменные — в v1.20.0, managed databases — в v1.22.0

1 / 6

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

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

Вывод

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

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

  • sqlc vet — статический анализ sql из версии 1.19.0: правила с именем, сообщением и CEL-выражением; выражение сработало в true — vet печатает сообщение и падает с ненулевым кодом.
  • CEL-выражения видят config (engine, version) и query (sql, name, cmd, params); правила двух уровней: определения на верхнем уровне, включение — списком в записи sql.
  • EXPLAIN-правила с версии 1.20.0 читают план: для Postgres — EXPLAIN FORMAT JSON с VERBOSE и COSTS; нужны database.uri или managed-база с актуальной схемой — sqlc миграции не применяет.
  • Managed databases (v1.22.0, локальные с v1.27.0): servers + database.managed: true — sqlc сам поднимает эфемерную базу с вашей схемой для vet и для точного анализа в generate.
  • Встроенное правило sqlc/db-prepare готовит каждый запрос против базы; @sqlc-vet-disable отключает правила для одного запроса — аптечка, а не режим жизни.
  • Дисциплина внедрения: правило заходит в проект, когда кодовая база ему уже удовлетворяет; в CI vet стоит рядом с sqlc diff; кольцо с прод-мониторингом вроде pganalyze возвращает найденное в схему через миграции.

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

Первое: включите для трекера правила no-delete и no-exec из канона, добавьте временный запрос с DELETE FROM и :exec — убедитесь, что sqlc vet падает с обоими сообщениями, потом уберите нарушителя. Второе: поднимите Postgres в Docker, опишите servers с uri и включите database.managed: true; прогоните sqlc generate и sqlc vet со встроенным sqlc/db-prepare — проверьте, что все запросы готовятся против эфемерной базы. Третье: добавьте правило no-seq-scan и проверьте его на выборке по колонке без индекса; добавьте индекс миграцией, убедитесь, что план сменился и vet стал зелёным — запишите оба плана в шпаргалку через SQLCDEBUG=dumpexplain=1. Четвёртое: напишите своё правило-конвенцию — например, все запросы на :many обязаны содержать LIMIT — и проверьте его против существующего пакета; вынесите исключение через @sqlc-vet-disable для одного легитимного запроса и напишите в PR строку почему. Пятое: добавьте шаг sqlc vet в CI после sqlc diff и сломайте его намеренно — скриншот красного шага в шпаргалку; почините и объясните, в какую волну внедрения это правило должно было попасть.

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

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

sqlc vet падает с правилом по EXPLAIN, хотя запрос хороший. Почему?

Самая частая причина — статистика. EXPLAIN-правила оценивают план, а план зависит от статистики таблиц, и на пустой CI-базе она скудна: планировщик честно говорит Seq Scan там, где на проде с данными будет index scan. Настройте CI-базу ближе к проду — наполнение, ANALYZE, — поднимите порог стоимости, а окончательный вердикт отдавайте монитору на живой базе: vet ловит очевидное, pganalyze подтверждает на реальной нагрузке.

Чем @sqlc-vet-disable отличается от удаления правила?

Областью и следом. Удаление правила выключает его для всего проекта навсегда; аннотация — только для одного запроса, и это решение видно в дифе рядом с кодом запроса. Рабочая норма: исключение обязано иметь причину в описании PR. Если исключений набирается много, правило не согласовано с командой — его переписывают или удаляют, а не глушируют по одному запросу.

Можно ли обойтись без базы и использовать только CEL-правила?

Да, и это нормальная первая волна: правила над config и query — no-delete, лимиты параметров, конвенции имён и аннотаций — работают без подключения, потому что видят метаданные запроса. База нужна, когда правило смотрит на план: postgresql.explain и mysql.explain заполняются только при настроенном database.uri или managed-режиме. Начните без базы, добавьте EXPLAIN-правила, когда CI уже поднимает Postgres для тестов.

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

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

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

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

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

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

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

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

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