Чистый SQL или ORM и Query Builder?

Небольшая заметка с полей.

Недавно втянулся в использование SQL синтаксиса LEFT JOIN LATERAL – ключевое слово LATERAL. Буквально по-другому стал смотреть на решение некоторых задач!

Проверил по документации, погугил, в популярных PHP ORM – нигде нет поддержки LATERAL, ни в Doctrine, ни в Laravel Query Builder, ни в Yii Query Builder, ни в Cycle ORM

И тут хочу дать пояснение, моё отношение к различным Query Builder и обёрткам над SQL синтаксисом. Вот какой подход я применяю при выборе между написанием простого SQL и использованием Query Builder / ORM:

• Если мне нужно поработать с конкретной сущностью в ООП стиле, скорее всего у меня уже есть описание модели реализованное для той или иной ORM, соответственно использую модель, использую ООП, под капотом все запросы за меня строит и выполняет ORM;

• Зачастую базовый фреймворк / админка / CRM предоставляют некую точку расширения, где нужно вписать небольшую функцию или переопределить метод и в этой точке расширения фреймворк нам в явном виде передаёт заготовку Query Builder – использую эту заготовку, навешивая свои дополнения к запросу. Обычно в этот Query Builder я добавляю условия отбора или атрибуты;

• Если я сам пишу некий фреймворк и подготавливаю эти точки расширения для будущих программистов, то создаю заготовку Query Builder;

• Во всех остальных случаях, когда надо сделать выборку из базы и, скорее всего, это выборка из двух или более таблиц, да ещё с подзапросами или EXISTS – тогда предпочитаю чистый SQL! Обычный SQL хорошо читается глазами, хорошо подсвечивается в PhpStorm и отлаживать его гораздо проще, чем Query Builder с миксом из анонимных функций.

По моей схеме выше кажется, что чистый SQL занимает всего один пункт из четырёх, но на практике это не так уж мало!

Ещё одно возражение против написания SQL – я ухожу от высокоуровневых абстракций работы с сущностями и их связями и проваливаюсь на более низкий уровень хранения данных. Может лучше обернуть всю низкоуровневую работу как минимум в некий репозитории или использовать языки типа DQL? Может и лучше. Надо смотреть по ситуации.

Когда по проекту раскидано много уникальных и не тривиальных запросов на чистом SQL и, допустим, мы решили сделать рефакторинг реляционной схемы БД, в этом случае придётся перелопатить все вручную написанные SQL запросы, раскиданные по разным файлам и модулям – проблемка. Кажется, используя паттерн репозиторий, т. е. некие классы прослойки, инкапсулирующие конкретные SQL запросы, большой рефакторинг схемы данных был бы проще.

С другой стороны, подобные классы репозитории со временем накапливают в себе совершенно разношерстные SQL запросы, множество частных случаев и в итоге превращаются некие GOD репозитории (по аналогии с GOD Object или Helper или Utils классами). Либо, если делить классы репозитории по модулям и подсистемам их становится так много, мы возвращаемся к тому, с чего начали – SQL запросы размазаны по коду всего проекта, сложно рефакторить схему.

Как всегда то или иное решение – это компромисс или tradeoff. Мой опыт и мой выбор таков, что я не стесняюсь писать запросы на чистом SQL по месту их применения, не вынося всю работу с базой в некие репозитории. А когда придёт время рефакторинга PhpStorm мне поможет. Да, PhpStorm умеет рефакторить и SQL запросы тоже! Я могу переименовать таблицу или атрибут во вкладке Database и этот переименование произойдёт и в кодовой базе, но только в тех местах, где PhpStorm разспознал SQL язык, сделал так называемый language injection. Конечно, переименование и Find Usages в PhpStorm не покрывают всех сценариев рефакторинга схемы данных, но как это часто бывает по правилу Парето – в 80% случаев достаточно.

Под конец дам ссылку на презентацию доклада Валентина Удальцова с PHP Russia 2021. Процитирую основные тезисы:

ORM, QueryBuilder’ы и прочие абстракции связывают руки при попытке использовать БД на полную катушку. Какой смысл выбирать между PostgreSQL, MySQL и Oracle, если ваша библиотека всё равно не умеет в upsert, lateral join, returning, json path и оконные функции?

В докладе я расскажу, как мы в Happy Inc. прошли путь от Doctrine ORM через DBAL и кастомный QueryBuilder до нативных запросов в чистом виде, и объясню, почему это во всех смыслах выгодное архитектурное решение.

https://phprussia.ru/moscow/2021/abstracts/7654

Всем SQL!

Выпуск №42 — MySQL 8 и caching_sha2_password

На днях решил попробовать MySQL 8, но при подключении из PHP получил ошибку. Погуглил, нашел советы по исправлению на StackOverflow и в различных блогах — слепое выполнение найденных инструкции исправило ситуацию, подключение заработало, но это не наш путь! Надо разобраться, что собственно происходит и как правильно поступить?

https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/
https://bugs.php.net/bug.php?id=76651
http://databaseblog.myname.nl/2018/02/how-cachingsha2password-leaks-passwords.html
http://mysqlblog.fivefarmers.com/2015/08/31/protecting-mysql-passwords-with-sha256_password-plugin/

Результаты подключения к MySQL 8.0.13 из различных версий PHP для двух различных пользователей (mysql_native_password и cached_sha2_password) при значении в my.cnf default-authentication-plugin=caching_sha2_password (либо не указывать эту опцию в my.cnf, т.к. это значение по умолчанию)

PHP 7.2.5:
— user_mysql_native: success
— user_cached_sha2: The server requested authentication method unknown to the client [caching_sha2_password]

PHP 7.2.9:
— user_mysql_native: Unexpected server respose while doing caching_sha2 auth: 109
— user_cached_sha2: success

PHP 7.3.0RC5
— user_mysql_native: success
— user_cached_sha2: The server requested authentication method unknown to the client [caching_sha2_password]

Результаты подключения к MySQL 8.0.13 из различных версий PHP для двух различных пользователей (mysql_native_password и cached_sha2_password) при значении в my.cnf default-authentication-plugin=mysql_native_password

PHP 7.2.5:
— user_mysql_native: success
— user_cached_sha2: The server requested authentication method unknown to the client [caching_sha2_password]

PHP 7.2.9
— user_mysql_native: success
— user_cached_sha2: success

PHP 7.3.0RC5
— user_mysql_native: success
— user_cached_sha2: The server requested authentication method unknown to the client [caching_sha2_password]