Pull to refresh

Comments 56

А что делать в ситуации когда нам нужна фильтрация по полям и в зависимости от запроса мы должны сформировать разный SQL?

Не надо разный SQL. Нужно один запрос с масимальным количеством полей. Если поле на задано (пустая строка) - по нему не фильтруем.
Пример:

CREATE OR REPLACE FUNCTION get_account_list(
	IN a_login TEXT,
	IN a_password TEXT,
	IN a_limit INT,
	IN a_offset INT
)
RETURNS TABLE (
  list client_accounts,
  full_count BIGINT
)
LANGUAGE plpgsql
AS $$
BEGIN
    RETURN QUERY
		SELECT a, count(*) OVER() AS full_count FROM client_accounts AS a
		WHERE ((a.login = a_login) OR (a_login = '')) and
			((a.password = a_password) OR (a_password = '')) 
		ORDER BY a.id  ASC
		LIMIT a_limit
		OFFSET a_offset;			
END;
$$;

ох насмотрелся я на такие "запросы" с максимальным количеством атрибутов, универсализированные "конструкторы", натчнутые на непродуманную семантическу модель без нормальной реализации средствами субд. куча тянущихся говноданных, нагрузка на субд, на сеть, на балансир и тд и тп., а если у вас структура поменялась? сколько sp отвалится? а сколько линков и внешних клиентов? нет однозначных решений, типа, вот это таблетка и она работает везде. каждый случай, особенно на хайлоад уникален. где-то можно и процедурами или функциями, а где-то параметризированными запросами, а где-то вьюхами, а в каком то случае и без динамики не обойтись.

Всё что угодно, но лучше избегать динамики ИМХО. Помимо всего, СУБД не любят уникальные запросы и это всё может ощутимо сказаться на производительности в целом.

Вы наверняка рассматривали вероятность использования кодогена, чтоб не зависеть от магических ключей мапы и не иметь оверхеда на запуск, когда надо добро все прочитать да распарсить. Почему вместо генерации обертки над sql в виде как раз go кода отказались? Ведь такой подход выглядел бы логичнее, строже, и гибче?

Да, вы угадали. И долго не мог выбрать - как лучше. Но хотелось иметь максимально простой вариант - добавил в sql-файл запрос и всё. Я периодически забываю запускать кодогенерацию ). Решающим аргументо было: Тесты на все запросы в любом случае должны быть. И ошибки в именах запросов они в любом случае должны выявлять. Если так - кодогенерация становится еще одной дополнительной и не обязательной подстраховкой. Но в целом, я считаю что плюсы и минусы кодогенерации равны 50/50. И это уже личный выбор, вкусовщина.

Чтоб не забывать запускать кодоген, часто достаточно не хранить сгенерированные файлы под гитом, и писать тесты ;)
В общем и целом, выбрали бы вы дорожку с генерацией кода - ваш покорный (и, уверен, много кто еще) прониклись и глубже бы стали смотреть ваш проект. И сравнивать его с sqlc, разумеется :D

Но магия ключей мап, и оверхед на запуск как минимум для меня выглядят как редфлаги, заставляющие пройти мимо :(

Удачи с проектом, не бойтесь экспериментировать!

Оверхед на запуск?! Лишние сколько-то миллисекунд при запуске - редфлаг?... Ну даже не знаю, что сказать )

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

В секунду чтение в мапу из памяти (ambedded FS)?!! )))
Да это микросекунды. Готов поставить эксперимент.

Если embed — да, но в библиотеке используется fs.FS, следовательно, os.DirFS(), например, тоже подойдет.

Ну так делать не надо, если только нет на то очень веских причин ))))

sqlc уже парсит *.sql и генерит по нему код

плюсую, очень классная вещь

Ну, не всем нравится дополнительная кодогенерация )

а что, она есть просит? после описания sql-запроса в файле вы либо снова ручками пишете код, использующий этот запрос, либо выполняете 1-секундную команду, получая единообразный и типизированный код на все запросы.
Ладно бы ваш подход чем-то принципиальным отличался от sqlc. Так вы просто делаете то же самое, только медленнее и хуже.

Ну насчет медленнее и хуже - приведите аргументы, прежде чем клеить ярлыки ). Почему я не хочу использовать кодогенерацию, я уже писал. Я ж не навязываю своё мнение.

Ну насчет медленнее и хуже - приведите аргументы

ручками и неединообразно

Почему я не хочу использовать кодогенерацию

Я периодически забываю запускать кодогенерацию

ну вы же кодогенерацию запускаете, чтобы сгенеренный код использовать? как у вас тут выйдет забыть сгенерить?

Мне кажется это бесконечный спор ).
Самое главное - sqlc же и модели генерирует? Я предпочитаю использовать по возможности одну модель для одной сущности для всех слоёв. По этой причине генерация моделей мне никак не подойдет.
Второстепенное - Когда я пишу метод, вызывающий запрос к БД ручками, всё под моим контролем. Я могу использовать все 100% возможностей, которые дает мне драйвер СУБД так, как мне нравится.
Кодогенерация отнимает у меня эту свободу.
JFI: Я не утверждаю, что использование sqlc - плохо. Отнюдь. Но подходы могут быть разные. И у каждого свои плюсы и минусы. Каждый выбирает то, что ему больше по душе. Идеальных решений нет.

