Объектное представление о реляционной модели

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

Само существо процесса перехвата сильно зависит от используемой СУБД и в большинстве случаев не представляет большого труда. Алгоритм работы сервера объектного представления предельно лаконичен:

  • получить запрос;
  • проверить относится запрос к классу (классам) или отношениям;
  • если запрос направлен к классу (классам), то выполнить перетрансляцию;
  • направить запрос к СУБД;
  • получить результаты;
  • если необходимо, то выполнить дополнительную обработку результатов запроса;
  • вернуть результаты запроса пользователю.

Проверка запроса сводится к получению списка таблиц, участвующих в запросе. Для каждой таблицы делается попытка найти её имя в отношении CLASSES. Если хотя бы одна таблица представляет собой класс, то запрос направляется на стадию перетрансляции. В противном случае, он перенаправляется к СУБД в первозданном виде.

Запрос на создание класса следует отличать от запроса на создание обычной таблицы, с этой целью в операторе CREATE TABLE вводится дополнительно четыре опциональных параметра.

CREATE TABLE <table name> [AS CLASS [ABSTRACT][INHERITED FROM <class name>]] (

[METHOD <method name> (<list of parameters>) [,
METHOD <method name> (<list of parameters>)]]
)

Опция AS CLASS определяет, что <table name>, специфицирует имя класса.

Опция ABSTRACT определяет класс, как абстрактный. Здесь и далее под абстрактным классом понимается класс, который не имеет связанных с ним одной или более таблиц.

Опция INHERITED FROM определяет, что данный класс является подклассом <class name>.

Опция METHOD определяет, что хранимая процедура с именем <table name> || <method name> является методом <method name> данного класса. Оператор “||” обозначает строковую операцию конкатенации. Вы вправе принять любое другое соглашение по именованию методов.

При создании подкласса, не являющегося абстрактным, необходимо получить имена и типы полей суперкласса, а также его индексы, триггеры, методы и ограничения. Это требуется для создания одной или более таблиц (экземпляров класса) ассоциированных с данным классом. Таблицы, ассоциированные с классом, должны иметь весь набор полей, полей, индексов, триггеров, методов и ограничений, определённых у всех суперклассов в совокупности.

Рассмотрим простейшую схему наследования. Пусть создаётся класс C, который является подклассом класса B, который, в свою очередь, является подклассом класса А (AÞ   BÞ     C). Пусть класс A имеет поля f1a и f2a, индекс i1a и методы m1a, m2a и m3a. Класс B имеет поля f1b, f2b и f3b, и метод m1b. Класс C имеет поле f1c, индекс i1c, методы m1c и m2c, а также ограничения co1c и co2c. Тогда таблица-экземпляр, созданная на основе описания класса С будет иметь:

поля: f1a, f2a, f1b, f2b, f3b, f1c;
индексы: ia1, ic1;
методы: m1a, m2a, m3a, m1b, m1c, m 2c;
ограничения: co1c, co2c.

В большинстве случаев порядок следования и определения полей, индексов, триггеров, методов и ограничений безразличен, но лучше придерживаться некоторого выбранного алгоритма, например, последовательность определения начинается с самого дальнего суперкласса (корня иерархии).

Наверное, нет смысла говорить, что менять структуру классов – скверная практика. Но в момент разработки возникают ситуации, когда данная операция необходима. Именно поэтому должна быть реализована возможность изменения структуры классов. Насколько осторожно и аккуратно ею будут пользоваться разработчики, целиком зависит от их опыта и квалификации. Аналогично тому, как мы можем менять структуру таблиц с помощью оператора ALTER TABLE, точно также можно воспользоваться этим оператором для изменения структуры класса.

ALTER TABLE <table name> [AS CLASS [ABSTRACT][INHERITED FROM <class name>]] (ADD|DROP) …
[METHOD <method name> (<list of parameters>) [,
METHOD <method name> (<list of parameters>) ]]

