Библиотека сайта 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: Базовая иерархия классов запроса
SELECT id FROM user WHERE name = ?
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
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.
Рисунок 20.9: Иерархия классов компилятора, включающая специфичные для PostgreSQL реализации классов
Подклассы класса Compiled
описывают наборы visit-методов, на каждый из которых ссылается определенный подкласс класса ClauseEvent
. Производится обход иерархии классов узлов ClauseEvent
, после чего запрос формируется путем рекурсивного объединения строковых результатов, возвращаемых каждой из visit-функций. По мере выполнения этой работы объект Compiled
изменяет состояние в зависимости от имен анонимных идентификаторов, имен граничных параметров, а также находящихся среди прочих параметров в составе запроса подзапросов, каждый из которых влияет как на результат генерации строки SQL-запроса, так и на конечный набор граничных параметров с их значениями по умолчанию. Рисунок 20.10 иллюстрирует процесс использования visit-методов для получения текстовых фрагментов запроса.
Рисунок 20.10: Иерархия вызовов функций в ходе компиляции запроса
Заполненная данными структура Compiled
содержит завершенную строку SQL-запроса и набор граничных значений. Эти данные преобразуются с помощью класса ExecutionContext
в формат, ожидаемый методом execute
реализации интерфейса DBAPI, который использует предположения об обязательном использовании кодировки Unicode в рамках объекта запроса, о типе коллекции, используемой для хранения граничных значений, а также о специфике того, как граничные условия должны преобразовываться в представления, подходящие для использования совместно с реализацией интерфейса DBAPI и используемой базой данных.
Продолжение статьи: Отображение классов при использовании объектно-реляционного отображения