мне если что sqlc не нравится, но по причине костылей с динамическими запросами (то же, что и у вас).
но рассматриваю я sqlset vs sqlc не как два разных подхода, а как базовый подход и подход, в который вы еще не эволюционировали.

Честность за честность: а я не имел живого опыта работы с ней )

Вопрос, в Go до сих пор нет ORM? Хотя бы аналог Dapper для C#, ну или что то более серьёзное типа EF?

Вопрос: А зачем в нативных либах (в родном пакете) - ОРМ? ИМХО - перебор. В сторонних либах - есть всё что душе угодно, выбирай - не хочу )

Есть хорошие SQL builder-ы зато (squirrel, например)

Безусловно. Но я предпочитаю сам писать SQL-код. Я уже описывал здесь свои аргументы.

Есть ещё sqlbuilder. Умеет в разные диалекты. Таким образом, вместо того чтобы собирать SQL из разных фрагментов, собирается все с помощью Go-кода. Скорее всего, довольно сложные запросы он собрать не поможет, и понадобится решение наподобие вашего.

Есть еще huma - тоже билдер. Мне подобный сахар не нравится по нескольким причинам. Напишу в отдельном комментарии. Запросы должны быть статичными если только это возможно. А это возможно и целесообразно в 99,999% случаев.
Вот: https://habr.com/ru/articles/977046/#comment_29259296

huma вроде бы не имеет отношения к БД. goqu?

Ты прав! Вот же блин всё время путаю. Дурацкие названия.. ))

Недостатки динамических запросов

  • Смешивается в одну кучу приложения GO и код SQL. Портировать будет труднее.

  • Уязвимость к SQL-инъекциям — главная и самая опасная. Если в запрос напрямую вставляются пользовательские данные без правильной обработки, злоумышленник может внедрить вредоносный код. Например, классика вроде ' OR '1'='1 может обойти аутентификацию или вытащить все данные из таблицы. Это приводит к утечкам данных, удалению БД или даже выполнению команд на сервере.

  • Проблемы с производительностью — Каждый динамический запрос часто компилируется заново, что не позволяет эффективно кэшировать планы выполнения. В результате — повышенная нагрузка на CPU, медленные запросы и возможные проблемы с параметр-сниффингом (неоптимальные планы для разных значений параметров).

  • Сложность отладки и поддержки — Код становится менее читаемым, труднее тестировать и логировать. Ошибки в формировании строки могут привести к синтаксическим ошибкам или неожиданному поведению.

Какая связь динамически составляемого запроса и SQL-инъекции? Что мешает в динамически формируемом запросе использовать подготовленные запросы и SQL параметры?

«Каждый динамический запрос часто компилируется заново» — но тем не менее, их обычно довольно ограниченное количество, а популярных в рамках приложения комбинаций еще меньше. И происходит эта «компиляция» не каждый раз, а только один при первом выполнении. Так что это всего несколько штук на запущенный инстанс базы, не так это и страшно)

Со вторым кейсом на прошлой работе сталкивался мой коллега. И это реально будет проблемой, если нарвешься. Но мне в первую очередь не нравится смешивание GO и SQL. Я люблю порядок, когда всё на своих полках )

Так sqlbuilder и делает статические запросы, если вы только сами не напишете цепочку сбора этого самого запроса динамической.

Билдер накидывает абстракцию над SQL и выражает ее в виде Go методов. И все рядовые запросы выглядят читаемо. Там все те же select, where и equals. И подстановки параметров там безопасные(через ?). Захотели переехать на другую бд- меняете диалект в настройках билдера.

К тому же, он умеет привязываться к гошным структурам через db тэги. Это выглядит ORM, но ограничивается генерацией запроса.

Потому что для рядовых запросов он освобождает от необходимости изобретать велосипед. В запросы надо передавать параметры, да ещё и в правильном порядке. Билдер следит за этим сам, от меня ему нужен только тип структуры. Плюс в коде это выглядит понятно и читаемо.

Если нужно написать сложный запрос, то я выберу чистый SQL. У билдера есть свой предел, где он становится неудобным. Но такие запросы не в каждом сервисе существуют.

Я за то чтобы использовать инструмент там, где он хорошо подходит.

Согласен с вами. Но я за то, чтобы использовать один инструмент везде и единообразно. Зачем тащить билдер, если простые запросы написать просто? ИМХО - он лишний. Или чистый SQL или - билдер. Не вижу никакой пользы от смешивания - только лишняя зависимость, лишний код. Это - только моё личное мнение )
Чистый SQL дает полную свободу и весь диапазон функциональных возможностей.