Опция AS CLASS позволяет превратить таблицу с именем <table name> в одноимённый класс. ABSTRACT определяет, что класс является абстрактным, если ранее класс существовал, но как реальный, то применение к нему данной опции приведёт к уничтожению связанных с ним одной или более таблиц. Опция INHERITED FROM <class name> делает возможным наследование подкласса от класса с именем <class name>. Это приводит к тому, что подкласс будет расширен полями, индексами, ограничениями, триггерами и методами суперклассов. В случае пересечения имён, например, если исходный класс и суперкласс имеют поля с одинаковым именем, операция должна быть отменена с выдачей соответствующего диагностического сообщения.

Традиционно изменение таблиц возможно только за счёт добавления или удаления полей и ограничений (CONSTRAINT). Но, работая с классами, можно аналогичным образом добавлять или удалять методы.

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

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

DROP TABLE <class name>;

Здесь <class name> является именем удаляемого класса.
При удалении класса следует предусмотреть проверку ссылочной целостности. Если класс имеет бинарные связи с другими классами или таблицами, то его удаление невозможно. В такой ситуации операция отменяется, а пользовательское приложение должно быть оповещено о возникшей проблеме.

Удаление суперкласса автоматически приводит к удалению всех его подклассов.

 К запросам на изменение пользовательских данных относятся запросы вставки (INSERT), изменения (UPDATE) и удаления (DELETE). Обработка этих запросов имеет как сходные, так различные черты. Главное различие в обработке запроса на вставку и запросами на изменение и удаление заключается в том, что запрос на вставку всегда направлен к одному и только одному экземпляру класса (таблице). В то время, как запросы на изменение и удаление захватывают не только тот класс, к которому они обращены, но и множество его подклассов. Это свойство не всегда очевидно. Можно проиллюстрировать это простым примером. Нельзя создать (отобразить) абстрактную плоскую фигуру, но не составляет труда нарисовать окружность заданного радиуса и цвета. В то время как можно потребовать изменить цвет любую фигуру или потребовать убрать с экрана фигуры, цвет которых равен заданному в условии цвету. Первое действие по созданию (отображению) аналогично добавлению, вторая и третья операции аналогичны изменению и удалению записей в некоторой таблице.

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

Здесь имеет смысл отметить что, тем не менее, запросы на вставку должны перехватываться сервером объектного представления и обрабатываться. Это связано с наличием связей между классами. Поскольку существующие СУБД поддерживают связи только между таблицами, а класс может быть представлен множеством таблиц, то, следовательно, сервер объектного представления должен выполнить эту работу сам. В качестве иллюстрации, можно привести простой пример. Пусть товары в магазин поставляют множество различных поставщиков, которые можно разделить на группы: поставщики-производители продукции, оптовые поставщики и, наконец, частные лица. Все группы поставщиков наряду с общими атрибутами обладают и уникальными, характерными только для данной группы. Тогда целесообразно представить каждую группу поставщиков, как подкласс абстрактного класса “ПОСТАВЩИКИ”. Теперь у нас появляется необходимость связать два абстрактных класса “ТОВАРЫ” и “ПОСТАВЩИКИ” бинарной связью многие ко многим (M-N) (каждый поставщик может поставлять множество разнообразных товаров и один вид товара может поставляться множеством различных поставщиков). Теперь при занесении в базу данных информации о новой поставке товара нам необходимо связать его с поставщиком. Однако поставщик товара может принадлежать любой группе (классу). Поэтому невозможно установить связь на уровне таблиц, но связь “ПОСТАВЩИКИ” – “ТОВАРЫ” абсолютно справедлива на уровне классов. С другой стороны, поскольку каждый товар должен иметь своего поставщика, то справедливо утверждение, что связь между поставщиком и товаром определяется на абстрактном уровне классов “ПОСТАВЩИКИ” – “ТОВАРЫ”, что и требовалось доказать.

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

Запросы на изменение и удаление, направленные к конкретному классу воздействуют и на все его подклассы. Здесь опять не возникает необходимости в изменении синтаксиса SQL запросов данного вида. При удалении всегда, а при изменении в только случае, если изменяются поля, входящие в уникальные (первичный) индексы, необходимо выполнять проверку на ссылочную целостность. Вся операция по удалению и изменению должна проводиться в рамках одной транзакции примерно в следующей последовательности:

  • начать транзакцию;
  • запустить триггеры класса (before);
  • проверить ссылочную целостность;
  • провести операцию;
  • запустить триггеры класса (after);
  • закончить транзакцию.

