Pull to refresh

Comments 15

Что мешает вынести в отдельный файл со списком констант? Так ты получишь такой же подход. Когда у тебя SQL в отдельном файле, как ты и хотел.

На счёт подсветки синтаксиса, сейчас уже редакторы умеют подсветить синтаксис внутри строковых переменных.

Но так тебе не нужно парсить файл, доставать запрос оттуда, надеяться, что другой разработчик не напутали с комментарием для нового запроса.

Так же я не понял, как у тебя ошибка в парсинге файла получилась ошибкой компиляции?

Константы в Go-файле - валидный подход. Но в чистом .sql файле работает полноценный SQL-тулинг: форматирование через pg_format, линтинг через sqlfluff, автодополнение колонок при подключении IDE к БД.

Парсер и риск ошибок. Регулярка тривиальная, 5 строк. Если разработчик забудет маркер, запрос не попадёт в map, и Get() упадёт с panic при первом же вызове. Ошибка не пройдёт незамеченной.

Ошибка компиляции. Тут я возможно действительно преувеличил. go:embed даст ошибку компиляции только если файл не найден. Однако никто кто мешает прикрутить нормальную валидацию SQL при инициализации пакета. Все это выходит на рамки простой статьи.

Как я писал в самом начали в конце статьи: это не единственный, да и не самый лучший способ организации работы с SQL файлами. Однако в большинстве случаев на моей практике он самый удобный.

Из всего перечисленного Вами, я не уверен только в линтере SQL, используя константы. Всё остальное работает и на константах. Но при этому у вас избавляется необходимость в чтении файла, эмбединга и всё значительно упрощается (как минимум находясь в функции запроса, легко перейти на сам SQL и изучить/изменить его).

А селективные процедуры в вашем SQL сервере ещё не изобрели? Просто если да, то сложные запросы где то там, с подсветкой, проверкой, всем этим. А в Го только довольно простейшие запросы к ним.

Хранимые процедуры легитимный подход, но со своими компромиссами. Логика размазывается между приложением и БД. Для меня это усложняет отладку и понимание системы целиком.

Версионирование процедур отдельная боль. Нужно синхронизировать миграции БД с деплоем приложения.

Тестирование тоже усложняется. Для unit-тестов репозитория теперь нужна реальная БД с актуальными процедурами. Плюс vendor lock-in: PL/pgSQL !== T-SQL !== MySQL процедуры.

Странно что Не бойся использовать разные инструменты в одном проекте при чтении статьи проходит мимо глаз. Если у вас команда DBA сильная и процедуры стандарт, отлично. Embedded SQL для тех, кто хочет держать всю логику в Go и деплоить одним бинарником.

>Логика размазывается между приложением и БД

Наоборот, логика существует в единственном месте, где и положено, в БД.

>Странно что Не бойся использовать разные инструменты в одном проекте при чтении статьи проходит мимо глаз

Наоборот, именно это и бросается в глаза. Вся логика в БД, и все инструменты доступа просто доступ, а не заменяют собой БД.

Ммм... как будто бы напрашивается идея с тем, чтобы DBA вели просто свой "пакет"/репозиторий с набором таких вот *.sql файлов, который подтягивать в нужное место через обертку в виде go.mod или сабмодулем.
На вскидку звучит интересно...
В конце концов существуют же кошмарные репозитории гугла с адовыми обертками для go и остальных :))
Ну что то типа такого https://github.com/google/brotli - кажется идея с аналогичной репой для sql звучит не так ужасно :)))

Делал похожим образом в одном специфичном внутреннем проекте, только использовал text/template т.к. нужно было хитро подставлять значения, или другие кусочки sql.
Оно существовало (и вроде как существует) как проект переноса легаси с питона.
Опять же, получается +- чище, в git удобнее отслеживать изменения, если затрагивались именно SQL запросы, ну и прочие полезные фишки типа прокидывания функций в template и т.д и т.п.
Примерно так выглядел какой нибудь маленький кусочек из множества

SELECT DISTINCT
    CADNUM_PARENT
FROM
    unio.T_HISTORICITY_LINK
WHERE
    CADNUM_PARENT != ANY( {{ joinedArgs `Cads` .Cads }} )
    AND CADNUM_CHILD = ANY( {{ joinedArgs `Cads` .Cads }} )
    AND CADNUM_CHILD != :cad_in
    AND CADNUM_PARENT != :cad_in

Интересное решение. Даже не думал об этом :-)

Тоже использую text/template для хранения SQL запросов отдельно от Go кода.

Дополнительное удобство что можно хранить несколько версий sql/v1/*.sql, sql/v2/*.sql, и т.д. и выбирать нужную версию SQL запросов в рантайм.

Да, sqlc отличный инструмент, но это разные весовые категории :-)

sqlc парсит SQL, генерирует Go-структуры и методы. Даёт типобезопасность, но добавляет шаг сборки, зависимость от внешнего тула и генерируемый код в репозиторий.

Мой же подход сейчас - это просто парсер на условно 50 строк кода. Ноль зависимостей, ноль генерации, полный контроль над кодом репозитория. Ты сам решаешь как сканировать результаты, какие хелперы использовать, как обрабатывать ошибки.

В целом считаю sqlc отличным интснтументом. Если нужна типобезопасность и не смущает кодогенерация, sqlc это одно из лучших решений, тут сложно спорить. Если хочешь минимализм и полный контроль, то вполне хватит простого go:embed с парсером. Иногда (да по правде говоря чаще всего), этого вполне достаточно.

Хороший разработчик не привязывается к одному инструменту, а понимает компромиссы каждого.

Вы реализовали маленькую часть MyBatis из java мира

Sign up to leave a comment.

Articles