Признаюсь, что мне подсознательно кажется, что за динамику топят те, кто не любит конструировать сложные SQL запросы, процедуры. Ну или не умеет и не хочет заморачиваться. Кажется так, потому что несколько лет назад был таким же. Но теперь я считаю, что лучше не строить над SQL сладкие надстройки. В любом случае, когда ты пишешь найтивный SQL-код - у тебя больше гибкости и контроля. Юзать билдер - это всё равно, что на Python писать go-код с помощью фреймворка. Всё равно что разговаривать с иностранцем через Google Translate. Ну или гланду удалять через одно место. Изврат )
В самом Postgres, кстати можно создавать динамические запросы в процедурах. Да и вообще, в Postgres сейчас столько всего напихали - трудно найти то, что он не умеет делать. Невозможно. И делает это он по-любому оптимальнее обычно. Как-то так...

Ну а про ORM-ы лучше вообще промолчу ))))

ORM позволяет стандартизировать интерфейс доступа к данным, пожертвовав гибкостью конкретной СУБД, в пользу унификации. Такой подход влияет на архитектуру кода.

Верно, согласен. Но на мой взгляд минусы перевешивают плюсы. В реальной жизни обходиться только ОРМ получится только для сферического коня в вакууме. Как концепция - классно. Но на практике - больно. ИМХО...

Билдеры и ОРМ-ы кажется удобными на старте, пока не появились специфические задачи/требования. А потом - это будет боль, мне кажется. Ну и бойлерплейт конечно.

Боль начинается из за отказа от унификации и острого желания найти уже готовую фичу в конкретной специфичной БД и пользоватся ею независимо от последствий в будущем - привязал проект к конкретной СУБД и точка, зато все быстро сделал - менеджеры таких любят.

Кстати, ни в коем случае не берите на работу тех, кто сможет объяснить менеджерам почему теперь мы не можем отказатся от этой технологии и использовать аналогичное open source решение :)

Ну если менять СУБД каждый год... То конечно ). Переписать имплементацию слоя репозитория раз в 10 лет - не криминально поверьте. Особенно если запросы хранятся отдельно от кода. Но мучится все эти 10 лет с минусами, вызванными абстрагированием от СУБД - тоже такое себе. Впрочем - зависит всё от контекста. И к слову, однотипные СУБД принципиально не отличаются по функционалу. 99% всего что можно делать в MS SQL сделаете и в Postgres легко. Я про SQL код.

Конечно нет, коллация в MSSQL по умолчанию регистронезависимая в отличии от Postgres и далее начиная с пагинации, регистронезависимые ILIKE и до оконных функций - много отличий, даже в типах данных дат, я молчу про json функции.

Я не про нюансы выполнения функций, я про наличие/отсутствие аналогичных функций. И никто не заставляет использовать какие-то уникальные фичи конкретной СУБД. Можно писать запросы, не используя уникальную специфику.
ОРМ априори не подпускает вас к низкому уровню. И если идеологически это хорошо, то практически, если у вас хайлоад и вы ходите максимально оптимизировать работу с БД - он вам не позволит этого сделать. А как показывает практика, рано или поздно такая потребность возникает. И потом люди локти кусают.

Highload и реляционная СУБД плохо совместимы, для highload другие БД обычно используются, минимальное требование - горизонтальная масштибируемость на запись, с этим у реляционных БД все плохо, на чтение ещё можно репликами разрулить, а вот если высокая нагрузка на запись, то приходиться танцевать с бубнами и менять структуру БД чтобы можно было сегментировать хранилище по разным дискам, пока не упрёшся в лимит по CPU, а уж по разным узлам разносить логически одно хранилище, это отдельная боль.

highload highload-у рознь )

Позволю себе еще одно объяснение по поводу моего отношения к ORM и кодогенерации. Чтоб не плодить холивары в нескольких местах:
Если проект достаточно сложный и долгоживущий, развивающийся, рано или поздно но возникает необходимость в ручном написании и вызове каких-то запросов. С большой вероятностью возникает. Возникает потому, что нужно реализовать какую-то фичу или минимизировать время и количество обращений к БД - мало ли. И вот тут приходится обходить ORM и кодогенерацию.
Сочетать же несколько инструментов одновременно для работы с БД конечно можно, но мне не хочется. Мне импонирует строгость и единообразие - меньше когнитивная нагрузка при работе с кодом проекта.
Я не утверждаю что мой подход единственно верный. Но думаю, он имеет право на жизнь наравне с другими ).

попробуйте всех таки sqlc. Вы явно не понимаете как он работает.

Спасибо. Да, давно собираюсь.

Возможно некоторые выше уже писали, не было возможности прочесть все комментарии.

Есть отличная Чистая архитектура Роберта Мартина (Clean Architecture by Unce Bob)

Я согласен что в вашем случае уже был готовый проект и писать с нуля не имело смысла, но в указанной выше архитектуре вам бы просто пришлось написать +1 реализацию интерфейса, что вероятно, не составило бы труда

Я не очень понял, о какой +1 реализации какого интерфейса речь.
И я не сторонник отличной чистой архитектуры. Я люблю, что хоть немного грязи было, но не полная стерильность ))

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

Sign up to leave a comment.

Articles