В случае если хотя бы один из шагов не может быть выполнен, транзакция должна быть возвращена в исходное состояние без каких-либо изменений в базе данных (rollback). При успешном завершении всех операций транзакция должна быть завершена оператором commit work.

Выборка данных из класса сводится к выборке данных из всех таблиц, ассоциированных, как с данным классом, так и со всеми его подклассами. Такое решение позволяет пользователям рассматривать любой класс, как одно цельное отношение. Детали того, что данное отношение является логическим и разбросано по нескольким физическим отношениям (таблицам), скрыты от пользователя. В результате достигается возможность абстрагирования от несущественных деталей. Когда нужно видеть такую сущность, как товар, то бессмысленно уточнять о каком виде товара идёт речь. Пользователь имеет возможность манипулировать только теми атрибутами, которые являются общими для любого товара. С другой стороны, сущность “ТОВАРЫ” определяет первичный ключ и, следовательно, не возникает проблем в случае, если необходимо узнать более подробную информацию о конкретном товаре. Связи между классами дают возможность составлять запросы на весьма высоком уровне абстракции. Например, мы будем в состоянии ответить на вопрос о том, какие партии товара поставлял поставщик, имеющий заданный адрес (банковские реквизиты, и т.п.), в течение определённого периода времени. При этом подразумевается, что и сущность “ПОСТАВЩИКИ” и сущность “ТОВАРЫ” представлены классами, то есть имеют несколько отличающихся в деталях видов.

Запросы на выборку данных записываются на SQL с помощью оператора SELECT. Этот оператор в своей объектной форме ничем не отличается от традиционного оператора и может иметь сложную структуру. Схема трансляции запроса, направленного к классу, в запрос, направленный к физическим отношениям (таблицам) логически достаточно прост и может иметь различные реализации. Реализация схемы трансляции сильно зависит от используемой СУБД. Дело в том, что каждая коммерческая СУБД имеет как свои ограничения, так и сильные/слабые стороны при выполнении сложных запросов. Для достижения эффективной реализации эти особенности конкретной СУБД необходимо учитывать.

Самое простое решение, доступное почти для всех коммерческих СУБД – это порождение временных таблиц. Однако такое решение может снизить скорость выполнения запроса на некоторых СУБД. Для них возможно более эффективным решением будет динамическая генерация VIEW, курсоров или даже хранимых селективных процедур. Наконец, некоторые СУБД имеют очень эффективные оптимизаторы запросов и могут легко “переваривать” запросы практически любой сложности. Для таких СУБД вполне применимо решение “в лоб” с использованием соединения запросов к нескольким отношениям посредством UNION. В исключительных случаях, можно превратить запрос к классу в последовательность запросов к физическим отношениям и “склеивание” результатов проводить непосредственно на сервере объектного представления. Но следует учитывать, что наличие в запросе агрегатных функций или предложений ORDER BY или GROUP BY, может привести к весьма сложной реализации самого сервера объектного представления.

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

Пример трансляции запроса с помощью соединения (UNION) можно проиллюстрировать следующим образом:пусть есть некоторый запрос о товаре..

SELECT * FROM GOODS WHERE SUPPLIER = ‘Рога и копыта’;

Его трансляция из объектной формы в физическую форму примет следующий вид:

SELECT <fields of class> FROM FOODS WHERE SUPPLIER = ‘Рога и копыта’
UNION
SELECT <fields of class> FROM FURNITURES WHERE SUPPLIER = ‘Рога и копыта’;

Здесь <fields of class> есть перечень полей, определённых для класса “ТОВАРЫ” (GOODS), но не для отношений “ПРОДУКТЫ” (FOODS) или “МЕБЕЛЬ” (FURNITURES). Поскольку набор атрибутов каждого из отношений может быть больше, чем у класса “ТОВАРЫ”, то, следовательно, выбираться из отношений будет только некоторое подмножество принадлежащих им атрибутов. В этом случае необходимо заменить символ “*” на список атрибутов, которые определены на уровне класса “ТОВАРЫ”.

Аналогичным образом в более сложных запросах можно сохранять результаты вложенных подзапросов во временных таблицах или продуцировать VIEW, курсоры или селективные хранимые процедуры.

Сайт Alexus Software Development