Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

SQLAlchemy

Глава 20 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: SQLAlchemy
Автор: Michael Bayer
Перевод: А. Панин

20.5. SQL-запросы

В момент начала разработки SQLAlchemy способ генерации SQL-запросов не был ясен. Текстовый язык мог быть хорошим кандидатом; это стандартный подход, лежащий в основе таких широко известных инструментов объектно-реляционного отображения, как HQL из состава Hibernate. В случае использования языка программирования Python, однако, был доступен более занимательный вариант: использование объектов и выражений языка Python для генерации древовидных структур представления запросов, причем возможным было даже изменение назначения операторов языка Python с целью использования их для формирования SQL-запросов.

Хотя рассматриваемый инструмент и не был первым инструментом, выполняющим подобные функции, следует упомянуть о библиотеке SQLBuilder из состава SQLObject от Ian Bicking, которая была использована как образец при создании системы работы с объектами языка Python и операторами, используемыми в рамках языка формирования запросов SQLAlchemy. При использовании данного подхода объекты языка Python представляют лексические части SQL-запроса. Методы этих объектов, также как и перегружаемые операторы, позволяют генерировать новые унаследованные от существующих лексические конструкции. Наиболее часто используемым объектом является представляющий столбец объект "Column" - библиотека SQLObject будет представлять такие объекты в рамках класса объектно-реляционного отображения, используя пространство имен с доступом посредством атрибута .q; также в SQLAlchemy объявлен атрибут с именем .c. Этот атрибут .c на сегодняшний день поддерживается и используется для представления элементов основной части, подвергающихся выборке, таких, как объекты, представляющие таблицы и запросы выборки.

Древовидные структуры запросов

Конструкция SQL-запроса в SQLAlchemy очень похожа на структуру, которую вы можете создать в случае разбора готового SQL-запроса - это дерево разбора синтаксических конструкций, отличающееся лишь тем, что вместо формирования на основе строки запроса его создает непосредственно разработчик. Основной тип ветви этого дерева разбора носит имя ClauseElement, а Рисунок 20.7 иллюстрирует отношение класса ClauseElement и некоторых ключевых классов.

Базовая иерархия классов запроса
Рисунок 20.7: Базовая иерархия классов запроса

Путем использования функций-конструкторов, методов и перегруженных функций операторов языка Python, структура аналогичного следующему запроса:
SELECT id FROM user WHERE name = ?
может быть следующим образом сформирована с помощью средств языка Python:
from sqlalchemy.sql import table, column, select
user = table('user', column('id'), column('name'))
stmt = select([user.c.id]).where(user.c.name=='ed')

Структура описанной выше конструкции select показана на Рисунке 20.8. Следует отметить, что представление строкового значения 'ed' находится внутри конструкции _BindParam, что приводит к трактовке его как маркера параметра ограничения, для обозначения которого в строке SQL-запроса используется знак вопроса.

Пример древовидного представления запроса
Рисунок 20.8: Пример древовидного представления запроса

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

Подход к использованию операторов языка Python

В SQLAlchemy подобный следующему запрос:
column('a') == 2
возвращает не логический результат True или False, а конструкцию SQL-запроса. Это делается для того, чтобы выполнить перегрузку операторов с помощью специальных функций для управления операторами языка Python, т.е., таких методов, как __eq__, __ne__, __le__, __lt__, __add__, __mul__. Узлы структуры, связанные со столбцами, предоставляют возможность работы с перегруженными операторами языка Python путем использования смешанного класса с именем ColumnOperators. После использования возможности перегрузки операторов запрос column('a') == 2 эквивалентен следующему коду:
from sqlalchemy.sql.expression import _BinaryExpression
from sqlalchemy.sql import column, bindparam
from sqlalchemy.operators import eq

_BinaryExpression(
    left=column('a'),
    right=bindparam('a', value=2, unique=True),
    operator=eq
)

Конструкция eq на самом деле является функцией из встроенного модуля operator. Представление операторов в виде объектов (т.е., operator.eq) вместо строк (т.е., =) позволяет задавать строковое представление запроса во время компиляции, когда имеется информация о диалекте используемой базы данных.

Компиляция

Главным классом, ответственным за преобразование древовидных представлений SQL-запросов в текстовый формат SQL-запросов является класс с именем Compiled. Этот класс имеет два основных подкласса, SQLCompiler и DOLCompiler. Класс SQLCompiler выполняет операции преобразования запросов SELECT, INSERT, UPDATE и DELETE, обобщенно классифицируемых как элементы языка запросов данных (data query language - DQL) и языка для манипуляций с данными (data manipulation language - DML), в то время, как класс DDLCompiler выполняет операции преобразования различных запросов CREATE и DROP, классифицируемых как элементы языка описания данных (data definition language - DDL). Существует дополнительная иерархия классов, предназначенная для реализации функций преобразования представлений строк на основе типов, задаваемых в рамках класса TypeCompiler. Отдельные диалекты предоставляют свои собственные подклассы всех трех типов классов компилятора с целью поддержки специфических для используемой базы данных аспектов языка SQL-запросов. На Рисунке 20.9 представлен обзор этой иерархии классов, сформированной для работы с диалектом PostgreSQL.

Иерархия классов компилятора, включающая специфичные для PostgreSQL реализации классов
Рисунок 20.9: Иерархия классов компилятора, включающая специфичные для PostgreSQL реализации классов

Подклассы класса Compiled описывают наборы visit-методов, на каждый из которых ссылается определенный подкласс класса ClauseEvent. Производится обход иерархии классов узлов ClauseEvent, после чего запрос формируется путем рекурсивного объединения строковых результатов, возвращаемых каждой из visit-функций. По мере выполнения этой работы объект Compiled изменяет состояние в зависимости от имен анонимных идентификаторов, имен граничных параметров, а также находящихся среди прочих параметров в составе запроса подзапросов, каждый из которых влияет как на результат генерации строки SQL-запроса, так и на конечный набор граничных параметров с их значениями по умолчанию. Рисунок 20.10 иллюстрирует процесс использования visit-методов для получения текстовых фрагментов запроса.

Иерархия вызовов функций в ходе компиляции запроса
Рисунок 20.10: Иерархия вызовов функций в ходе компиляции запроса

Заполненная данными структура Compiled содержит завершенную строку SQL-запроса и набор граничных значений. Эти данные преобразуются с помощью класса ExecutionContext в формат, ожидаемый методом execute реализации интерфейса DBAPI, который использует предположения об обязательном использовании кодировки Unicode в рамках объекта запроса, о типе коллекции, используемой для хранения граничных значений, а также о специфике того, как граничные условия должны преобразовываться в представления, подходящие для использования совместно с реализацией интерфейса DBAPI и используемой базой данных.


Продолжение статьи: Отображение классов при использовании объектно-реляционного отображения