Вот я все на ORM гоню.
Но сегодня я даже был готов отступиться, увидив на конфе доклад "NORM - фреймворк без ORM".
Это какая-то лютая смесь ligualeo-style и mongodb:
У меня, конечно, нет CS PhD, как у авторов, но вот то, что на скрине, это какое-то форменное вредительство...
PS: кстати да, с помощью parsers.dev на основании SQL можно нагенерить контракты или весь код бэкенда на любом языке. Или вот такую тулзу зацените, сэкономит кучу времени (если вы, конечно, не пишете так, как на скрине)
Но сегодня я даже был готов отступиться, увидив на конфе доклад "NORM - фреймворк без ORM".
Это какая-то лютая смесь ligualeo-style и mongodb:
select * from account_update($${
"phones":[{"phone_id":"4", "phone":"3123334557"}]}
$$::json, 4);
Описание с сайта конференции:Хорошо известно, что, хотя производительность базы данных велика и каждый запрос выполняется за миллисекунды, общее время отклика приложения может быть медленным, поэтому пользователи могут долго ждать ответа. Мы знаем, что проблема не в базе данных, а в том, как разработчики приложений с ней общаются. В частности, речь идет об ORM - Object-Relational Mappers. Разработчики баз данных ненавидят их, но разработчики приложений любят их, потому что они позволяют разрабатывать приложения без каких-либо знаний о внутреннем устройстве СУБД. В результате производительность системы часто оказывается неприемлемо низкой.Нет, чтоб взять parsers.dev и все контракты нагенерить, будем вот такое изобретать!
Единственный способ изменить это - предоставить разработчикам приложений такой же простой в использовании инструмент, как ORM, но позволяющий избежать распространенных ошибок ORM. Вот почему мы разработали NORM - No-ORM Framework.
Во время этой презентации мы рассмотрим примеры кода из репозитория https://github.com/hettie-d/NORM и узнаем, как создавать «транспортные объекты» для эффективной передачи данных между приложениями и базами данных.
У меня, конечно, нет CS PhD, как у авторов, но вот то, что на скрине, это какое-то форменное вредительство...
PS: кстати да, с помощью parsers.dev на основании SQL можно нагенерить контракты или весь код бэкенда на любом языке. Или вот такую тулзу зацените, сэкономит кучу времени (если вы, конечно, не пишете так, как на скрине)
Хороший фреймворк... и конкурсы интересные...
https://hakibenita.com/django-32-exciting-features#queryset-alias
https://hakibenita.com/django-32-exciting-features#queryset-alias
Hakibenita
Exciting New Features in Django 3.2
And my wishlist for the future...
Мне кажется, что драматически улучшить уже нельзя...
Если у вас много приватных реп, но в стату отчет по ним не попадает, можно это исправить в contribution settings -> private contribution.
Светите свои отчеты в каментах :)
Если у вас много приватных реп, но в стату отчет по ним не попадает, можно это исправить в contribution settings -> private contribution.
Светите свои отчеты в каментах :)
На самом деле на скрине выше почти месяц без коммитов. МЕСЯЦ!
Как-то не замечаешь этого, когда прокрастинируешь.
Но я хотел обратить ваше внимание на другое. Если решите начинать свой проект, внимательно изучите этот скрин :)
Давайте посчитаем, сколько вы работаете в найме из 365 дней. В РФ на 2021:
календарных дней: 365
выходных/праздничных дней: 118
рабочих дней: 247
отпуск: 30 дней
болеем две недели весной: 15 дней
болеем две недели осенью: 15 дней
187 дней работаем.
50% времени не работаем.
Получаем 100% денег.
(Не буду заводить шарманку сколько при этом сверху к зп платит работодатель, чтобы никто не отписался)
Когда у вас стартап:
Работаем 146% времени.
Получаем 0% денег.
Пользуясь случаем напоминаю:
https://twitter.com/year_progress/status/1368607386320179211
Как-то не замечаешь этого, когда прокрастинируешь.
Но я хотел обратить ваше внимание на другое. Если решите начинать свой проект, внимательно изучите этот скрин :)
Давайте посчитаем, сколько вы работаете в найме из 365 дней. В РФ на 2021:
календарных дней: 365
выходных/праздничных дней: 118
рабочих дней: 247
отпуск: 30 дней
болеем две недели весной: 15 дней
болеем две недели осенью: 15 дней
187 дней работаем.
50% времени не работаем.
Получаем 100% денег.
(Не буду заводить шарманку сколько при этом сверху к зп платит работодатель, чтобы никто не отписался)
Когда у вас стартап:
Работаем 146% времени.
Получаем 0% денег.
Пользуясь случаем напоминаю:
https://twitter.com/year_progress/status/1368607386320179211
Twitter
Year Progress
▓▓▓░░░░░░░░░░░░ 18%
Я еще от новых достижений в джанге не отошел, как тут такое...
```
И эти люди запрещаютковыряться мне в носу писать на типизированном js!
Еще вспомнилось
```
def foo(bar=[]):```
bar.append(1)
return bar
print(foo()) # [1]
print(foo()) # [1, 1]
print(foo()) # [1, 1, 1]
И эти люди запрещают
Еще вспомнилось
У нас в чате регулярно происходят академические срачи. Последний, например, про то как внутри питон работает. Присоединяйтесь! https://news.1rj.ru/str/joinchat/Tqy4Z0AKtdiFOz16
Если этот пост соберет 1000 лайков, сяду пилить свою облачную бд.
опен-кором, с движками по подписке и всяким таким....
https://baserow.io/
*предложение не является публичной офертой
опен-кором, с движками по подписке и всяким таким....
https://baserow.io/
*предложение не является публичной офертой
Baserow
Baserow - Open source no-code database
Discover Baserow, the open-source no-code platform for building databases and applications. No code or technical skills needed. Start creating for free today!
На наступившей неделе мне доверили честь вести коллективный твиттер аккаунт https://twitter.com/sqaunderhood
Я расскажу чем мы занимаемся и как все устроено.
Сюда буду дублировать лонгридами в конце дня.
Постараюсь в своем твиттере и линкеде постить копию на английском (лет ми спик фром май харт ин иглиш).
Поэтому лайк, шер, и что там еще полагается
Я расскажу чем мы занимаемся и как все устроено.
Сюда буду дублировать лонгридами в конце дня.
Постараюсь в своем твиттере и линкеде постить копию на английском (лет ми спик фром май харт ин иглиш).
Поэтому лайк, шер, и что там еще полагается
Twitter
Протестировал (@sqaunderhood) | Twitter
The latest Tweets from Протестировал (@sqaunderhood). Коллективный твиттер о качественной разработке ПО.
Контакт: @estet
Контакт: @estet
План на неделю в twitter.com/sqaunderhood:
- Категории проблем. Проблемы в схеме БД
- Проблемы в запросах
- Проблемы на границе сопряжения приложения и базы
- Создаем свой анализатор
- Как можно автоматизировать поиск проблем
- Для чего еще пригодится анализатор
- Забавные wtf в PostgreSQL и Snowflake
Дисклеймер: все примеры тестировались в PostgreSQL или SnowflakeDB. В других базах может быть иначе.
Каждый из озвученных тезисов не будет иметь ультимативный характер и приводится в справочных целях.
Используйте на свой страх и риск :)
Независимо от способа создания SQL (руками или при помощи кодогенерации), проблемы с запросами можно разделить на несколько больших групп:
1) naming convention
2) производительность
3) безопасность
4) архитектура
1) Naming convention
В некоторых архитектурах (data vault, anchor modelling) соглашение об именах является главной скрепой :)
Но и в обычной жизни это тоже пригодится. Особенно при работе с аналитическими базами.
В работе дата инженеров случаются смены вендеров БД.
Неожиданно можно узнать, что часть имен считаются ключевыми словами в одних базах и не считаются таковыми в других.
Например, LIMIT в SnowflakeDB (примеры в конце недели)
Но объективно, это не сильно аффектит качество ПО :)
2) Производительность
Кажется, что это самая главная проблема при работе с БД. Большая часть проектов пишется с использованием ORM.
Но как мы знаем, ORM придумывали не для того, чтобы у вас все быстро работало, а чтобы быстро работали вы :)
3) Безопасность
В сознании разработчика слова "безопасность" и "БД" находят пересечения только в SQL инъекциях. Эти проблемы давно решены на уровне фреймворков и специально об этом думать не надо, просто используй best practice. ORM или нет - не важно. Эскейпить строки умеют все.
Но это далеко не все. Есть еще, например, регуляторные требования относительно персональных или банковских данных. Нельзя допустить, чтобы кто-то из сотрудников имел доступ одновременно к имени и фамилии пользователя. Или чтобы система выводила полный номер карты.
А что, если запрос может привести к отказу в обслуживании из-за большого объема обрабатываемых данных? В треде https://twitter.com/SanSYS/status/1299657208934916097 грустные истории о том, как lingualeo не глядя сохраняли весь пользовательский инпут в JSON. И потом его вытягивали.
Да, это больше про производительность. Но часто проблемы будут иметь несколько сторон.
4) Архитектура
Самая богатая на проблемы группа. Количество разных ошибок не поддается счету и их очень сложно искать.
Про них я в основном и буду рассказывать.
Все проблемы в этой группе связанны с нарушением целостности логических связей между элементами базы:
- избыточная обработка невозможных состояний
- недостаточная обработка возможных состояний
- запутанные условия в выражениях
- зависящее от вендора поведение
- worst practices
- Категории проблем. Проблемы в схеме БД
- Проблемы в запросах
- Проблемы на границе сопряжения приложения и базы
- Создаем свой анализатор
- Как можно автоматизировать поиск проблем
- Для чего еще пригодится анализатор
- Забавные wtf в PostgreSQL и Snowflake
Дисклеймер: все примеры тестировались в PostgreSQL или SnowflakeDB. В других базах может быть иначе.
Каждый из озвученных тезисов не будет иметь ультимативный характер и приводится в справочных целях.
Используйте на свой страх и риск :)
Независимо от способа создания SQL (руками или при помощи кодогенерации), проблемы с запросами можно разделить на несколько больших групп:
1) naming convention
2) производительность
3) безопасность
4) архитектура
1) Naming convention
В некоторых архитектурах (data vault, anchor modelling) соглашение об именах является главной скрепой :)
Но и в обычной жизни это тоже пригодится. Особенно при работе с аналитическими базами.
В работе дата инженеров случаются смены вендеров БД.
Неожиданно можно узнать, что часть имен считаются ключевыми словами в одних базах и не считаются таковыми в других.
Например, LIMIT в SnowflakeDB (примеры в конце недели)
Но объективно, это не сильно аффектит качество ПО :)
2) Производительность
Кажется, что это самая главная проблема при работе с БД. Большая часть проектов пишется с использованием ORM.
Но как мы знаем, ORM придумывали не для того, чтобы у вас все быстро работало, а чтобы быстро работали вы :)
3) Безопасность
В сознании разработчика слова "безопасность" и "БД" находят пересечения только в SQL инъекциях. Эти проблемы давно решены на уровне фреймворков и специально об этом думать не надо, просто используй best practice. ORM или нет - не важно. Эскейпить строки умеют все.
Но это далеко не все. Есть еще, например, регуляторные требования относительно персональных или банковских данных. Нельзя допустить, чтобы кто-то из сотрудников имел доступ одновременно к имени и фамилии пользователя. Или чтобы система выводила полный номер карты.
А что, если запрос может привести к отказу в обслуживании из-за большого объема обрабатываемых данных? В треде https://twitter.com/SanSYS/status/1299657208934916097 грустные истории о том, как lingualeo не глядя сохраняли весь пользовательский инпут в JSON. И потом его вытягивали.
Да, это больше про производительность. Но часто проблемы будут иметь несколько сторон.
4) Архитектура
Самая богатая на проблемы группа. Количество разных ошибок не поддается счету и их очень сложно искать.
Про них я в основном и буду рассказывать.
Все проблемы в этой группе связанны с нарушением целостности логических связей между элементами базы:
- избыточная обработка невозможных состояний
- недостаточная обработка возможных состояний
- запутанные условия в выражениях
- зависящее от вендора поведение
- worst practices
Переходим к поиску проблем в схеме БД.
Первый документ, который стоит изучить: https://wiki.postgresql.org/wiki/Don't_Do_This
Там вы найдете небольшое количество правил, которые легко учесть при разработке, если вы не используете ORM.
Но на что еще нам стоит обратить внимание?
Покажу 10 из 1.5k правил.
Пара моментов, связанных с производительностью:
1) зеркальные индексы (a,b) и (b,a) или индексы с перестановкой
В общем случае невозможно определить оптимальный порядок колонок в индексе. При анализе стоит учесть все множество запросов в проекте, которые могли бы частично использовать многоколоночный индекс.
Но даже если таких запросов нет, они могут появиться.
В любом случае стоит избегать индексов одного типа, созданных по одним и тем же полям. Это замедлит обновление таблицы, увеличит нагрузку на диск, а значит заафектит производительность всей базы.
2) UNIQUE индекс, включающий поля неограниченного размера
Проверка уникальности сама по себе дорогая операция, которая будет тормозить изменение таблицы. Но кроме того, такой индекс таит в себе еще несколько опасных побочных эффектов.
Во-первых, полная уникальность длинной строки, скорее всего не имеет смысла с точки зрения архитектуры. Обычно нужны будут каки-то функции от текста - lowercase, tsvector, или что-то более сложное.
Во-вторых, в один прекрасный момент можно получить ошибку вот с таким хинтом:
Values larger than 1/3 of a buffer page cannot be indexed.
Поэтому рекомендуется строить такие индексы по какому-нибудь хешу
Несколько архитектурных issues:
3) заданное DEFAULT значение без ограничений NOT NULL
Самый распространенный кейс использования значений по умолчанию - отсутствие необходимости указывать значение при вставке данных.
Скорее всего это означает, что отсутствие значения нас не устроит.
Стоит обозначить это явно.
4) Константные DEFAULT значения для колонок, используемых в уникальных индексах
Может привести к возникновению ошибки при вставке строк без явного указания значений для таких колонок.
Скорее всего в тестах такой кейс не придет никому в голову, а значит найти причину будет сложно.
5) DEFAULT значение для колонок с внешним ключем
Кроме очевидного вопроса "зачем?" возникает 2 опасных кейса при вставке данных:
- значение DEFAULT есть во внешней таблице, но не соответствует бизнес логике. Это ломает связанность данных
- значения нет во внешней таблице, ошибка
6) Внешний ключ по SERIAL/IDENTITY колонкам
Мало того, что это не имеет физического смысла, это довольно вероятно со временем приведет к ошибкам при вставке, когда соответствующие значения во внешней таблице закончатся.
7) Уникальные TIMESTAMP колонки
Не имеет физического смысла в большинстве случаев, т.к. TIMESTAMP имеет точность в 1 микросекунду.
Кроме того, могут возникнуть проблемы при массовой вставке с использованием функций времени, фиксируемых на начало транзакции, например NOW()
8) Уникальный индекс включает другой уникальный индекс
Бессмысленно и имеет побочные эффекты, аналогичные (1)
9) Похожие имена
PostgreSQL приводит к нижнему регистру все имена, указанные без кавычек. Snowflake к верхнему регистру. Но имена в кавычках будут регистро-чувствительными.
"Field", "field" и field (превратится в FIELD в Snowflake) будут считаться разными идентификаторами, что может привести к ошибке
10) Неявная генерация имен
Для ограничений, индексов или других объектов, которые не требуют обязательного указания имени при создании, база данных подбирает имена самостоятельно.
В PostgreSQL для этого есть специальный алгоритм. Но если сгенерированные имена объектов похожи, к ним будут добавлены числовые индексы.
А это значит, что важен порядок создания этих объектов.
Поэтому на разных экземплярах базы имена одних и тех же объектов могут быть перепутаны.
Это может привести к очень печальным последствиям, например, при удалении объектов.
Но Snowflake ведет себя иначе. Он генерирует неуказанные явно имена случайным образом. При изменениях схемы такое поведение потребует большого количества внимательности и ручного труда.
Первый документ, который стоит изучить: https://wiki.postgresql.org/wiki/Don't_Do_This
Там вы найдете небольшое количество правил, которые легко учесть при разработке, если вы не используете ORM.
Но на что еще нам стоит обратить внимание?
Покажу 10 из 1.5k правил.
Пара моментов, связанных с производительностью:
1) зеркальные индексы (a,b) и (b,a) или индексы с перестановкой
В общем случае невозможно определить оптимальный порядок колонок в индексе. При анализе стоит учесть все множество запросов в проекте, которые могли бы частично использовать многоколоночный индекс.
Но даже если таких запросов нет, они могут появиться.
В любом случае стоит избегать индексов одного типа, созданных по одним и тем же полям. Это замедлит обновление таблицы, увеличит нагрузку на диск, а значит заафектит производительность всей базы.
2) UNIQUE индекс, включающий поля неограниченного размера
Проверка уникальности сама по себе дорогая операция, которая будет тормозить изменение таблицы. Но кроме того, такой индекс таит в себе еще несколько опасных побочных эффектов.
Во-первых, полная уникальность длинной строки, скорее всего не имеет смысла с точки зрения архитектуры. Обычно нужны будут каки-то функции от текста - lowercase, tsvector, или что-то более сложное.
Во-вторых, в один прекрасный момент можно получить ошибку вот с таким хинтом:
Values larger than 1/3 of a buffer page cannot be indexed.
Поэтому рекомендуется строить такие индексы по какому-нибудь хешу
Несколько архитектурных issues:
3) заданное DEFAULT значение без ограничений NOT NULL
Самый распространенный кейс использования значений по умолчанию - отсутствие необходимости указывать значение при вставке данных.
Скорее всего это означает, что отсутствие значения нас не устроит.
Стоит обозначить это явно.
4) Константные DEFAULT значения для колонок, используемых в уникальных индексах
Может привести к возникновению ошибки при вставке строк без явного указания значений для таких колонок.
Скорее всего в тестах такой кейс не придет никому в голову, а значит найти причину будет сложно.
5) DEFAULT значение для колонок с внешним ключем
Кроме очевидного вопроса "зачем?" возникает 2 опасных кейса при вставке данных:
- значение DEFAULT есть во внешней таблице, но не соответствует бизнес логике. Это ломает связанность данных
- значения нет во внешней таблице, ошибка
6) Внешний ключ по SERIAL/IDENTITY колонкам
Мало того, что это не имеет физического смысла, это довольно вероятно со временем приведет к ошибкам при вставке, когда соответствующие значения во внешней таблице закончатся.
7) Уникальные TIMESTAMP колонки
Не имеет физического смысла в большинстве случаев, т.к. TIMESTAMP имеет точность в 1 микросекунду.
Кроме того, могут возникнуть проблемы при массовой вставке с использованием функций времени, фиксируемых на начало транзакции, например NOW()
8) Уникальный индекс включает другой уникальный индекс
Бессмысленно и имеет побочные эффекты, аналогичные (1)
9) Похожие имена
PostgreSQL приводит к нижнему регистру все имена, указанные без кавычек. Snowflake к верхнему регистру. Но имена в кавычках будут регистро-чувствительными.
"Field", "field" и field (превратится в FIELD в Snowflake) будут считаться разными идентификаторами, что может привести к ошибке
10) Неявная генерация имен
Для ограничений, индексов или других объектов, которые не требуют обязательного указания имени при создании, база данных подбирает имена самостоятельно.
В PostgreSQL для этого есть специальный алгоритм. Но если сгенерированные имена объектов похожи, к ним будут добавлены числовые индексы.
А это значит, что важен порядок создания этих объектов.
Поэтому на разных экземплярах базы имена одних и тех же объектов могут быть перепутаны.
Это может привести к очень печальным последствиям, например, при удалении объектов.
Но Snowflake ведет себя иначе. Он генерирует неуказанные явно имена случайным образом. При изменениях схемы такое поведение потребует большого количества внимательности и ручного труда.
Вчера я рассказал о 10 проблемах, которые можно встретить в схемах БД (DDL), а сегодня поговорим о том, что можно найти в самих запросах (DML).
Так же, как и вчера, я покажу 10 случайных правил из моей обширной полутора тысячной коллекции :)
Несколько заметок о производительности
1) count(DISTINCT unique)
Когда разработчик хочет посчитать количество уникальных значений, он скорее всего использует конструкцию count(DISTINCT).
Но если колонка, по которому требуется сделать выборку, является уникальным в силу уже имеющихся ограничений (уникальная колонка, результат работы функции в подзапросе и тд), то убрав DISTINCT в этом выражении, мы ускорим count в 100 раз.
2) Проверка на NULL значений после приведения типов
Причем тайпкаст может быть прийти из подзапроса и обнаружить это будет не так просто.
Если такая конструкция используется в массовых операциях при сканировании таблицы, то довольно просто можно ускорить запросы просто убрав приведение типов.
Как понимаете, такие запросы довольно просто получить при использовании различных ORM.
3) В условии JOIN ON не используется присоединяемая таблица
Довольно популярная ошибка, которую так же можно отнести и в разряд архитектурных. Встречается повсеместно при копипасте:
Это приведет к лавинообразному росту: для каждой строки, где t1.id = t.id будут присоединяться ВСЕ данные из таблицы t2.
При аналитических запросах, где используются агрегационные функции довольно просто такое не заметить
4) JOIN таблиц без использования внешних ключей
Если не существует внешнего ключа, описывающего связь t1.id и t2.id, то вполне возможно, что в запрос закралась ошибка, полученная при копипасте.
Эту проблему надо рассматривать вместе еще с одной DDL-проблемой: если есть внешний ключ, то исходящее поле стоит включить в индекс, т.к. по внешнему ключу скорее всего будет JOIN.
Целевая таблица внешнего ключа обязана иметь уникальный индекс, а в исходящей стоит создать обычный
JOIN по полям, не имеющих связи в виде внешнего ключа могут и не иметь подходящих индексов, что замедлит операцию.
Но эта ошибка выглядит более страшно с точки зрения архитектуры. Такой запрос вернет невалидный результат! Мы связали поля, которые не имеют логической связи.
5) порядок колонок в GROUP BY _МОЖЕТ_ иметь значение
Порядок колонок в группировке никак не влияет на содержимое результата. Но на производительность может влиять существенно. Результат _МОЖЕТ_ зависеть от кучи вещей:
вариативность данных, колонка или выражение используется в группировке, версия базы, включена ли JIT компиляция, и еще кучи параметров.
Попробуйте поменять порядок колонок в GROUP BY и возможно, вам удастся ускорить запрос в несколько раз
Но в bigquery, говорят, все пошло еще дальше, и там рекомендуют самостоятельно заниматься расстановкой условий в WHERE:
2.3 WHERE clause: Expression order matters
https://wklytech.medium.com/bigquery-sql-optimization-c7a7db170c56
Я все еще очень надеюсь, что это fake news :)
Так же, как и вчера, я покажу 10 случайных правил из моей обширной полутора тысячной коллекции :)
Несколько заметок о производительности
1) count(DISTINCT unique)
Когда разработчик хочет посчитать количество уникальных значений, он скорее всего использует конструкцию count(DISTINCT).
Но если колонка, по которому требуется сделать выборку, является уникальным в силу уже имеющихся ограничений (уникальная колонка, результат работы функции в подзапросе и тд), то убрав DISTINCT в этом выражении, мы ускорим count в 100 раз.
2) Проверка на NULL значений после приведения типов
A IS NOT NULLв 2 раза быстрее чем
A :: type IS NOT NULL
Причем тайпкаст может быть прийти из подзапроса и обнаружить это будет не так просто.
Если такая конструкция используется в массовых операциях при сканировании таблицы, то довольно просто можно ускорить запросы просто убрав приведение типов.
Как понимаете, такие запросы довольно просто получить при использовании различных ORM.
3) В условии JOIN ON не используется присоединяемая таблица
Довольно популярная ошибка, которую так же можно отнести и в разряд архитектурных. Встречается повсеместно при копипасте:
... t1 JOIN t2 ON t1.id = t.id
Это приведет к лавинообразному росту: для каждой строки, где t1.id = t.id будут присоединяться ВСЕ данные из таблицы t2.
При аналитических запросах, где используются агрегационные функции довольно просто такое не заметить
4) JOIN таблиц без использования внешних ключей
JOIN t2 ON t1.id = t2.id
Если не существует внешнего ключа, описывающего связь t1.id и t2.id, то вполне возможно, что в запрос закралась ошибка, полученная при копипасте.
Эту проблему надо рассматривать вместе еще с одной DDL-проблемой: если есть внешний ключ, то исходящее поле стоит включить в индекс, т.к. по внешнему ключу скорее всего будет JOIN.
Целевая таблица внешнего ключа обязана иметь уникальный индекс, а в исходящей стоит создать обычный
JOIN по полям, не имеющих связи в виде внешнего ключа могут и не иметь подходящих индексов, что замедлит операцию.
Но эта ошибка выглядит более страшно с точки зрения архитектуры. Такой запрос вернет невалидный результат! Мы связали поля, которые не имеют логической связи.
5) порядок колонок в GROUP BY _МОЖЕТ_ иметь значение
Порядок колонок в группировке никак не влияет на содержимое результата. Но на производительность может влиять существенно. Результат _МОЖЕТ_ зависеть от кучи вещей:
вариативность данных, колонка или выражение используется в группировке, версия базы, включена ли JIT компиляция, и еще кучи параметров.
Попробуйте поменять порядок колонок в GROUP BY и возможно, вам удастся ускорить запрос в несколько раз
Но в bigquery, говорят, все пошло еще дальше, и там рекомендуют самостоятельно заниматься расстановкой условий в WHERE:
2.3 WHERE clause: Expression order matters
https://wklytech.medium.com/bigquery-sql-optimization-c7a7db170c56
Я все еще очень надеюсь, что это fake news :)
Перейдем к архитектурным проблемам
6) LIMIT/OFFSET без ORDER BY
К сожалению не все знают, что в PostgreSQL нет сортировки по умолчанию. Это значит что в общем случае вы каждый раз можете получать случайный порядок строк. Не просто один и тот же случайный, а каждый раз новый
Если от порядка строк зависит бизнес логика внутри приложения, обязательно используйте ORDER BY
Ситуация ухудшается, если вы используете OFFSET/LIMIT. Пока оставим за скобками то, что не рекомендуется использовать эти конструкции.
Если порядок не определен, то используя OFFSET/LIMIT, вы можете получить случайный кусок данных, а совсем не тот, на который вы рассчитывали.
7) LIMIT применяется к запросу, возвращающему 1 строку
Это яркий пример наложения чрезмерных ограничений. Вроде ничего страшного нет. На данные LIMIT не повлияет. Да, но может повлиять на разработчика, который будет менять такой запрос.
Так же может увеличить сложность приложения, которое напишет разработчик, ожидающий массив, а не одну строку.
8) OFFSET применяется к запросу, возвращающему 1 строку
А вот это уже действительно плохо. Запрос с OFFSET > 0 будет возвращать пустой результат, там где он на самом деле есть.
SELECT 1 OFFSET 1
А т.к. OFFSET, как правило, передается из приложения, получить пустоту особенно легко
9) псевдоним выражения совпадает с именем поля
Казалось бы, что тут такого? Но давайте снова подумаем о разработчике, который, быть может, и запроса самого не видит, а лишь использует результат в приложении.
10) отсутствует ORDER BY для агрегационных функций
В PosgreSQL существует ряд функций, которые могут вернуть результат в виде массива или объекта. Например, array_agg. Мы помним, что порядок результатов у нас не гарантирован?
Ну так вот,
Если мы хотим иметь гарантированно упорядоченный массив, нам нужно указать порядок внутри самой функции - array_agg(f ORDER BY f)
6) LIMIT/OFFSET без ORDER BY
К сожалению не все знают, что в PostgreSQL нет сортировки по умолчанию. Это значит что в общем случае вы каждый раз можете получать случайный порядок строк. Не просто один и тот же случайный, а каждый раз новый
Если от порядка строк зависит бизнес логика внутри приложения, обязательно используйте ORDER BY
Ситуация ухудшается, если вы используете OFFSET/LIMIT. Пока оставим за скобками то, что не рекомендуется использовать эти конструкции.
Если порядок не определен, то используя OFFSET/LIMIT, вы можете получить случайный кусок данных, а совсем не тот, на который вы рассчитывали.
7) LIMIT применяется к запросу, возвращающему 1 строку
Это яркий пример наложения чрезмерных ограничений. Вроде ничего страшного нет. На данные LIMIT не повлияет. Да, но может повлиять на разработчика, который будет менять такой запрос.
Так же может увеличить сложность приложения, которое напишет разработчик, ожидающий массив, а не одну строку.
8) OFFSET применяется к запросу, возвращающему 1 строку
А вот это уже действительно плохо. Запрос с OFFSET > 0 будет возвращать пустой результат, там где он на самом деле есть.
SELECT 1 OFFSET 1
А т.к. OFFSET, как правило, передается из приложения, получить пустоту особенно легко
9) псевдоним выражения совпадает с именем поля
SELECT id + 1 AS id FROM t1
Казалось бы, что тут такого? Но давайте снова подумаем о разработчике, который, быть может, и запроса самого не видит, а лишь использует результат в приложении.
#define TRUE FALSE //Happy debugging suckers
10) отсутствует ORDER BY для агрегационных функций
В PosgreSQL существует ряд функций, которые могут вернуть результат в виде массива или объекта. Например, array_agg. Мы помним, что порядок результатов у нас не гарантирован?
Ну так вот,
array_agg(f) ... ORDER BY fне поможет...
Если мы хотим иметь гарантированно упорядоченный массив, нам нужно указать порядок внутри самой функции - array_agg(f ORDER BY f)
На всякий случай напомню, что это контент я готовлю для https://twitter.com/sqaunderhood, которым я рулю на этой неделе.
Богомерзкий твиттер приносит много боли, заставляя пилить все на 280 символов. Мне, привыкшему к длинным телегам, это больно :)
Поэтому вечером я скидываю все что понаписал сюда.
Но я дублирую (как могу) на английском в своем акке, поэтому, если захотите пошарить с англоговорящими коллегами, велкам https://twitter.com/antonrevyako
Богомерзкий твиттер приносит много боли, заставляя пилить все на 280 символов. Мне, привыкшему к длинным телегам, это больно :)
Поэтому вечером я скидываю все что понаписал сюда.
Но я дублирую (как могу) на английском в своем акке, поэтому, если захотите пошарить с англоговорящими коллегами, велкам https://twitter.com/antonrevyako
Twitter
Протестировал (@sqaunderhood) | Twitter
The latest Tweets from Протестировал (@sqaunderhood). Коллективный твиттер о качественной разработке ПО.
Контакт: @estet
Контакт: @estet
Проблемы на границе сопряжения приложения и базы.
Описываемые проблемы есть и у тех, кто пишет SQL руками, и у пользователей ORM (возможно в меньшей степени).
Чем хороши ORM?
- не надо думать о соответствии типов в приложении и базе. Об этом подумали разработчики фреймворка
- не надо думать о консистентности схемы и запросов. Если схема изменилась и запрос перестал соответствовать схеме, вы узнаете об этом прямо в IDE
- кодогенерация
Разработчик, который пишет чистые SQL-запросы, о таком функционале может только мечтать.
Нет никакого универсального способа заключить контракт между базой и приложением так, чтобы сохранялся контроль над консистентностью типов и соответствии запросов и схемы.
Насколько это большая проблема?
На мой взгляд, довольно серьезная.
При изменении базы или запросов спасти смогут только интеграционные тесты.
Если они очень подробно написаны. Особенно fail-кейсы.
Но это не точно...
Что может пойти не так?
Для результатов запросов можно выделить несколько проблем:
1) Изменятся типы полей
2) Изменится nullabitity
3) Изменится класс количества строк в ответе
4) Изменится логическая связанность полей
5) Изменится порядок строк
1) Изменятся типы полей
Типы полей в результатах могут смениться по нескольким причинам. Например:
- изменился тип исходной колонки в схеме БД
- изменилась функция или выражение для вычисляемого поля (или их параметры)
Возможно, это сразу будет выявлено при тестировании. Например, в js изменение INT на BIGINT или FLOAT приведет к изменению типа с number на string внутри приложения.
Представим, что с данными ничего не происходит внутри приложения, а они всего лишь отдаются во внешние сервисы.
При этом рантайму все равно что там за тип вернула база (js, python и тд).
Можно представить что может произойти при недостаточно строгих тестах...
Или еще вариант: BIGINT преобразуется в строку. Но внезапно вместо числа в строке мы начинаем получать произвольный текст.
И даже если у нас есть json-schema, которая используется в тестах, мы скорее всего ничего не заметим.
Т.е. либо мы должны иметь очень строгие тесты, либо очень внимательных инженеров на круглосуточном дежурстве :)
2) Изменится nullabitity
Почему такое может произойти?
- изменилось ограничение на NULL у исходной колонки в схеме БД
- добавился или исчез внешний ключ между таблицами при JOIN
- изменился тип JOIN
- изменились условия фильтрации в оригинальном запросе или вложенных запросах
Например, если удалили внешний ключ, по которому производится LEFT JOIN, то уже нельзя будет гарантировать то, что вместо присоединяемого значения мы не получим NULL.
Аналогичная ситуация возникнет, если внешний ключ есть, но JOIN изменили на LEFT JOIN.
Изменившиеся условия фильтрации так же могут влиять на nullabitity:
SELECT (SELECT 1 WHERE TRUE)
vs
SELECT (SELECT 1 WHERE FALSE)
Это будет проблемой во всех языках, кроме, наверное, Java, где ожидаемо, что любое поле объекта nullable :)
Результат - много 500ых ошибок в логах...
Описываемые проблемы есть и у тех, кто пишет SQL руками, и у пользователей ORM (возможно в меньшей степени).
Чем хороши ORM?
- не надо думать о соответствии типов в приложении и базе. Об этом подумали разработчики фреймворка
- не надо думать о консистентности схемы и запросов. Если схема изменилась и запрос перестал соответствовать схеме, вы узнаете об этом прямо в IDE
- кодогенерация
Разработчик, который пишет чистые SQL-запросы, о таком функционале может только мечтать.
Нет никакого универсального способа заключить контракт между базой и приложением так, чтобы сохранялся контроль над консистентностью типов и соответствии запросов и схемы.
Насколько это большая проблема?
На мой взгляд, довольно серьезная.
При изменении базы или запросов спасти смогут только интеграционные тесты.
Если они очень подробно написаны. Особенно fail-кейсы.
Но это не точно...
Что может пойти не так?
Для результатов запросов можно выделить несколько проблем:
1) Изменятся типы полей
2) Изменится nullabitity
3) Изменится класс количества строк в ответе
4) Изменится логическая связанность полей
5) Изменится порядок строк
1) Изменятся типы полей
Типы полей в результатах могут смениться по нескольким причинам. Например:
- изменился тип исходной колонки в схеме БД
- изменилась функция или выражение для вычисляемого поля (или их параметры)
Возможно, это сразу будет выявлено при тестировании. Например, в js изменение INT на BIGINT или FLOAT приведет к изменению типа с number на string внутри приложения.
Представим, что с данными ничего не происходит внутри приложения, а они всего лишь отдаются во внешние сервисы.
При этом рантайму все равно что там за тип вернула база (js, python и тд).
Можно представить что может произойти при недостаточно строгих тестах...
Или еще вариант: BIGINT преобразуется в строку. Но внезапно вместо числа в строке мы начинаем получать произвольный текст.
И даже если у нас есть json-schema, которая используется в тестах, мы скорее всего ничего не заметим.
Т.е. либо мы должны иметь очень строгие тесты, либо очень внимательных инженеров на круглосуточном дежурстве :)
2) Изменится nullabitity
Почему такое может произойти?
- изменилось ограничение на NULL у исходной колонки в схеме БД
- добавился или исчез внешний ключ между таблицами при JOIN
- изменился тип JOIN
- изменились условия фильтрации в оригинальном запросе или вложенных запросах
Например, если удалили внешний ключ, по которому производится LEFT JOIN, то уже нельзя будет гарантировать то, что вместо присоединяемого значения мы не получим NULL.
Аналогичная ситуация возникнет, если внешний ключ есть, но JOIN изменили на LEFT JOIN.
Изменившиеся условия фильтрации так же могут влиять на nullabitity:
SELECT (SELECT 1 WHERE TRUE)
vs
SELECT (SELECT 1 WHERE FALSE)
Это будет проблемой во всех языках, кроме, наверное, Java, где ожидаемо, что любое поле объекта nullable :)
Результат - много 500ых ошибок в логах...
3) Изменится класс количества строк в ответе
Что такое класс количества строк?
Количество строк можно разделить на 5 групп: ни одной, одна, одна или ни одной, много, много или ни одной.
И я крайне редко видел, чтобы кто-то проверял класс количества строк в приложениях.
Но это крайне важная метрика, которая очень сильно влияет на качество и безопасность приложения!
Например, сложный запрос с несколькими CTE создает заявки на исходящие выплаты клиентам.
Подразумеваться, что создается 1 документ за раз и возвращается его ID.
В приложении мы берем первую строку ответа и используем данные из нее. Если что-то пошло не так и в ответе не вернулось ни одной строки, мы, скорее всего это проверим и выбросим ошибку в приложении.
А что, если вернулось много строк? Мы взяли первую, но про остальные мы ничего не узнаем.
Мы не планировали, что запрос может вернуть несколько строк.
И хорошо, если мы не разрабатываем какой-нибудь крипто-сервис, который автоматически исполняет заявки на выплаты :)
Причина почему класс количества строк может измениться, аналогичны пункту (2).
Но если в случае с NULL приложение может упасть и оставить незавершенные задачи, то в данном случае последствия могут быть куда хуже.
И ладно бы, если это проблема возникала только когда что-то будет меняться. С вероятностью, близкой к 100% это проблема уже существует в вашем приложении на данный момент, т.е. довольно сложно "на глаз" определить корректный класс количества строк в сложном запросе.
Сложно сказать, насколько ORM может помочь в контроле класса строк, но, полагаю, что не сильно.
4) Изменится логическая связанность полей
Тоже довольно опасная категория изменений.
Что, если вместо t1.id будет возвращаться t1.id + 1?
Или MAX(t1.id)?
Или COALESCE(t1.id, 0)?
Или вообще t2.id?
Особенно если эта колонка напрямую не влияет на дальнейшие действия внутри приложения, а отдается клиенту "как есть".
Произойти такое изменение может на любом уровне вложенности или во внешних объектах, например во VIEW.
5) Изменится порядок строк
Про эту проблему я рассказывал вчера. В PostgreSQL нет гарантированного порядка строк, если не указан ORDER BY.
Если внутри приложения от порядка строк что-то зависит, то такое изменение может сломать этот функционал.
Изменение порядка строк может так же произойти где-то в подзапросе и заметить это будет довольно сложно.
Хорошо, если тесты написаны с учетом того, что порядок строк важен.
А теперь давайте окинем взглядом все что я тут понаписал за 3 дня и трезво оценим - что из этого учтено в ваших проектах?
А это лишь малая доля возможных проблем.
Этому городу нужен супергерой!
Давайте завтра прикинем что нам нужно, чтобы сделать анализатор, который спасет нас от всех этих проблем.
Что такое класс количества строк?
Количество строк можно разделить на 5 групп: ни одной, одна, одна или ни одной, много, много или ни одной.
И я крайне редко видел, чтобы кто-то проверял класс количества строк в приложениях.
Но это крайне важная метрика, которая очень сильно влияет на качество и безопасность приложения!
Например, сложный запрос с несколькими CTE создает заявки на исходящие выплаты клиентам.
Подразумеваться, что создается 1 документ за раз и возвращается его ID.
В приложении мы берем первую строку ответа и используем данные из нее. Если что-то пошло не так и в ответе не вернулось ни одной строки, мы, скорее всего это проверим и выбросим ошибку в приложении.
А что, если вернулось много строк? Мы взяли первую, но про остальные мы ничего не узнаем.
Мы не планировали, что запрос может вернуть несколько строк.
И хорошо, если мы не разрабатываем какой-нибудь крипто-сервис, который автоматически исполняет заявки на выплаты :)
Причина почему класс количества строк может измениться, аналогичны пункту (2).
Но если в случае с NULL приложение может упасть и оставить незавершенные задачи, то в данном случае последствия могут быть куда хуже.
И ладно бы, если это проблема возникала только когда что-то будет меняться. С вероятностью, близкой к 100% это проблема уже существует в вашем приложении на данный момент, т.е. довольно сложно "на глаз" определить корректный класс количества строк в сложном запросе.
Сложно сказать, насколько ORM может помочь в контроле класса строк, но, полагаю, что не сильно.
4) Изменится логическая связанность полей
Тоже довольно опасная категория изменений.
Что, если вместо t1.id будет возвращаться t1.id + 1?
Или MAX(t1.id)?
Или COALESCE(t1.id, 0)?
Или вообще t2.id?
Особенно если эта колонка напрямую не влияет на дальнейшие действия внутри приложения, а отдается клиенту "как есть".
Произойти такое изменение может на любом уровне вложенности или во внешних объектах, например во VIEW.
5) Изменится порядок строк
Про эту проблему я рассказывал вчера. В PostgreSQL нет гарантированного порядка строк, если не указан ORDER BY.
Если внутри приложения от порядка строк что-то зависит, то такое изменение может сломать этот функционал.
Изменение порядка строк может так же произойти где-то в подзапросе и заметить это будет довольно сложно.
Хорошо, если тесты написаны с учетом того, что порядок строк важен.
А теперь давайте окинем взглядом все что я тут понаписал за 3 дня и трезво оценим - что из этого учтено в ваших проектах?
А это лишь малая доля возможных проблем.
Этому городу нужен супергерой!
Давайте завтра прикинем что нам нужно, чтобы сделать анализатор, который спасет нас от всех этих проблем.
У меня до сих пор эта шутка жива и висит на втором мониторе. Жаль, что не стала популярной. Хардварь стартапы - боль.
https://news.1rj.ru/str/pathetic_low_freq/392
https://news.1rj.ru/str/pathetic_low_freq/392
Telegram
Жалкие низкочастотники
Лет 10 назад я купил на несуществующем бложике прототип Lightpack: странной штуки для задней подсветки монитора, визуально расширяющей экран контекстными цветами. Прототип был в виде платы с кучей хвостов с LED-дами, в собранном виде напоминал ксеноморфа…
Сегодня создаем свой анализатор!
На самом деле за 3 дня я описал 2 разные проблемы (анализ запросов и контроль соответствия типов), которые и решать-то надо бы по-разному.
Но практика показала, что они решаются одновременно :)
Я покажу путь, которым шел я.
Давайте сделаем MVP системы контроля соответствия типов.
Нужно как-то смайнить типы из наших запросов, и потом сопоставить их с типами в приложении.
Давайте сначала погуглим...
Тадам!
https://github.com/adelsz/pgtyped
Все готово, берем и используем.
Только есть небольшой минус (он же плюс). Эта тулза "компилирует" запросы в prepared statements и уже из них извлекает типы.
И сделать это можно, только подключившись к базе.
Почему это минус? У нас же есть docker...
Потому что мы можем узнать только одну вещь из вчерашнего списка - типы на выходе.
Ни nullability, ни класса количества строк, ни сортировки, ни логической связанности - ничего этого нет.
А почему это плюс?
Этот отличный план, Уолтер. Надежный, как швейцарские часы (с)
Если получать типы из базы, то есть 100% гарантия, что именно такие типы и будут в рантайме.
На самом деле и это с оговорками, я вполне могу сделать запрос, у которого будут динамические типы в ответе.
Но, как говорится, не повторяйте это дома...
Но когда я озаботился вопросом создания своего анализатора, такой волшебной тулзины еще не было, а сам я до такого не догадался, и тогда пришли они - РЕКЕКСПЫ!
боже_крис_они_же_еще_дети.gif
Скажу сразу: ТАК ДЕЛАТЬ НЕ НАДО. Даже если очень захочется. Почему - расскажу чуть позже.
Но мы же делаем MVP, поэтому кому не пофиг? :)
Т.к. у нас есть только запросы и все, нам нужно взять откуда-то информацию о типах.
Где их взять? Давайте сами их укажем!
Кхе-кхе... Ну ок... теперь у нас есть информация о типах. Осталось пройтись по этому регекспом...
:: <тип> AS "<имя>" - это имена и типы результата
${<имя>} :: <тип> - это имена и типы параметров
Абсолютно проклято...
На самом деле таким лихим ходом мы избавились о необходимости запускать инстанс БД. И типы гарантированы - мы ж к ним тайпкастим.
Но что может пойти не так?
Во-первых, никто так запросы не пишет. Это гемо... утомительно.
Во-вторых, ни о каком соответствии схеме тут речи не идет. Если схема изменится - типы не изменятся. И вот это уже сильно хуже, чем ситуация, где мы совсем ничего не знаем про типы.
Если мы ничего не знаем, есть шансы, что измененный тип будет отловлен в тестах. А тут без шансов.
И мы поставим под удар продакшен - там может случиться ошибка тайпкастинга, и мы получим исключение.
Не, ну можно, конечно, эти тайпкасты после обработки вырезать, но это уже какой-то перебор, у нас же MVP!
На самом деле за 3 дня я описал 2 разные проблемы (анализ запросов и контроль соответствия типов), которые и решать-то надо бы по-разному.
Но практика показала, что они решаются одновременно :)
Я покажу путь, которым шел я.
Давайте сделаем MVP системы контроля соответствия типов.
Нужно как-то смайнить типы из наших запросов, и потом сопоставить их с типами в приложении.
Давайте сначала погуглим...
Тадам!
https://github.com/adelsz/pgtyped
Все готово, берем и используем.
Только есть небольшой минус (он же плюс). Эта тулза "компилирует" запросы в prepared statements и уже из них извлекает типы.
И сделать это можно, только подключившись к базе.
Почему это минус? У нас же есть docker...
Потому что мы можем узнать только одну вещь из вчерашнего списка - типы на выходе.
Ни nullability, ни класса количества строк, ни сортировки, ни логической связанности - ничего этого нет.
А почему это плюс?
Этот отличный план, Уолтер. Надежный, как швейцарские часы (с)
Если получать типы из базы, то есть 100% гарантия, что именно такие типы и будут в рантайме.
На самом деле и это с оговорками, я вполне могу сделать запрос, у которого будут динамические типы в ответе.
Но, как говорится, не повторяйте это дома...
Но когда я озаботился вопросом создания своего анализатора, такой волшебной тулзины еще не было, а сам я до такого не догадался, и тогда пришли они - РЕКЕКСПЫ!
боже_крис_они_же_еще_дети.gif
Скажу сразу: ТАК ДЕЛАТЬ НЕ НАДО. Даже если очень захочется. Почему - расскажу чуть позже.
Но мы же делаем MVP, поэтому кому не пофиг? :)
Т.к. у нас есть только запросы и все, нам нужно взять откуда-то информацию о типах.
Где их взять? Давайте сами их укажем!
SELECT
id :: INT AS "id"
FROM users
WHERE
name = ${name} :: VARCHAR(16)
Кхе-кхе... Ну ок... теперь у нас есть информация о типах. Осталось пройтись по этому регекспом...
:: <тип> AS "<имя>" - это имена и типы результата
${<имя>} :: <тип> - это имена и типы параметров
Абсолютно проклято...
На самом деле таким лихим ходом мы избавились о необходимости запускать инстанс БД. И типы гарантированы - мы ж к ним тайпкастим.
Но что может пойти не так?
Во-первых, никто так запросы не пишет. Это гемо... утомительно.
Во-вторых, ни о каком соответствии схеме тут речи не идет. Если схема изменится - типы не изменятся. И вот это уже сильно хуже, чем ситуация, где мы совсем ничего не знаем про типы.
Если мы ничего не знаем, есть шансы, что измененный тип будет отловлен в тестах. А тут без шансов.
И мы поставим под удар продакшен - там может случиться ошибка тайпкастинга, и мы получим исключение.
Не, ну можно, конечно, эти тайпкасты после обработки вырезать, но это уже какой-то перебор, у нас же MVP!
Итак, типы мы доставать научились, как из этого сгенерить что-то полезное?
Возьмите чистую кастрюлю...
На самом деле тут потребуется шаг, на котором споткнется процентов 90 всех проектов:
мы не сможем ничего сгенерировать и проанализировать, если запросы находятся вперемешку с кодом приложения.
Нужно вынести SQL в отдельные файлы.
Плюсов у этого приблизительно дофига, а минус ровно один - это нужно сделать :)
Во-первых, SQL-код становится более качественным, когда у вас пропадает возможность клеить его в рантайме.
Во-вторых, увеличивается покрытие тестами.
В-третьих, теперь изменения запросов в системе контроля версий можно отслеживать отдельно от кода приложения.
Так же улучшается и архитектура самого приложения: там, где раньше была лапша if'ов и склейки строк, теперь пяток отдельных методов.
И для каждого описаны типы!
Ну допустим. А дальше?
А дальше берем любой шаблонизатор...
Нет, ну можно, конечно, заморочиться с каким-нибудь конструктором AST и депарсером, но вы помните, что мы все еще делаем MVP?
Короче - у нас есть типы и теперь мы можем превратить их в ваши модели, POJO или json-schema
Заметьте, что мы уже реализовали 2/3 плюсов ORM, о которых говорили вчера. Теперь нам не надо думать о соответствии типов в приложении и базе и у нас есть кодогенерация!
Но все-таки из списка проблем на границе базы и приложения решена только 1 проблема из 5.
В моем случае такая тулза сэкономила где-то процентов 30 рабочего времени. Если бы не это извращение с регекспами, сэкономило бы все 50.
В принципе, тут бы можно было и остановиться, ситуация уже лучше, чем была до начала разговора.
Но мы ж поргомисты, пляшем дальше...
А вот дальше нам нужно сделать качественный скачок от как-то полученных типов к анализу запросов.
И тут вариант один - нам нужен AST парсер.
Если у нас будет AST мы сможем сделать как минимум linter-like правила.
Например, второе правило вторника - avoid typecast IS [NOT] NULL
И это моя особая боль. Официальных AST парсеров для диалектов SQL можно сказать, что нет.
Ныть об этом я могу еще один день, но это вряд ли кому-то будет интересно.
Если коротко - вам придется много программировать. Ну или не придется, если вы не бросите читать меня до завтра :)
Глобально нам повезло. Для #PostgreSQL есть AST парсер, который смогли выковырять из сервера:
https://github.com/lfittl/libpg_query
Есть обертки для разных языков.
Минус один - стабильный парсер есть только диалекта десятой версии.
Если к информации о типах и AST мы добавим данные из INFORMATION_SCHEMA (да, придется опять использовать живую базу), то мы при должном упорстве даже сможем понимать типы в подзапросах.
Это все даст нам возможность придумывать более сложные правила для анализатора.
И даже в некоторых простых случаях мы сможем следить за связанностью полей.
Как можно догадаться, linter-like правила на AST это тот рубеж, после которого начинается настоящее месилово.
Потом нам понадобится описание всех типов всех функций, операторов, правила выполнения implicit typecast и много-много всякого...
Например, только из AST нельзя однозначно понять что обозначает * в запросе
Вы можете сказать - "ну вон же, FROM t"!
А если есть колонка t.t? А если она композитного типа?
Короче, все сложно...
Дальше либо стоит остановиться (мы и так неплохо поработали), либо придется писать КОМПИЛЯТОР SQL в Intermediate Representation Object, фактически повторяя поведение базы...
Я, пожалуй, остановлюсь, т.к. компилятор это явно перебор :)
Скажу лишь, что я его написал*...
Поэтому завтра будут истории о том, как просто можно им воспользоваться уже сейчас и какие бенефиты вы от этого получите!
Возьмите чистую кастрюлю...
На самом деле тут потребуется шаг, на котором споткнется процентов 90 всех проектов:
мы не сможем ничего сгенерировать и проанализировать, если запросы находятся вперемешку с кодом приложения.
Нужно вынести SQL в отдельные файлы.
Плюсов у этого приблизительно дофига, а минус ровно один - это нужно сделать :)
Во-первых, SQL-код становится более качественным, когда у вас пропадает возможность клеить его в рантайме.
Во-вторых, увеличивается покрытие тестами.
В-третьих, теперь изменения запросов в системе контроля версий можно отслеживать отдельно от кода приложения.
Так же улучшается и архитектура самого приложения: там, где раньше была лапша if'ов и склейки строк, теперь пяток отдельных методов.
И для каждого описаны типы!
Ну допустим. А дальше?
А дальше берем любой шаблонизатор...
Нет, ну можно, конечно, заморочиться с каким-нибудь конструктором AST и депарсером, но вы помните, что мы все еще делаем MVP?
Короче - у нас есть типы и теперь мы можем превратить их в ваши модели, POJO или json-schema
Заметьте, что мы уже реализовали 2/3 плюсов ORM, о которых говорили вчера. Теперь нам не надо думать о соответствии типов в приложении и базе и у нас есть кодогенерация!
Но все-таки из списка проблем на границе базы и приложения решена только 1 проблема из 5.
В моем случае такая тулза сэкономила где-то процентов 30 рабочего времени. Если бы не это извращение с регекспами, сэкономило бы все 50.
В принципе, тут бы можно было и остановиться, ситуация уже лучше, чем была до начала разговора.
Но мы ж поргомисты, пляшем дальше...
А вот дальше нам нужно сделать качественный скачок от как-то полученных типов к анализу запросов.
И тут вариант один - нам нужен AST парсер.
Если у нас будет AST мы сможем сделать как минимум linter-like правила.
Например, второе правило вторника - avoid typecast IS [NOT] NULL
И это моя особая боль. Официальных AST парсеров для диалектов SQL можно сказать, что нет.
Ныть об этом я могу еще один день, но это вряд ли кому-то будет интересно.
Если коротко - вам придется много программировать. Ну или не придется, если вы не бросите читать меня до завтра :)
Глобально нам повезло. Для #PostgreSQL есть AST парсер, который смогли выковырять из сервера:
https://github.com/lfittl/libpg_query
Есть обертки для разных языков.
Минус один - стабильный парсер есть только диалекта десятой версии.
Если к информации о типах и AST мы добавим данные из INFORMATION_SCHEMA (да, придется опять использовать живую базу), то мы при должном упорстве даже сможем понимать типы в подзапросах.
Это все даст нам возможность придумывать более сложные правила для анализатора.
И даже в некоторых простых случаях мы сможем следить за связанностью полей.
Как можно догадаться, linter-like правила на AST это тот рубеж, после которого начинается настоящее месилово.
Потом нам понадобится описание всех типов всех функций, операторов, правила выполнения implicit typecast и много-много всякого...
Например, только из AST нельзя однозначно понять что обозначает * в запросе
SELECT t.* FROM tЗдесь t.* это не table = 't', columns = '*', а массив ['t', '*']
Вы можете сказать - "ну вон же, FROM t"!
А если есть колонка t.t? А если она композитного типа?
Короче, все сложно...
Дальше либо стоит остановиться (мы и так неплохо поработали), либо придется писать КОМПИЛЯТОР SQL в Intermediate Representation Object, фактически повторяя поведение базы...
Я, пожалуй, остановлюсь, т.к. компилятор это явно перебор :)
Скажу лишь, что я его написал*...
Поэтому завтра будут истории о том, как просто можно им воспользоваться уже сейчас и какие бенефиты вы от этого получите!